Add Declarative Web Framework Design
parent
181849d640
commit
ae5ece7c13
1 changed files with 127 additions and 0 deletions
127
Declarative-Web-Framework-Design.md
Normal file
127
Declarative-Web-Framework-Design.md
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
# Declarative Web Framework
|
||||||
|
|
||||||
|
The eventual goal of this is to create a language that compiles down to files, in a very similar way to Nix. The idea is to have a pure language that describes an impure computation.
|
||||||
|
|
||||||
|
* Some kind of type that represents "files" on disk. This is the compile output
|
||||||
|
* Need a way of saying "generate me a path during compile-time" in a way that all of the downstreams still refer to the same path
|
||||||
|
* Could be a hash based on the file:span of the invocation?
|
||||||
|
* Lots of tools for performing string parsing and editing
|
||||||
|
* A higher-level "resource" that may compile to files.
|
||||||
|
* A resource consists of:
|
||||||
|
* Arbitrary attribute set, that can include other sub-resources
|
||||||
|
* Recommended to only have one of the following two:
|
||||||
|
* A set of "default" sub-resources to include
|
||||||
|
* A set of files to emit
|
||||||
|
* A function that operates over and modifies other resources
|
||||||
|
* Example: a database schema may be a higher-level resource. Compiling this should result in some database files
|
||||||
|
* To perform migrations, the migration CLI tool that's generated would write a file indicating the new state of the database it just learned back into the source repo to be checked in.
|
||||||
|
* I think the type signature should look something like this:
|
||||||
|
* ```
|
||||||
|
DatabaseTable : {
|
||||||
|
name: String,
|
||||||
|
files: Set<File>,
|
||||||
|
// Resources is essentially a set of files along with some particular fields of interest
|
||||||
|
// Here, apply is allowed to assume that all the files in the files field exist
|
||||||
|
apply: Resources -> Resources
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* A concept of a "singleton" resource
|
||||||
|
* I think this would mostly be for the post-compilation step, where including for example any DocumentationResource should trigger a post-compilation collection of all documentation resources
|
||||||
|
* Quoting for various source code languages like Javascript
|
||||||
|
|
||||||
|
## Stages of compilation
|
||||||
|
|
||||||
|
1. A non-Turing complete step is used to collect everything into a flat set of resources
|
||||||
|
2. An optional post-compilation step is run, consisting of all the PostCompilationResource
|
||||||
|
|
||||||
|
## Example components
|
||||||
|
|
||||||
|
### Auth plugin
|
||||||
|
|
||||||
|
```
|
||||||
|
let db : SQLDatabase<Sqlite> = ...
|
||||||
|
let adaptor : SQLWithDb<_> = Adaptor({ db })
|
||||||
|
let authPlugin = AuthPlugin({
|
||||||
|
backend: ... // some kind of config for a database adaptor
|
||||||
|
// one example would be "SQL db w/ password"
|
||||||
|
// another could be "SQL db w/ magic link"
|
||||||
|
clientStrategy: ... // some config like session cookie or jwt
|
||||||
|
allowGuests: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
authPlugin would be {
|
||||||
|
base: Resource,
|
||||||
|
usersTable: SQLTableResource,
|
||||||
|
loginRoute: (LoginRouteOpts) -> EndpointResource,
|
||||||
|
loginComponent: (LoginComponentOpts) -> WebComponentResource,
|
||||||
|
registerRoute: (RegisterRouteOpts) -> EndpointResource,
|
||||||
|
registerComponent: (RegisterComponentOpts) -> WebComponentResource,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comments plugin
|
||||||
|
|
||||||
|
```
|
||||||
|
let commentsPlugin = CommentsPlugin({
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
result {
|
||||||
|
base: Resource,
|
||||||
|
component: (CommentComponentOpts) -> WebComponentResource,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example endpoint
|
||||||
|
|
||||||
|
```
|
||||||
|
let submitFlagComponent = ({}) => {
|
||||||
|
let endpoint = EndpointResource({
|
||||||
|
guards: {
|
||||||
|
user: UserAuthGuard(),
|
||||||
|
flags: DbTableGuard(flagTable),
|
||||||
|
flagChecker: FlagCheckerGuard(user),
|
||||||
|
submittedFlag: PostJsonFieldGuard("flag", "string"),
|
||||||
|
},
|
||||||
|
// This entire next part emits an AST, it does NOT actually run this code
|
||||||
|
action: do {
|
||||||
|
result <- flagChecker(submittedFlag);
|
||||||
|
flags.insert({
|
||||||
|
userId: user.id,
|
||||||
|
flag: submittedFlag,
|
||||||
|
correct: result.correct,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let inputBox = WebComponentResource(jsxQuote!(
|
||||||
|
({ postSubmit }) => {
|
||||||
|
const error = useState();
|
||||||
|
const onSubmit = () => {
|
||||||
|
const result = await fetch(#{endpoint.url}, { method: "POST" });
|
||||||
|
};
|
||||||
|
return <form onSubmit={onSubmit}>
|
||||||
|
<input placeholder="Submit flag..." />
|
||||||
|
</form>;
|
||||||
|
}
|
||||||
|
))
|
||||||
|
Resource.bundle([ endpoint, inputBox ])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy to kubernetes plugin
|
||||||
|
|
||||||
|
This would be a function that takes a resource consisting of a bunch of endpoints, database tables, etc. and produces a deploy script
|
||||||
|
|
||||||
|
### Deploy to docker compose plugin
|
||||||
|
|
||||||
|
Run a post-compile script that produces a docker image with a particular image name, as well as a `docker-compose.yml` file
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
There should be a "documentation resource" type, which gets collected all together with a search index
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
* Need something like multi-stage programs? Functions annotated with @stage(n). Can't imagine a situation needing this yet
|
||||||
|
*
|
Loading…
Reference in a new issue