app infrastructure and web components
npm install xiome- đ xiome components are universal plugins for websites
- đĄī¸ let your users enjoy a passwordless login experience
- đ engage your users with cool features, like a questions board
- đ° monetize your community with subscriptions and paywalls
- âī¸ let xiome cloud do the heavy lifting running the servers
- ⥠jumpstart your app by using xiome's auth system
1. create your community at https://xiome.io/setup
2. copy-paste your community's html install snippet into your web page's html
3. copy-paste any components you like from https://xiome.io/components into your website's
(click to show details)
fundamental skills
- âšī¸ you don't have to master these skills. just be aware of them, so you know what to study when you encounter the need
- learn how to use git and github so you can collaborate
- fork projects on github
- use a visualization tool like gitk to understand git graphs
- make and manage branches
- add and reset staged changes, make and amend commits
- manage git remotes, fetch, pull, and pull branches
- interactive rebase to rewrite and cleanup history
- keep your branches up to date by rebasing onto master regularly
- resolve and merge conflicts
- make pull requests on github, and respond to code reviews
- learn the basics of using a bash shell
- learn the basics of typescript
- learn npm to install dependencies and run npm package scripts
- learn how to write code that blends in with the style and formatting of the rest of the codebase
- please be aware of the whitespace you author (the vscode setting Render whitespace setting Boundary is great for this)
technical prerequisites
- if you're on windows, first, setup wsl and learn how it works
- or otherwise install a linux virtual machine for development (we recommend debian+kde on vmware)
- install git, nodejs, vscode, and connect github with ssh keys
initial setup
- fork the xiome project on github, and git clone your fork
- open a terminal in your cloned directory, and run these commands
- npm install to install the project's dependencies
- npm run build to run a full build of the project
during your development sessions
- run npm install if you've recently pulled any changes to the package json
- run these background processes, each in their own terminal
- npm run watch to continually rebuild source files on save
- npm start to run a local http server at http://localhost:8080/
(if npm start fails, try it again, it often works the second time)
- note: tmux is a great way to split terminal windows
- note: of course, when you're done, end each process by pressing ctrl+c
- open vscode by running code .
- open your web browser
- see the xiome website at http://localhost:8080/
- see the mocksite at http://localhost:8080/mocksite/
- disable your browser's caching
- open chrome's developer tools
- in the network tab, enable "disable cache" mode
- or find the equivalent in your plebeian browser
- now you are ready to code
- whenever you save a source file, the watch routine will automatically rebuild it, then you can refresh the browser to see your changes
- you can press vscode hotkey ctrl+shift+b to run the typescript build, which allows vscode to nicely highlight typescript errors for you to address
xiome's mock mode and the mocksite
- xiome super-cool mock mode
- the watch routine builds xiome into "mock" mode
- in mock mode, no connections are made to any real apis, database, etc
- 100% of the code, even the backend code, is running locally within the browser
- this allows you to test xiome's features without the muss or fuss of externalities like running servers or databases
- this mock mode also provides a unified debugging experience within the browser (even for backend business logic)
- the "database" state is actually saved in localStorage
- in the browser dev console, you can wipe clean all the state by calling localStorage.clear() then refreshing the page
- you can work on the xiome.io website itself at http://localhost:8080/
- you can develop new features in the "mocksite" at http://localhost:8080/mocksite/
- the mocksite is a crappy-looking site that mimics a website that had installed xiome
- it's a "proving grounds" for developing new features
- you can login with a special email address creative@xiome.io, which is a special fake account on the mocksite that has administrator privileges
the more you know
- this is an open source project, all contributions are under the mit license
(click to show details)
everything about xiome is fully contained in this single git repository.
let's take a stroll through the codebase.
- .github/
this is where the github actions live.
they do continuous integration and deployments.
- .vscode/
this is where the settings for the vscode editor live.
it makes sure you'll indent with tabs, as jesus intended.
- helm/
this is where the scary kubernetes code lives.
it orchestrates the node servers in the cloud.
- s/
this is where all the typescript source code lives.
there is where the fun development happens.
- s/assembly/
this is where all the pieces of xiome are assembled, much like legos, into a working backend and frontend.
this place is honestly frightening.
- s/common/
this is where we should put xiome code that many features can share.
- s/features/
this is where the feature development happens.
each feature contains all its backend and frontend code.
you should feel cozy here.
- s/framework/
this is where xiome defines fundamental standards for the whole system.
important base classes, like component, which is based on lit-element.
- s/toolbox/
this is a big collection of assorted utilities.
code here may be useful outside of xiome.
some of these tools are candidates to become independent libraries one day.
some of these are experimental.
- s/types/
these are where some common system-wide types should go.
- scripts/
just some handy shell scripts.
sometimes i use scripts/randomid generate new ids.
- web/
this is where the https://xiome.io/ website lives.
and also the mocksite we use during development.
- x/
these are just build artifacts.
never write code here â it's deleted and regenerated every build.
(click to show details)
xiome's fun code is organized conceptually into features.
these features are probably what you want to work on.
so here's what's in a feature directory:
feature/components/ feature/models/ feature/api/ feature/coolfeature.test.ts feature/testing/ if you think about xiome like an onion, you'll notice some distinct layers.
each layer has its own little landscape of concepts and tools you'll need to learn, if you want to get anything done.
opsops for loading spinnersopsops work further down the readmexiome are "wired up" with models and state managementxio are simpler standalone components without any wiringsthis.share, which is a bag of goodies, like models and other facilitiesintegrate-blah-components.tstheme.css.ts existss/framework/theme.css.tssnapstatereadable state, but not the writable statesubscribe or track functionsnapstate further below in the readmeopsops further below in the readmerenraku and how xiome uses itexpose takes a first argument called authauth is where you'll find info about the current userauth is where you'll find the database tables you need to useauth is returned by the policy that each service hasdbmage to interact with the databasedbmage.Id instance in the databasedbmage further downdarkvalley for validationdarkvalley further down(click to show details)
- "ops" is xiome's system for displaying loading spinners for asynchronous operations ${value} ${value}
- it's designed to be compatbile with state management libraries â this is why ops are simple object literals, instead of fancy class instances with methods and stuff
- ``ts`
import {ops, Op} from "./s/framework/ops.js"
op
- an is an object that can be in one of four states:none
- â the op is uninitializedloading
- â the op is loadingerror
- â an error has occurredready
- â loading is done, the data is readyops
- is a toolkit with functions to create or interpret op objectsops.none()
- create ops
- â create an op in none stateops.loading()
- â create an op in loading stateops.error("thing failed lol")
- â create an op in error state, provide a reason stringops.ready(value)
- â create an op in ready state, provide the data valueops.replaceValue(op, newValue)
- â create an op with the same state as another opops.isNone(op)
- check the current state of an op (return a boolean)
- ops.isLoading(op)
- ops.isError(op)
- ops.isReady(op)
- ops.value(op)
- extract the value out of an op (or return undefined)
- `
- typescript types
ts`
let textOp: Op
textOp = ops.ready("hello")
`
- select (return different values based on the state of an op)
ts`
const value = ops.select(op, {
none: () => 1,
loading: () => 2,
error: reason => 3,
ready: value => 4,
})
`
- running async operations
(perform an async operation, while updating an op property)
ts`
let textOp: Op
const text = await ops.operation({
setOp: op => textOp = op,
promise: fetchTextFromSomewhere(),
errorReason: "failed to fetch the text",
})
`
- consolidate many ops into one
(only in terms of state, value is discarded)
ts`
const op = ops.combine(op1, op2, op3)
ops.mode(op)
- debugging tools
- â return an op's mode expressed as a stringconsole.log(ops.debug(op))
- â log the op's details for console debugging`
- usage in components
- the xio-op component is for low-level control of op rendering
(you can customize the loading spinner and more)
js
html`
- renders a loading spinner when the op is loading
- has a slot for each op state
- use renderOp to render a proper component for an op`
ts
import {renderOp} from "./s/framework/render-op.js"
render() {
return renderOp(op, value => html
)`
}
- render an op-wrapped value, but without any loading spinner
(no component)`
ts
import {whenOpReady} from "./s/framework/when-op-ready.js"
render() {
return whenOpReady(op, value => html
)`
}
(click to show details)
- đŽ snapstate is how xiome manages frontend application state
- see snapstate's readme on github: https://github.com/chase-moskal/snapstate
(click to show details)
- đ§ââī¸ dbmage is how xiome systems interact with the database.
- see dbmage's readme on github: https://github.com/chase-moskal/dbmage
(click to show details)
- darkvalley is xiome's validation system for user inputs
- it's used on the frontend and backend alike, for validating forms, and apis
- a darkvalley validator is a function that returns a "problems" array of strings
- the problem strings are meant to be user-readable
- darkvalley provides many functions that return validator functions
- let's make an example validator for a string
`ts
import {validator, string, minLength, maxLength, notWhitespace}
from "./s/toolbox/darkvalley.js"
const validateCoolString = validator
// ^
// create a standard validator,
// providing a typescript generic
// for the type that it will accept
string(), // <--------- require input to be a string
minLength(1), // <----- require input length is at least 1
maxLength(10), // <---- require input length at most 10
notWhitespace(), // <-- require input isn't all whitespace
)
const problems1 = validateCoolString("hello!")
//= []
const problems2 = validateCoolString("")
//= ["too small"]
const problems3 = validateCoolString("abcdefghijk")
//= ["too big"]
const problems4 = validateCoolString(" ")
//= ["can't be all whitespace"]
``
- if the resulting problems array is empty (problems.length === 0), then the input has passed validation
- darkvalley has functions to prepare many kinds of validators
- for example, let's validate an array of numbers between 0 and 100
ts
import {validator, array, each, number, min, max}
from "./s/toolbox/darkvalley.js"
const validateNumberArray = validator
array(),
each(
number(),
min(0),
max(100),
),
)
const problems1 = validate([1, 2, 99])
//= []
const problems2 = validate([1, 2, "99"])
//= ["(3) must be a number"]
const problems3 = validate([1, 2, -99])
//= ["(3) too small"]
const problems4 = validate([101, 2, 99])
//= ["(1) too big"]
``
- okay, now let's validate a whole object, and all its contents
ts
import {schema, validator, string, number, minLength, maxLength}
from "./s/toolbox/darkvalley.js"
const validateUserObject = schema<{
nickname: string
karma: number
}>(
nickname: validator(string(), minLength(1), maxLength(10)),
karma: validator(number()),
)
const problems1 = validateUserProblems({nickname: "chase", karma: 99})
//= []
``
- darkvalley has a bunch of handy validator preppers
ts
import {validator, string, regex, url, origin, email}
from "./s/toolbox/darkvalley.js"
const validateLetters = validator
string(),
regex(/[a-zA-Z]+/i, "must be letters"),
)
const validateUrl = validator
const validateOrigin = validator
const validateEmail = validator
`multi
- allows multiple problems to be returned at once, validator
whereas stops and returns the first problem encountered.`
ts
import {validator, multi, string, minLength, notWhitespace}
from "./s/toolbox/darkvalley.js"
const validateName = validator
string(),
multi( // <-- multi allows multiple problems to be returned at once
minLength(3),
notWhitespace(),
),
)
const problems1 = validateName(" ")
//= ["too small", "can't be all whitespace"]
`branch
- is like an 'or' operator, ignoring problems in one branch if another passes`
ts
import {validator, branch, string, url, https, localhost}
from "./s/toolbox/darkvalley.js"
const validateHttpsOrLocalhost = validator
string(),
url(),
branch(
https(),
localhost(),
),
)
const problems1 = validateHttpsOrLocalhost("http://chasemoskal.com/")
//= [
//= "must be secure, starting with 'https'",
//= "or, must be a localhost address"
//= ]
``
(click to show details)
we like to give little bitcoin rewards to show appreciation for good contributions.
how to participate:
- find a task on the issues page with a bounty
- post a comment and ask to be assigned
- do the work, make a good pull request
- post your public bitcoin deposit address into the issue
if we merge the work to master, you may be eligible to receive a reward.
but remember, there are no guarantees: bounties are fun rewards, not contracts. the rules became too complicated, so now all bounties and rewards are arbitrated by chase moskal based on subjective factors and personal honor code.