babavoss 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +7 -0
- package/bin/voss.ts +48 -0
- package/gui/babavoss-web.js +234 -0
- package/gui/chunk-5hpp1ypv.js +11710 -0
- package/gui/chunk-9rq662fd.js +8627 -0
- package/gui/chunk-k3cm8j6r.js +433 -0
- package/gui/chunk-pk09y98y.js +50 -0
- package/gui/chunk-smz02qa6.js +186 -0
- package/gui/chunk-wwqypxre.js +49 -0
- package/gui/gui.js +2015 -0
- package/gui/react-compiler-runtime.js +39 -0
- package/gui/react-dom-client.js +21 -0
- package/gui/react-dom.js +44 -0
- package/gui/react-jsx-runtime.js +19 -0
- package/gui/react.js +105 -0
- package/gui/theme.css +3053 -0
- package/index.ts +15 -0
- package/package.json +50 -4
- package/src/baba/check.ts +90 -0
- package/src/baba/config.ts +261 -0
- package/src/baba/find.ts +12 -0
- package/src/baba/init.ts +176 -0
- package/src/baba/node.ts +483 -0
- package/src/baba/project.ts +63 -0
- package/src/baba/registry.ts +35 -0
- package/src/baba/worker.ts +55 -0
- package/src/bench/index.ts +6 -0
- package/src/bench/measure.ts +132 -0
- package/src/bench/scenarios.ts +136 -0
- package/src/build/builder.ts +74 -0
- package/src/build/failure.ts +78 -0
- package/src/build/guard.ts +85 -0
- package/src/build/mdx-register.ts +3 -0
- package/src/build/mdx.ts +38 -0
- package/src/build/project.ts +38 -0
- package/src/build/views.ts +146 -0
- package/src/builder/main.ts +29 -0
- package/src/desktop/bob.ts +76 -0
- package/src/desktop/desktop.css +111 -0
- package/src/desktop/icons.ts +50 -0
- package/src/desktop/index.ts +323 -0
- package/src/desktop/routes.ts +98 -0
- package/src/desktop/view.tsx +673 -0
- package/src/door/core.ts +384 -0
- package/src/ecs/baba.ts +431 -0
- package/src/ecs/codec.ts +334 -0
- package/src/ecs/handles.ts +91 -0
- package/src/ecs/replica.ts +150 -0
- package/src/ecs/runtime.ts +603 -0
- package/src/ecs/scheduler.ts +75 -0
- package/src/ecs/snapshot.ts +102 -0
- package/src/ecs/state.ts +759 -0
- package/src/ecs/system.ts +256 -0
- package/src/ecs/table.ts +420 -0
- package/src/ecs/testbed.ts +97 -0
- package/src/exec/host.ts +177 -0
- package/src/exec/main.ts +98 -0
- package/src/exec/watch.ts +7 -0
- package/src/exec/wire.ts +29 -0
- package/src/generated/build.ts +4 -0
- package/src/gui/css.d.ts +1 -0
- package/src/gui/gui.tsx +245 -0
- package/src/gui/index.ts +51 -0
- package/src/gui/inspector.tsx +47 -0
- package/src/gui/levels.tsx +73 -0
- package/src/gui/promptware.tsx +68 -0
- package/src/gui/runner.tsx +118 -0
- package/src/gui/theme.css +498 -0
- package/src/gui/theme.ts +25 -0
- package/src/gui/wizard.tsx +227 -0
- package/src/guide/add-a-desktop.mdx +100 -0
- package/src/guide/compose-an-interface.mdx +84 -0
- package/src/guide/index.ts +13 -0
- package/src/guide/reach-outside.mdx +112 -0
- package/src/guide/spec-a-system.mdx +93 -0
- package/src/guide/systems-together.mdx +72 -0
- package/src/guide/write-a-system.mdx +183 -0
- package/src/guide/write-promptware.mdx +90 -0
- package/src/http/server.ts +310 -0
- package/src/kernel/build.ts +21 -0
- package/src/kernel/builder.ts +105 -0
- package/src/kernel/children.ts +117 -0
- package/src/kernel/context.ts +90 -0
- package/src/kernel/lock.ts +46 -0
- package/src/kernel/names.ts +14 -0
- package/src/kernel/schema.ts +130 -0
- package/src/kernel/where.ts +12 -0
- package/src/kit/index.ts +232 -0
- package/src/maker/system.ts +213 -0
- package/src/mcp/daemon.ts +61 -0
- package/src/mcp/main.ts +208 -0
- package/src/mcp/rpc.ts +64 -0
- package/src/mcp/tools.ts +125 -0
- package/src/prompt/evals.ts +42 -0
- package/src/prompt/index.ts +242 -0
- package/src/prompt/jsx-dev-runtime.ts +1 -0
- package/src/prompt/jsx-runtime.ts +49 -0
- package/src/prompt/mdx.d.ts +1 -0
- package/src/promptware/compile.ts +183 -0
- package/src/promptware/define.ts +16 -0
- package/src/promptware/disk.ts +72 -0
- package/src/promptware/markdown.d.ts +6 -0
- package/src/promptware/sync.ts +437 -0
- package/src/promptware/system.ts +215 -0
- package/src/runtime/bridge.ts +85 -0
- package/src/runtime/connect.ts +54 -0
- package/src/runtime/env.ts +35 -0
- package/src/runtime/harness.ts +80 -0
- package/src/runtime/main.ts +119 -0
- package/src/runtime/worker.ts +33 -0
- package/src/server/edge.ts +332 -0
- package/src/server/main.ts +45 -0
- package/src/server/messages.ts +97 -0
- package/src/server/protocol.ts +37 -0
- package/src/services/args.ts +45 -0
- package/src/services/exec.ts +69 -0
- package/src/services/fs.ts +139 -0
- package/src/services/http.ts +30 -0
- package/src/services/index.ts +113 -0
- package/src/services/secrets.ts +18 -0
- package/src/shell/address.ts +21 -0
- package/src/shell/args.ts +219 -0
- package/src/shell/client.ts +107 -0
- package/src/shell/codes.ts +26 -0
- package/src/shell/positional.ts +20 -0
- package/src/shell/run.ts +470 -0
- package/src/shell/service.ts +167 -0
- package/src/shell/state.ts +204 -0
- package/src/spec/adapters.ts +72 -0
- package/src/spec/diff.ts +26 -0
- package/src/spec/files.ts +17 -0
- package/src/spec/index.ts +155 -0
- package/src/spec/run.ts +97 -0
- package/src/spec/take.ts +54 -0
- package/src/test/index.ts +8 -0
- package/src/test/prove.ts +56 -0
- package/src/test/records.ts +23 -0
- package/src/test/specs.ts +56 -0
- package/src/test/steps.ts +100 -0
- package/src/test/voss-dir.ts +17 -0
- package/src/transport/messages.ts +110 -0
- package/src/transport/transport.ts +62 -0
- package/src/wall/probe.ts +67 -0
- package/src/wall/profile.ts +103 -0
- package/src/wall/spawn.ts +59 -0
- package/src/web/app.tsx +53 -0
- package/src/web/core.tsx +140 -0
- package/src/web/form.ts +155 -0
- package/src/web/hooks.ts +135 -0
- package/src/web/index.tsx +17 -0
- package/src/web/list.ts +19 -0
- package/src/web/maker.tsx +766 -0
- package/src/web/objects.tsx +213 -0
- package/src/web/socket.ts +84 -0
- package/src/web/state.tsx +69 -0
- package/src/web/store.ts +221 -0
- package/src/web/ui.tsx +135 -0
- package/README.md +0 -5
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// A system: one module of a baba, written as one expression. Its declaration
|
|
2
|
+
// is data, what the state holds of it (model), how it reaches outside
|
|
3
|
+
// (effects) and which systems it reads; its pieces are made from it, each
|
|
4
|
+
// whole where it is written: a step, an effect's binding, an action or a
|
|
5
|
+
// query with its contract beside its body. The system is one object: its
|
|
6
|
+
// handles by name, `pond.fish`, everywhere, and under SYSTEM what the runtime
|
|
7
|
+
// runs. Every piece sees the state as this system may touch it: a handle it
|
|
8
|
+
// did not declare, and does not read, does not type-check.
|
|
9
|
+
//
|
|
10
|
+
// export default system({ name: "pond", model, effects, reads: [tasks] }, (pond, p) => [
|
|
11
|
+
// p.step("swim", (w) => …),
|
|
12
|
+
// p.on(pond.fetch, { done: (w, e, r) => … }),
|
|
13
|
+
// p.action("feed", { summary, args, result, run: (w, a) => … }),
|
|
14
|
+
// p.query("pond", { result, read: (w) => … }),
|
|
15
|
+
// ]);
|
|
16
|
+
//
|
|
17
|
+
// A big system declares itself in a file of its own, `declare({...})`, and
|
|
18
|
+
// writes pieces beside their helpers in parts, `part(decl, (trunk, p) => [...])`.
|
|
19
|
+
// Pure: no IO, no Bun.
|
|
20
|
+
import type { Schema } from "../kernel/schema.ts";
|
|
21
|
+
import { declareModel, systemObject, dataOf, action as makeAction, query as makeQuery, SYSTEM, type AcceptDef, type ResourceDef, type EffectDef, type ActionDef, type QueryDef, type Refs, type Shape, type Of, type System, type SystemData, type Contract, type Binding, type Step } from "./baba.ts";
|
|
22
|
+
import type { Ask, Component, Comps, Ress, Effs, Effect, Entity, Value, Args, Result, EventOf, Row, Values, Not, Change, Select, Done, Failed, Settlement } from "./state.ts";
|
|
23
|
+
|
|
24
|
+
// ---- the declaration ----------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** A model entry: a component's shape, an ask, or a resource. */
|
|
27
|
+
export type Entry = Shape | AcceptDef | ResourceDef<any>;
|
|
28
|
+
|
|
29
|
+
type ComponentsOf<M> = { [K in keyof M as M[K] extends ResourceDef<any> ? never : K]: Exclude<M[K], ResourceDef<any>> };
|
|
30
|
+
type ResourcesOf<M> = { [K in keyof M as M[K] extends ResourceDef<any> ? K : never]: Extract<M[K], ResourceDef<any>> };
|
|
31
|
+
/** The handles a declaration makes, by name, each branded with the system's name. */
|
|
32
|
+
export type Handles<N extends string, M, E> = Refs<N, ComponentsOf<M>, ResourcesOf<M>, E>;
|
|
33
|
+
|
|
34
|
+
type ComponentNames<M> = { [K in keyof M & string]: M[K] extends ResourceDef<any> ? never : K }[keyof M & string];
|
|
35
|
+
/** Each effect, checked against the model: a mirror names one of its components, and what its list answers has that component's shape. */
|
|
36
|
+
type Checked<M, E> = {
|
|
37
|
+
[K in keyof E]: E[K] extends EffectDef<any, any, any, infer Mi, infer L>
|
|
38
|
+
? [Mi] extends [null] ? E[K]
|
|
39
|
+
: Mi extends ComponentNames<M> ? (unknown extends L ? E[K] : L extends Of<M[Mi & keyof M]> ? E[K] : { error: `source ${K & string}: what its list answers is not ${Mi}'s shape` })
|
|
40
|
+
: { error: `source ${K & string}: mirror ${Mi & string} is not a component of the model` }
|
|
41
|
+
: E[K];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** A system's declaration: its name, what the state holds of it, how it reaches outside, which systems it reads. */
|
|
45
|
+
export interface Decl<N extends string = string, M = any, E = any, R extends readonly System[] = readonly System[]> {
|
|
46
|
+
readonly name: N;
|
|
47
|
+
readonly model?: M;
|
|
48
|
+
readonly effects?: E;
|
|
49
|
+
/** The systems whose handles its pieces may read, and whose asks they may add. Reads go one way: two systems never read each other. */
|
|
50
|
+
readonly reads?: R;
|
|
51
|
+
}
|
|
52
|
+
const DECLARED: unique symbol = Symbol.for("babavoss.declared");
|
|
53
|
+
type Named<D> = D extends Decl<infer N> ? N : never;
|
|
54
|
+
type HandlesOfDecl<D> = D extends Decl<infer N, infer M, infer E> ? Handles<N, M, E> : never;
|
|
55
|
+
type ReadsOfDecl<D> = D extends Decl<any, any, any, infer R> ? R : readonly [];
|
|
56
|
+
|
|
57
|
+
// ---- the state as a system sees it ----------------------------------------
|
|
58
|
+
|
|
59
|
+
/** A system's handles, as types. */
|
|
60
|
+
export type HandlesOf<S> = S extends { readonly [SYSTEM]: { readonly $types?: { readonly handles: infer X } } } ? X : never;
|
|
61
|
+
type ReadsOf<S> = S extends { readonly [SYSTEM]: { readonly $types?: { readonly reads: infer R } } } ? R : never;
|
|
62
|
+
/** The handles of every system read, as one union: what a piece may read, and ask of. */
|
|
63
|
+
export type ReadUnion<R> = R extends readonly (infer S)[] ? HandlesOf<S> : never;
|
|
64
|
+
|
|
65
|
+
type See<X, R> = Comps<X> | Comps<R>;
|
|
66
|
+
type TermOf<C extends Component> = C | Not | Change<C>;
|
|
67
|
+
type Asks<R> = Extract<Comps<R>, Ask>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The state as a step, a binding or an action of a system sees it: it writes
|
|
71
|
+
* what it owns and asks of what it reads; it reads what it owns and what it
|
|
72
|
+
* reads; it runs its own effects. A handle it did not declare does not type-check.
|
|
73
|
+
*/
|
|
74
|
+
export interface Scope<X, R = never> {
|
|
75
|
+
/** The round's time, milliseconds since the epoch. */
|
|
76
|
+
readonly now: number;
|
|
77
|
+
/** Milliseconds since the previous round's time; 0 in the first. */
|
|
78
|
+
readonly elapsed: number;
|
|
79
|
+
readonly round: number;
|
|
80
|
+
random(): number;
|
|
81
|
+
transaction<T>(fn: () => T): T;
|
|
82
|
+
spawn(): Entity;
|
|
83
|
+
spawn<K extends Comps<X> | Asks<R>>(c: K, value: Value<K>): Entity;
|
|
84
|
+
despawn(e: Entity): void;
|
|
85
|
+
alive(e: Entity): boolean;
|
|
86
|
+
/** The system that made `e`, if one did. */
|
|
87
|
+
maker(e: Entity): string | undefined;
|
|
88
|
+
/** Every entity alive, in spawn order. */
|
|
89
|
+
all(): Entity[];
|
|
90
|
+
idsOf(c: See<X, R>): Entity[];
|
|
91
|
+
add<K extends (Comps<X> | Asks<R>) & Component<string, true>>(e: Entity, c: K): void;
|
|
92
|
+
add<K extends Comps<X> | Asks<R>>(e: Entity, c: K, value: Value<K>): void;
|
|
93
|
+
update<K extends Comps<X>>(e: Entity, c: K, fn: (value: Value<K>) => Value<K>): void;
|
|
94
|
+
update<K extends Ress<X>>(r: K, fn: (value: Value<K>) => Value<K>): void;
|
|
95
|
+
remove(e: Entity, ...cs: Comps<X>[]): void;
|
|
96
|
+
set<K extends Ress<X>>(r: K, value: Value<K>): void;
|
|
97
|
+
get<K extends See<X, R>>(e: Entity, c: K): Value<K> | undefined;
|
|
98
|
+
get<K extends Ress<X> | Ress<R>>(r: K): Value<K>;
|
|
99
|
+
has(e: Entity, c: See<X, R>): boolean;
|
|
100
|
+
count(c: See<X, R>): number;
|
|
101
|
+
find<K extends See<X, R>>(c: K, key: string | number): Entity | undefined;
|
|
102
|
+
query<const Cs extends readonly TermOf<See<X, R>>[]>(...terms: Cs): Row<Cs>[];
|
|
103
|
+
query<Cs extends readonly TermOf<See<X, R>>[]>(sel: Select<Cs>): Row<Cs>[];
|
|
104
|
+
each<const Cs extends readonly TermOf<See<X, R>>[]>(terms: Cs, fn: (e: Entity, ...values: Values<Cs>) => void): void;
|
|
105
|
+
each<Cs extends readonly TermOf<See<X, R>>[]>(sel: Select<Cs>, fn: (e: Entity, ...values: Values<Cs>) => void): void;
|
|
106
|
+
run<K extends Effs<X>>(effect: K, args?: Args<K>, entity?: Entity): void;
|
|
107
|
+
cancel(effect: Effs<X>, entity?: Entity): void;
|
|
108
|
+
running(effect: Effs<X>, entity?: Entity): boolean;
|
|
109
|
+
events<K extends Effs<X>>(effect: K): { entity: Entity | null; event: EventOf<K> }[];
|
|
110
|
+
done<K extends Effs<X>>(effect: K): Done<K>[];
|
|
111
|
+
failed<K extends Effs<X>>(effect: K): Failed<K>[];
|
|
112
|
+
settlement(of: Entity | Effs<X>): Settlement | undefined;
|
|
113
|
+
}
|
|
114
|
+
/** The state as a query sees it, between rounds: reads only. */
|
|
115
|
+
export type Reader<X, R = never> = Pick<Scope<X, R>, "now" | "round" | "get" | "has" | "count" | "find" | "query" | "each" | "running" | "settlement" | "alive" | "maker" | "all" | "idsOf">;
|
|
116
|
+
|
|
117
|
+
/** The state a system's pieces see, for a helper written outside them: `(w: StateOf<typeof pond>) => …`; a declaration's too. */
|
|
118
|
+
export type StateOf<S> = S extends Decl ? Scope<HandlesOfDecl<S>, ReadUnion<ReadsOfDecl<S>>> : Scope<HandlesOf<S>, ReadUnion<ReadsOf<S>>>;
|
|
119
|
+
/** The state a system's queries see, for a helper an action and a query share. */
|
|
120
|
+
export type ReaderOf<S> = S extends Decl ? Reader<HandlesOfDecl<S>, ReadUnion<ReadsOfDecl<S>>> : Reader<HandlesOf<S>, ReadUnion<ReadsOf<S>>>;
|
|
121
|
+
|
|
122
|
+
// ---- the pieces ---------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
const PIECE: unique symbol = Symbol.for("babavoss.piece");
|
|
125
|
+
interface Of_<O extends string> { readonly [PIECE]: true; readonly of: O }
|
|
126
|
+
/** A step: run every round, in the order the system lists its steps. */
|
|
127
|
+
export interface StepPiece<O extends string = string> extends Of_<O> { readonly kind: "step"; readonly name: string; readonly run: Step }
|
|
128
|
+
/** An effect's binding: how its answers land, and for which entities it runs. */
|
|
129
|
+
export interface OnPiece<O extends string = string> extends Of_<O> { readonly kind: "on"; readonly effect: Effect; readonly binding: Binding }
|
|
130
|
+
/** An action: its contract and its body. */
|
|
131
|
+
export interface ActionPiece<O extends string = string, K extends string = string, A = any, R = any> extends Of_<O> { readonly kind: "action"; readonly name: K; readonly def: ActionDef<A, R> }
|
|
132
|
+
/** A query: its contract and its body. */
|
|
133
|
+
export interface QueryPiece<O extends string = string, K extends string = string, A = any, R = any> extends Of_<O> { readonly kind: "query"; readonly name: K; readonly def: QueryDef<A, R> }
|
|
134
|
+
export type Piece<O extends string = string> = StepPiece<O> | OnPiece<O> | ActionPiece<O> | QueryPiece<O>;
|
|
135
|
+
/** What a system's list may hold: a piece, or nothing, for a piece made only when a condition holds: `flag && p.step(…)`. */
|
|
136
|
+
export type Maybe<O extends string = string> = Piece<O> | false | null | undefined;
|
|
137
|
+
|
|
138
|
+
/** How one effect's answers land, and for which entities it runs. */
|
|
139
|
+
export interface BindingOf<W, E> {
|
|
140
|
+
/** The entities to run it for, with the args of each; one job per entity, asked again only once answered. */
|
|
141
|
+
for?: (w: W) => Iterable<readonly [Entity, Args<E>]>;
|
|
142
|
+
done?: (w: W, entity: Entity | null, result: Result<E>) => void;
|
|
143
|
+
failed?: (w: W, entity: Entity | null, error: string) => void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The maker of a system's pieces, `p`: each piece is typed by the system and recorded by it. */
|
|
147
|
+
export interface Maker<O extends string, X, R> {
|
|
148
|
+
step(name: string, run: (w: Scope<X, R>) => void): StepPiece<O>;
|
|
149
|
+
on<E extends Effs<X>>(effect: E, binding: BindingOf<Scope<X, R>, E>): OnPiece<O>;
|
|
150
|
+
action<const K extends string, const S extends Shape = {}, Res = void>(name: K, def: { summary?: string; args?: S; positional?: (keyof Of<S> & string)[]; result?: Schema<Res>; run(w: Scope<X, R>, args: Of<S>): NoInfer<Res> }): ActionPiece<O, K, Of<S>, Res>;
|
|
151
|
+
query<const K extends string, const S extends Shape = {}, Res = unknown>(name: K, def: { summary?: string; args?: S; positional?: (keyof Of<S> & string)[]; result?: Schema<Res>; read(w: Reader<X, R>, args: Of<S>): NoInfer<Res> }): QueryPiece<O, K, Of<S>, Res>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type Flat<T> = { [K in keyof T]: T[K] } & {};
|
|
155
|
+
type Acts<P extends readonly unknown[]> = Flat<{ [I in keyof P & `${number}` as P[I] extends ActionPiece<any, infer K> ? K : never]: P[I] extends ActionPiece<any, any, infer A, infer R> ? { args: A; result: R } : never }>;
|
|
156
|
+
type Queries<P extends readonly unknown[]> = Flat<{ [I in keyof P & `${number}` as P[I] extends QueryPiece<any, infer K> ? K : never]: P[I] extends QueryPiece<any, any, infer A, infer R> ? { args: A; result: R } : never }>;
|
|
157
|
+
/** The system as its pieces see it while they are made: its handles, its contract not yet known. */
|
|
158
|
+
export type Building<N extends string, X, R> = System<N, X, R, unknown>;
|
|
159
|
+
|
|
160
|
+
/** Pieces of a system written in another file, typed by its declaration: `export const stages = part(decl, (trunk, p) => [ … ])`, listed as `...stages(trunk, p)`. */
|
|
161
|
+
export type Part<D extends Decl, P extends readonly Maybe<Named<D>>[] = readonly Maybe<Named<D>>[]> = (system: Building<Named<D>, HandlesOfDecl<D>, ReadsOfDecl<D>>, p: Maker<Named<D>, HandlesOfDecl<D>, ReadUnion<ReadsOfDecl<D>>>) => P;
|
|
162
|
+
|
|
163
|
+
// ---- making a system ----------------------------------------------------
|
|
164
|
+
|
|
165
|
+
const isResource = (v: unknown): v is ResourceDef<any> => typeof v === "object" && v !== null && (v as ResourceDef<any>).isResource === true;
|
|
166
|
+
const isPiece = (v: unknown): v is Piece => typeof v === "object" && v !== null && (v as Piece)[PIECE] === true;
|
|
167
|
+
const describe = (pc: Piece) => pc.kind === "on" ? `binding of ${pc.effect.name}` : `${pc.kind} ${pc.name}`;
|
|
168
|
+
|
|
169
|
+
/** A declaration's handles, made once: by declare() at import of its file, or by system() for an inline one. */
|
|
170
|
+
function built(decl: Decl) {
|
|
171
|
+
const known = (decl as { [DECLARED]?: ReturnType<typeof declareModel> })[DECLARED];
|
|
172
|
+
if (known) return known;
|
|
173
|
+
if (typeof decl !== "object" || decl === null) throw new Error("system: the first argument is the declaration, { name, model, effects, reads }");
|
|
174
|
+
const components: Record<string, Shape | AcceptDef> = {};
|
|
175
|
+
const resources: Record<string, ResourceDef<any>> = {};
|
|
176
|
+
for (const [k, v] of Object.entries((decl.model ?? {}) as Record<string, Entry>)) { if (isResource(v)) resources[k] = v; else components[k] = v as Shape | AcceptDef; }
|
|
177
|
+
return declareModel(decl.name, { components, resources, effects: (decl.effects ?? {}) as Record<string, EffectDef<any, any>> });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A declaration in a file of its own, checked now: names, key fields, mirrors and initial values, at import. Parts and the system both take it. */
|
|
181
|
+
export function declare<
|
|
182
|
+
const N extends string,
|
|
183
|
+
const M extends Record<string, Entry> = {},
|
|
184
|
+
const E extends Record<string, EffectDef<any, any, any, any, any>> = {},
|
|
185
|
+
const R extends readonly System[] = readonly [],
|
|
186
|
+
>(decl: { name: N; model?: M; effects?: E & Checked<M, E>; reads?: R }): Decl<N, M, E, R> {
|
|
187
|
+
const d = built(decl as Decl);
|
|
188
|
+
return Object.freeze({ ...decl, [DECLARED]: d }) as never;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Pieces of a system written beside their helpers in another file; the system lists them with `...part(system, p)`. */
|
|
192
|
+
export function part<const D extends Decl, const P extends readonly Maybe<Named<D>>[]>(decl: D, make: Part<D, P>): Part<D, P> {
|
|
193
|
+
return ((system, p) => {
|
|
194
|
+
if (dataOf(system).name !== decl.name) throw new Error(`a part of ${decl.name} was given ${dataOf(system).name}`);
|
|
195
|
+
return make(system, p);
|
|
196
|
+
}) as Part<D, P>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A system: the declaration, and the function that makes its pieces from it.
|
|
201
|
+
* `make` gets the system itself, its handles by name, and `p`, which makes
|
|
202
|
+
* pieces; it answers the list of them, steps in the order they run. A piece
|
|
203
|
+
* made and not listed, or listed twice, or another system's, is refused.
|
|
204
|
+
*/
|
|
205
|
+
export function system<
|
|
206
|
+
const N extends string,
|
|
207
|
+
const M extends Record<string, Entry> = {},
|
|
208
|
+
const E extends Record<string, EffectDef<any, any, any, any, any>> = {},
|
|
209
|
+
const R extends readonly System[] = readonly [],
|
|
210
|
+
const P extends readonly Maybe<N>[] = readonly [],
|
|
211
|
+
>(decl: { name: N; model?: M; effects?: E & Checked<M, E>; reads?: R } | Decl<N, M, E, R>, make?: (system: Building<N, Handles<N, M, E>, R>, p: Maker<N, Handles<N, M, E>, ReadUnion<R>>) => P): System<N, Handles<N, M, E>, R, Contract<Acts<P>, Queries<P>>> {
|
|
212
|
+
const d = built(decl as Decl);
|
|
213
|
+
const name = d.name;
|
|
214
|
+
const reads = [...((decl as Decl).reads ?? [])];
|
|
215
|
+
reads.forEach((r, i) => { try { dataOf(r); } catch { throw new Error(`${name} reads ${i === 0 ? "a" : `its ${i + 1}th`} system that is not one yet: ${r === undefined ? "undefined, a module read before it finished: two systems read each other, or an import cycle" : "not a system"}`); } });
|
|
216
|
+
const { system: sys, complete } = systemObject(d, reads);
|
|
217
|
+
const made = new Set<Piece>();
|
|
218
|
+
const piece = <T extends Piece>(pc: Omit<T, typeof PIECE | "of">): T => { const done = Object.freeze({ ...pc, [PIECE]: true, of: name }) as unknown as T; made.add(done); return done; };
|
|
219
|
+
const p: Maker<N, any, any> = {
|
|
220
|
+
step: (n, run) => piece<StepPiece<N>>({ kind: "step", name: n, run: run as Step }),
|
|
221
|
+
on: (effect, binding) => {
|
|
222
|
+
if ((effect as Effect | undefined)?.owner !== name) throw new Error(`${name} binds ${(effect as Effect | undefined)?.name ?? String(effect)}, which is not one of its effects`);
|
|
223
|
+
return piece<OnPiece<N>>({ kind: "on", effect: effect as Effect, binding: binding as Binding });
|
|
224
|
+
},
|
|
225
|
+
action: (n, def) => piece<ActionPiece<N>>({ kind: "action", name: n, def: makeAction(def as never) }) as never,
|
|
226
|
+
query: (n, def) => piece<QueryPiece<N>>({ kind: "query", name: n, def: makeQuery(def as never) }) as never,
|
|
227
|
+
};
|
|
228
|
+
const listed = (make ? make(sys as never, p) : []) as readonly unknown[];
|
|
229
|
+
if (!Array.isArray(listed)) throw new Error(`${name}: the function that makes its pieces answers a list of them: (${name}, p) => [ … ]`);
|
|
230
|
+
const steps: { name: string; run: Step }[] = [];
|
|
231
|
+
const on = new Map<Effect, Binding>();
|
|
232
|
+
const actions: Record<string, ActionDef> = {};
|
|
233
|
+
const queries: Record<string, QueryDef> = {};
|
|
234
|
+
const seen = new Set<Piece>();
|
|
235
|
+
for (const v of listed) {
|
|
236
|
+
if (v === false || v === null || v === undefined) continue;
|
|
237
|
+
if (!isPiece(v)) throw new Error(`${name} lists something that is not a piece; pieces are made with p: p.step, p.on, p.action, p.query`);
|
|
238
|
+
if (v.of !== name) throw new Error(`${name} lists ${describe(v)}, which is ${v.of}'s`);
|
|
239
|
+
if (seen.has(v)) throw new Error(`${name} lists its ${describe(v)} twice`);
|
|
240
|
+
seen.add(v);
|
|
241
|
+
if (v.kind === "step") { if (steps.some((x) => x.name === v.name)) throw new Error(`${name} has two steps named ${v.name}`); steps.push({ name: v.name, run: v.run }); }
|
|
242
|
+
else if (v.kind === "on") { if (on.has(v.effect)) throw new Error(`${name} binds ${v.effect.name} twice`); on.set(v.effect, v.binding); }
|
|
243
|
+
else if (v.kind === "action") { if (v.name in actions) throw new Error(`${name} has two actions named ${v.name}`); actions[v.name] = v.def; }
|
|
244
|
+
else { if (v.name in queries) throw new Error(`${name} has two queries named ${v.name}`); queries[v.name] = v.def; }
|
|
245
|
+
}
|
|
246
|
+
const forgotten = [...made].filter((pc) => !seen.has(pc));
|
|
247
|
+
if (forgotten.length) throw new Error(`${name} made ${forgotten.map(describe).join(", ")} and did not list ${forgotten.length === 1 ? "it" : "them"}: every piece p makes goes in the list it answers`);
|
|
248
|
+
return complete({ steps, on, actions, queries }) as never;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A system's data, for the framework: its name, model, reads, steps, bindings and contract. */
|
|
252
|
+
export { dataOf, type SystemData };
|
|
253
|
+
/** The actions of a system, as types. */
|
|
254
|
+
export type Act<S> = S extends { readonly [SYSTEM]: { readonly $types?: { readonly contract: Contract<infer A, any> } } } ? A : never;
|
|
255
|
+
/** The queries of a system, as types. */
|
|
256
|
+
export type Read<S> = S extends { readonly [SYSTEM]: { readonly $types?: { readonly contract: Contract<any, infer Q> } } } ? Q : never;
|