@rindle/client 0.3.1 → 0.4.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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mutation-ops.d.ts +140 -0
- package/dist/mutation-ops.d.ts.map +1 -0
- package/dist/mutation-ops.js +60 -0
- package/dist/mutation-ops.js.map +1 -0
- package/dist/schema.d.ts +53 -5
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +65 -4
- package/dist/schema.js.map +1 -1
- package/dist/ssr.d.ts +14 -0
- package/dist/ssr.d.ts.map +1 -1
- package/dist/ssr.js +15 -0
- package/dist/ssr.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +24 -1
- package/src/mutation-ops.ts +190 -0
- package/src/schema.ts +130 -5
- package/src/ssr.ts +21 -0
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export * from "./store.ts";
|
|
|
6
6
|
export * from "./ssr.ts";
|
|
7
7
|
export { resolveChange, subRow } from "./resolve.ts";
|
|
8
8
|
export type { NamedRow, ResolvedChange } from "./resolve.ts";
|
|
9
|
+
export type { KeyedRow, MutationOp, ServerWriteTx } from "./mutation-ops.ts";
|
|
10
|
+
export { driveMutationAsync, driveMutationSync, isGeneratorMutator, isoTx, shared, } from "./mutation-ops.ts";
|
|
11
|
+
export type { ArgSchema, AsyncEffectExec, BatchEffect, IsoTx, MutationGen, MutatorCtx, ReadEffect, SharedMutator, SharedMutatorWithArgs, SyncEffectExec, YieldEffect, } from "./mutation-ops.ts";
|
|
9
12
|
export { stableKey } from "./key.ts";
|
|
10
13
|
export { COMPARATOR_VERSION, compareNumber, compareRows, compareString, compareValue } from "./compare.ts";
|
|
11
14
|
export type { Backend, BackendDevObserver, BackendServerDelta, ChangeEvent, ColType, FlatChange, FlatOp, Mutation, MutationEnvelope, NormalizedEvent, NormalizedOp, NormalizedSource, NormalizedTableSchema, OptimisticSource, PathSeg, ProgressFrame, QueryId, RemoteQuery, ResultType, WireNode, WireProjection, WireRel, WireSchema, WireValue, } from "./types.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE7D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE3G,YAAY,EACV,OAAO,EACP,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,OAAO,EACP,UAAU,EACV,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,OAAO,EACP,aAAa,EACb,OAAO,EACP,WAAW,EACX,UAAU,EACV,QAAQ,EACR,cAAc,EACd,OAAO,EACP,UAAU,EACV,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG9F,YAAY,EACV,SAAS,EACT,GAAG,EACH,KAAK,EACL,SAAS,EACT,kBAAkB,EAClB,WAAW,EACX,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAa,GACd,MAAM,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE7D,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,EACL,MAAM,GACP,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,KAAK,EACL,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,WAAW,GACZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE3G,YAAY,EACV,OAAO,EACP,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,OAAO,EACP,UAAU,EACV,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,OAAO,EACP,aAAa,EACb,OAAO,EACP,WAAW,EACX,UAAU,EACV,QAAQ,EACR,cAAc,EACd,OAAO,EACP,UAAU,EACV,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG9F,YAAY,EACV,SAAS,EACT,GAAG,EACH,KAAK,EACL,SAAS,EACT,kBAAkB,EAClB,WAAW,EACX,GAAG,EACH,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAa,GACd,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// @rindle/client — the backend-agnostic flat-change client core: typed schema, query builder,
|
|
2
2
|
// comparator, Backend seam, and the ArrayView contract. (The ArrayView folding impl + the
|
|
3
3
|
// Store/backend wiring land in later slices.)
|
|
4
|
-
export * from "./schema.js"; // table/columns/primaryKey, createSchema/extendSchema, string/number/boolean/json, Col, RowOf, Row, TableDef, tableSpec, SCHEMA
|
|
4
|
+
export * from "./schema.js"; // table/columns/primaryKey, createSchema/extendSchema, refineTable/refineSchema, string/number/boolean/json, Col, RowOf, Row, TableDef, tableSpec, SCHEMA
|
|
5
5
|
export * from "./operators.js"; // eq/ne/gt/ge/lt/le/like/notLike/ilike/notIlike/inList/notInList/is/isNot, and/or, Pred, Cond, Arg
|
|
6
6
|
export * from "./query.js"; // Query, QueryRoot, queries/newQueryBuilder, defineQuery/NamedQuery, exists, notExists, defineFragment/Fragment/FragmentRef/isFragment
|
|
7
7
|
export * from "./view.js"; // ArrayView, SingularArrayView, FlatArrayView, SingularView, ViewTypes
|
|
8
8
|
export * from "./store.js"; // Store, WriteTx, AssembledNode, DehydratedQuery, DehydratedState
|
|
9
9
|
export * from "./ssr.js"; // createServerStore, ServerStore, OneShotBackend, OneShotQueryFn, OneShotResult, ServerStoreOptions
|
|
10
10
|
export { resolveChange, subRow } from "./resolve.js"; // positional FlatChange → named rows (inverse of the wire encoder)
|
|
11
|
+
// shared (generator) mutator seam — one body, sync on the client + async on the server
|
|
12
|
+
export { driveMutationAsync, driveMutationSync, isGeneratorMutator, isoTx, shared, } from "./mutation-ops.js";
|
|
11
13
|
export { stableKey } from "./key.js"; // canonical viewKey for an AST (shared with @rindle/react)
|
|
12
14
|
export { COMPARATOR_VERSION, compareNumber, compareRows, compareString, compareValue } from "./compare.js";
|
|
13
15
|
export { CLIENT_MUTATIONS_SCHEMA, CLIENT_MUTATIONS_TABLE, LMID_QUERY_NAME } from "./types.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,0FAA0F;AAC1F,8CAA8C;AAE9C,cAAc,aAAa,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,0FAA0F;AAC1F,8CAA8C;AAE9C,cAAc,aAAa,CAAC,CAAC,0JAA0J;AACvL,cAAc,gBAAgB,CAAC,CAAC,mGAAmG;AACnI,cAAc,YAAY,CAAC,CAAC,uIAAuI;AACnK,cAAc,WAAW,CAAC,CAAC,uEAAuE;AAClG,cAAc,YAAY,CAAC,CAAC,kEAAkE;AAC9F,cAAc,UAAU,CAAC,CAAC,oGAAoG;AAE9H,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC,CAAC,mEAAmE;AAIzH,uFAAuF;AACvF,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,EACL,MAAM,GACP,MAAM,mBAAmB,CAAC;AAe3B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC,CAAC,2DAA2D;AAEjG,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6B3G,OAAO,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { WireValue } from "./types.ts";
|
|
2
|
+
/** A keyed row: column name → cell. The ergonomic write shape (validated against the schema at
|
|
3
|
+
* runtime). JSON columns carry their raw JSON string (a {@link WireValue}), never a parsed object. */
|
|
4
|
+
export type KeyedRow = Record<string, WireValue>;
|
|
5
|
+
/** One structured write intent — the discriminated union that mirrors the write half of the client
|
|
6
|
+
* `MutationTx` 1:1. Keyed (column-name addressed), so it is independent of column order. */
|
|
7
|
+
export type MutationOp = {
|
|
8
|
+
kind: "insert";
|
|
9
|
+
table: string;
|
|
10
|
+
row: KeyedRow;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "upsert";
|
|
13
|
+
table: string;
|
|
14
|
+
row: KeyedRow;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "insertIgnore";
|
|
17
|
+
table: string;
|
|
18
|
+
row: KeyedRow;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "update";
|
|
21
|
+
table: string;
|
|
22
|
+
row: KeyedRow;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "delete";
|
|
25
|
+
table: string;
|
|
26
|
+
pk: KeyedRow;
|
|
27
|
+
};
|
|
28
|
+
/** The ASYNC server-side write surface a mutator runs against. Semantically the twin of the client's
|
|
29
|
+
* synchronous `MutationTx` write methods — same names, same keyed-row arguments — but every op is a
|
|
30
|
+
* Promise so a Postgres backend can execute it live against an open transaction (read-your-writes).
|
|
31
|
+
* A rindle/daemon backend resolves writes immediately (accumulate) and serves `row` from committed
|
|
32
|
+
* state. `insert` requires every column; `update`/`delete` require the pk columns. */
|
|
33
|
+
export interface ServerWriteTx {
|
|
34
|
+
/** Insert a FULL row (every column named; missing or unknown columns throw). */
|
|
35
|
+
insert(table: string, row: KeyedRow): Promise<void>;
|
|
36
|
+
/** Update the row identified by the pk columns; only the named non-pk columns change. A missing
|
|
37
|
+
* row is a NO-OP. */
|
|
38
|
+
update(table: string, row: KeyedRow): Promise<void>;
|
|
39
|
+
/** Insert, or fully replace when the pk already exists (a FULL row, like `insert`). */
|
|
40
|
+
upsert(table: string, row: KeyedRow): Promise<void>;
|
|
41
|
+
/** Insert a FULL row, or do nothing if the pk already exists (the SQL twin of the client
|
|
42
|
+
* `if (!tx.row(pk)) tx.insert(row)` pattern). */
|
|
43
|
+
insertIgnore(table: string, row: KeyedRow): Promise<void>;
|
|
44
|
+
/** Delete the row identified by the pk columns. A missing row is a NO-OP. */
|
|
45
|
+
delete(table: string, pk: KeyedRow): Promise<void>;
|
|
46
|
+
/** Read one row by primary key, through the OPEN transaction on both backends
|
|
47
|
+
* (read-your-writes: live on Postgres; via an interactive mutation session on the daemon,
|
|
48
|
+
* DAEMON-INTERACTIVE-TXN-DESIGN.md §5.3). */
|
|
49
|
+
row(table: string, pk: KeyedRow): Promise<KeyedRow | undefined>;
|
|
50
|
+
}
|
|
51
|
+
/** A point read a generator mutator yields; the driver resolves it and feeds the row back through
|
|
52
|
+
* `gen.next(row)`. Read-your-writes on every tier: the client reads its local engine, and both
|
|
53
|
+
* server backends read the open transaction (the daemon via an interactive mutation session). */
|
|
54
|
+
export type ReadEffect = {
|
|
55
|
+
kind: "row";
|
|
56
|
+
table: string;
|
|
57
|
+
pk: KeyedRow;
|
|
58
|
+
};
|
|
59
|
+
/** A fan-out a generator mutator yields: run several effects "together". The server driver resolves
|
|
60
|
+
* them with `Promise.all`; the client driver runs them in array order (already synchronous). Results
|
|
61
|
+
* return in the SAME order on both tiers, so the mutator stays deterministic. The `yield` evaluates
|
|
62
|
+
* to `(KeyedRow | undefined)[]` at runtime (cast it — the generator's single next-type is a row). */
|
|
63
|
+
export type BatchEffect = {
|
|
64
|
+
kind: "all";
|
|
65
|
+
effects: readonly YieldEffect[];
|
|
66
|
+
};
|
|
67
|
+
/** Everything a generator mutator may `yield`: a write {@link MutationOp}, a {@link ReadEffect}, or a
|
|
68
|
+
* {@link BatchEffect}. */
|
|
69
|
+
export type YieldEffect = MutationOp | ReadEffect | BatchEffect;
|
|
70
|
+
/** The tier-AGNOSTIC effect factory a generator mutator writes against. Every method just BUILDS an
|
|
71
|
+
* effect to `yield` — it performs no I/O and holds no state, so the single {@link isoTx} instance is
|
|
72
|
+
* shared by every mutator on both tiers; only the driver differs. `insert`/`upsert`/`insertIgnore`
|
|
73
|
+
* take a FULL row; `update`/`delete` take the pk columns (`update` adds the columns to change). */
|
|
74
|
+
export interface IsoTx {
|
|
75
|
+
insert(table: string, row: KeyedRow): MutationOp;
|
|
76
|
+
update(table: string, row: KeyedRow): MutationOp;
|
|
77
|
+
upsert(table: string, row: KeyedRow): MutationOp;
|
|
78
|
+
insertIgnore(table: string, row: KeyedRow): MutationOp;
|
|
79
|
+
delete(table: string, pk: KeyedRow): MutationOp;
|
|
80
|
+
row(table: string, pk: KeyedRow): ReadEffect;
|
|
81
|
+
all(effects: readonly YieldEffect[]): BatchEffect;
|
|
82
|
+
}
|
|
83
|
+
/** The one shared effect factory (stateless — see {@link IsoTx}). */
|
|
84
|
+
export declare const isoTx: IsoTx;
|
|
85
|
+
/** The minimal per-invocation context a shared mutator sees on BOTH tiers: the acting principal. The
|
|
86
|
+
* server injects its AUTHENTICATED user; the client injects its local user. (The server's own
|
|
87
|
+
* `MutationContext` is a superset of this.) */
|
|
88
|
+
export interface MutatorCtx {
|
|
89
|
+
user: string;
|
|
90
|
+
}
|
|
91
|
+
/** What a generator mutator IS: `yield tx.<op>()` on every side effect; a `yield tx.row()` expression
|
|
92
|
+
* evaluates to the row. Neither sync nor async — the tier's driver decides, which is what lets one
|
|
93
|
+
* body run synchronously on the client and against a live async transaction on the server. */
|
|
94
|
+
export type MutationGen = Generator<YieldEffect, void, KeyedRow | undefined>;
|
|
95
|
+
/** A generator (isomorphic) mutator, shared verbatim by both tiers: the client trusts typed `args`,
|
|
96
|
+
* the server parses untrusted args into `Args` before invoking. */
|
|
97
|
+
export type SharedMutator<Args, Ctx extends MutatorCtx = MutatorCtx> = (tx: IsoTx, args: Args, ctx: Ctx) => MutationGen;
|
|
98
|
+
/** The minimal arg validator a shared mutator can carry so the SERVER can parse UNTRUSTED wire args
|
|
99
|
+
* before driving it (the client trusts its typed callsites and never calls this). Structural on
|
|
100
|
+
* purpose — a zod schema satisfies it as-is — so `@rindle/client` stays validator-library-agnostic. */
|
|
101
|
+
export interface ArgSchema<Args> {
|
|
102
|
+
parse(raw: unknown): Args;
|
|
103
|
+
}
|
|
104
|
+
/** A shared mutator that CARRIES its own arg validator, co-located at the def site (`shared(schema,
|
|
105
|
+
* gen)`). The client registers it exactly like a bare generator mutator — the `.args` validator is
|
|
106
|
+
* inert there (typed callsites skip the parse); the server ({@link runSharedMutation}, via the
|
|
107
|
+
* api-server's `sharedApiMutators`) reads `.args` to parse untrusted wire args before driving the
|
|
108
|
+
* SAME body. */
|
|
109
|
+
export type SharedMutatorWithArgs<Args, Ctx extends MutatorCtx = MutatorCtx> = SharedMutator<Args, Ctx> & {
|
|
110
|
+
args: ArgSchema<Args>;
|
|
111
|
+
};
|
|
112
|
+
/** Co-locate a shared (generator) mutator with the validator for its args — pairing the arg SHAPE
|
|
113
|
+
* with the body that consumes it at ONE site, so neither tier restates it (the client derives its
|
|
114
|
+
* callsite type from `Args`; the server parses untrusted args through `.args`). Returns the SAME
|
|
115
|
+
* generator function with an `args` property attached (`Object.assign` mutates + returns it), so the
|
|
116
|
+
* registered value is byte-for-byte what the client drove before: {@link isGeneratorMutator} still
|
|
117
|
+
* detects it and it still `satisfies ClientRegistry`. */
|
|
118
|
+
export declare function shared<Args, Ctx extends MutatorCtx = MutatorCtx>(args: ArgSchema<Args>, run: SharedMutator<Args, Ctx>): SharedMutatorWithArgs<Args, Ctx>;
|
|
119
|
+
/** True iff `fn` is a generator function (a shared/isomorphic mutator) rather than a plain function —
|
|
120
|
+
* the driver accepts both forms. Detected structurally (native `GeneratorFunction`). */
|
|
121
|
+
export declare function isGeneratorMutator(fn: unknown): fn is (...args: never[]) => MutationGen;
|
|
122
|
+
/** A tier's SYNCHRONOUS effect executor (the browser wasm engine): apply a write, resolve a read —
|
|
123
|
+
* both immediate. */
|
|
124
|
+
export interface SyncEffectExec {
|
|
125
|
+
apply(op: MutationOp): void;
|
|
126
|
+
read(table: string, pk: KeyedRow): KeyedRow | undefined;
|
|
127
|
+
}
|
|
128
|
+
/** Drive a generator mutator SYNCHRONOUSLY (the client, inside the wasm write transaction). Each
|
|
129
|
+
* yielded write applies immediately; each read is resolved and fed back; a batch runs in order. */
|
|
130
|
+
export declare function driveMutationSync(gen: MutationGen, exec: SyncEffectExec): void;
|
|
131
|
+
/** A tier's ASYNCHRONOUS effect executor (the server transaction): every op is a Promise. */
|
|
132
|
+
export interface AsyncEffectExec {
|
|
133
|
+
apply(op: MutationOp): Promise<void>;
|
|
134
|
+
read(table: string, pk: KeyedRow): Promise<KeyedRow | undefined>;
|
|
135
|
+
}
|
|
136
|
+
/** Drive a generator mutator ASYNCHRONOUSLY (the server, against the open transaction). Writes are
|
|
137
|
+
* awaited (harmless: a single interactive Postgres connection serializes statements anyway, and the
|
|
138
|
+
* daemon backend resolves them instantly), reads suspend, and a batch fans out with `Promise.all`. */
|
|
139
|
+
export declare function driveMutationAsync(gen: MutationGen, exec: AsyncEffectExec): Promise<void>;
|
|
140
|
+
//# sourceMappingURL=mutation-ops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-ops.d.ts","sourceRoot":"","sources":["../src/mutation-ops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Isomorphic mutation ops — the backend-agnostic "rindle mutation" vocabulary
|
|
2
|
+
// (MUTATORS-ISOMORPHIC plan). A mutator names WHAT it writes (insert/update/upsert/
|
|
3
|
+
// insertIgnore/delete over a keyed row), not HOW — the server renders dialect SQL and the
|
|
4
|
+
// client applies to its local engine. This lives in `@rindle/client` (the leaf both tiers
|
|
5
|
+
// import); the SQL renderer that consumes it is server-only (`@rindle/api-server`).
|
|
6
|
+
/** The one shared effect factory (stateless — see {@link IsoTx}). */
|
|
7
|
+
export const isoTx = {
|
|
8
|
+
insert: (table, row) => ({ kind: "insert", table, row }),
|
|
9
|
+
update: (table, row) => ({ kind: "update", table, row }),
|
|
10
|
+
upsert: (table, row) => ({ kind: "upsert", table, row }),
|
|
11
|
+
insertIgnore: (table, row) => ({ kind: "insertIgnore", table, row }),
|
|
12
|
+
delete: (table, pk) => ({ kind: "delete", table, pk }),
|
|
13
|
+
row: (table, pk) => ({ kind: "row", table, pk }),
|
|
14
|
+
all: (effects) => ({ kind: "all", effects }),
|
|
15
|
+
};
|
|
16
|
+
/** Co-locate a shared (generator) mutator with the validator for its args — pairing the arg SHAPE
|
|
17
|
+
* with the body that consumes it at ONE site, so neither tier restates it (the client derives its
|
|
18
|
+
* callsite type from `Args`; the server parses untrusted args through `.args`). Returns the SAME
|
|
19
|
+
* generator function with an `args` property attached (`Object.assign` mutates + returns it), so the
|
|
20
|
+
* registered value is byte-for-byte what the client drove before: {@link isGeneratorMutator} still
|
|
21
|
+
* detects it and it still `satisfies ClientRegistry`. */
|
|
22
|
+
export function shared(args, run) {
|
|
23
|
+
return Object.assign(run, { args });
|
|
24
|
+
}
|
|
25
|
+
/** True iff `fn` is a generator function (a shared/isomorphic mutator) rather than a plain function —
|
|
26
|
+
* the driver accepts both forms. Detected structurally (native `GeneratorFunction`). */
|
|
27
|
+
export function isGeneratorMutator(fn) {
|
|
28
|
+
return (typeof fn === "function" &&
|
|
29
|
+
fn.constructor?.name === "GeneratorFunction");
|
|
30
|
+
}
|
|
31
|
+
/** Drive a generator mutator SYNCHRONOUSLY (the client, inside the wasm write transaction). Each
|
|
32
|
+
* yielded write applies immediately; each read is resolved and fed back; a batch runs in order. */
|
|
33
|
+
export function driveMutationSync(gen, exec) {
|
|
34
|
+
const run = (eff) => {
|
|
35
|
+
if (eff.kind === "row")
|
|
36
|
+
return exec.read(eff.table, eff.pk);
|
|
37
|
+
if (eff.kind === "all")
|
|
38
|
+
return eff.effects.map(run);
|
|
39
|
+
exec.apply(eff);
|
|
40
|
+
return undefined;
|
|
41
|
+
};
|
|
42
|
+
for (let step = gen.next(); !step.done; step = gen.next(run(step.value)))
|
|
43
|
+
;
|
|
44
|
+
}
|
|
45
|
+
/** Drive a generator mutator ASYNCHRONOUSLY (the server, against the open transaction). Writes are
|
|
46
|
+
* awaited (harmless: a single interactive Postgres connection serializes statements anyway, and the
|
|
47
|
+
* daemon backend resolves them instantly), reads suspend, and a batch fans out with `Promise.all`. */
|
|
48
|
+
export async function driveMutationAsync(gen, exec) {
|
|
49
|
+
const run = async (eff) => {
|
|
50
|
+
if (eff.kind === "row")
|
|
51
|
+
return exec.read(eff.table, eff.pk);
|
|
52
|
+
if (eff.kind === "all")
|
|
53
|
+
return (await Promise.all(eff.effects.map(run)));
|
|
54
|
+
await exec.apply(eff);
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
for (let step = gen.next(); !step.done; step = gen.next(await run(step.value)))
|
|
58
|
+
;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=mutation-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation-ops.js","sourceRoot":"","sources":["../src/mutation-ops.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,oFAAoF;AACpF,0FAA0F;AAC1F,0FAA0F;AAC1F,oFAAoF;AAgFpF,qEAAqE;AACrE,MAAM,CAAC,MAAM,KAAK,GAAU;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACxD,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACxD,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACxD,YAAY,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACpE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACtD,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAChD,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;CAC7C,CAAC;AAsCF;;;;;0DAK0D;AAC1D,MAAM,UAAU,MAAM,CACpB,IAAqB,EACrB,GAA6B;IAE7B,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;yFACyF;AACzF,MAAM,UAAU,kBAAkB,CAAC,EAAW;IAC5C,OAAO,CACL,OAAO,EAAE,KAAK,UAAU;QACvB,EAA0C,CAAC,WAAW,EAAE,IAAI,KAAK,mBAAmB,CACtF,CAAC;AACJ,CAAC;AASD;oGACoG;AACpG,MAAM,UAAU,iBAAiB,CAAC,GAAgB,EAAE,IAAoB;IACtE,MAAM,GAAG,GAAG,CAAC,GAAgB,EAAwB,EAAE;QACrD,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5D,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAoC,CAAC;QACvF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IACF,KAAK,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;AAC5E,CAAC;AAQD;;uGAEuG;AACvG,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAgB,EAAE,IAAqB;IAC9E,MAAM,GAAG,GAAG,KAAK,EAAE,GAAgB,EAAiC,EAAE;QACpE,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5D,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAoC,CAAC;QAC5G,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IACF,KAAK,IAAI,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;AAClF,CAAC"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Arg, Cond } from "./operators.ts";
|
|
2
2
|
import type { ColType } from "./types.ts";
|
|
3
|
-
/** A column descriptor. `type` drives the comparator + JSON parsing; `__t` is a phantom.
|
|
3
|
+
/** A column descriptor. `type` drives the comparator + JSON parsing; `__t` is a phantom. A
|
|
4
|
+
* nullable column is a `Col<T | null>` — that is the ONLY thing nullability changes at the type
|
|
5
|
+
* level, so `RowOf` (and the field-condition factory) widen automatically. */
|
|
4
6
|
export interface Col<T> {
|
|
5
7
|
readonly type: ColType;
|
|
6
8
|
readonly __t?: T;
|
|
@@ -10,10 +12,19 @@ export type AnyCols = Record<string, Col<unknown>>;
|
|
|
10
12
|
export type RowOf<C extends AnyCols> = {
|
|
11
13
|
[K in keyof C]: ColT<C[K]>;
|
|
12
14
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
/** The chainable form returned by the column factories: a {@link Col} plus `.nullable()`.
|
|
16
|
+
*
|
|
17
|
+
* `.nullable()` widens the column's value type to `T | null` — its `Row<…>` field becomes
|
|
18
|
+
* `T | null`, and (design 206 Phase 2) it may be omitted from an insert. `rindle schema gen` emits
|
|
19
|
+
* `.nullable()` for every nullable (non-`NOT NULL`) SQL column; you can also call it by hand on a
|
|
20
|
+
* local-only table's columns. It is idempotent and stays chainable. */
|
|
21
|
+
export interface ColBuilder<T> extends Col<T> {
|
|
22
|
+
nullable(): ColBuilder<T | null>;
|
|
23
|
+
}
|
|
24
|
+
export declare const string: <T extends string = string>() => ColBuilder<T>;
|
|
25
|
+
export declare const number: <T extends number = number>() => ColBuilder<T>;
|
|
26
|
+
export declare const boolean: () => ColBuilder<boolean>;
|
|
27
|
+
export declare const json: <T = unknown>() => ColBuilder<T>;
|
|
17
28
|
/** Metadata key on a {@link TableDef} (a `unique symbol`, so no column name collides). */
|
|
18
29
|
export declare const SCHEMA: unique symbol;
|
|
19
30
|
export interface TableMeta<N extends string = string, C extends AnyCols = AnyCols> {
|
|
@@ -92,6 +103,43 @@ export declare function createSchema<const T extends readonly AnyTable[]>(opts:
|
|
|
92
103
|
export declare function extendSchema<S extends ColsMap, const T extends readonly AnyTable[]>(base: Schema<S>, opts: {
|
|
93
104
|
tables: T;
|
|
94
105
|
}): Schema<S & SchemaOf<T>>;
|
|
106
|
+
/** Per-column narrowings for {@link refineTable}: each entry must keep the column's kind and narrow
|
|
107
|
+
* its TS type (`Col<T2>` with `T2 extends T`) — `json<Meta>()` on a json column, a literal-union
|
|
108
|
+
* `string<"a" | "b">()` on a string column. Kind changes are rejected (cross-kind at compile time,
|
|
109
|
+
* same-phantom kind flips like `string()` on a json column at runtime). */
|
|
110
|
+
export type ColRefinements<C extends AnyCols> = {
|
|
111
|
+
readonly [K in keyof C]?: Col<ColT<C[K]>>;
|
|
112
|
+
};
|
|
113
|
+
/** `C` with the columns named in `R` re-typed to their refined `Col`s. */
|
|
114
|
+
export type RefinedCols<C extends AnyCols, R extends ColRefinements<C>> = {
|
|
115
|
+
[K in keyof C]: K extends keyof R ? (R[K] extends Col<unknown> ? R[K] : C[K]) : C[K];
|
|
116
|
+
};
|
|
117
|
+
/** Narrow a generated table's column TYPES without touching its runtime shape.
|
|
118
|
+
*
|
|
119
|
+
* Returns the SAME def, re-typed (identity, after validating that every refined column exists and
|
|
120
|
+
* keeps its kind) — so conditions built from it, `rel(...)`s anchored on it, and `Row<typeof t>`
|
|
121
|
+
* all see the narrowed types. Pass the result to {@link refineSchema} so query roots narrow too. */
|
|
122
|
+
export declare function refineTable<N extends string, C extends AnyCols, R extends ColRefinements<C>>(base: TableDef<N, C>, cols: R): TableDef<N, RefinedCols<C, R>>;
|
|
123
|
+
/** A table def acceptable to {@link refineSchema} over `Schema<S>`: its NAME must be one of `S`'s
|
|
124
|
+
* tables (a def for an unknown table is a compile error at the call site). */
|
|
125
|
+
export type RefinableTable<S extends ColsMap> = {
|
|
126
|
+
readonly [SCHEMA]: TableMeta<Extract<keyof S, string>, AnyCols>;
|
|
127
|
+
};
|
|
128
|
+
/** `S` with each table named in `T` re-typed to that def's (refined) columns. (The inner
|
|
129
|
+
* `extends AnyCols` guard is how the checker proves the remapped `SchemaOf<T>[K]` is a column
|
|
130
|
+
* map while `T` is still generic; it always holds for a concrete `T`.) */
|
|
131
|
+
export type RefinedColsMap<S extends ColsMap, T extends readonly AnyTable[]> = {
|
|
132
|
+
[K in keyof S]: K extends keyof SchemaOf<T> ? (SchemaOf<T>[K] extends AnyCols ? SchemaOf<T>[K] : S[K]) : S[K];
|
|
133
|
+
};
|
|
134
|
+
/** Swap {@link refineTable}-narrowed table defs into a generated schema, re-typing those tables for
|
|
135
|
+
* everything downstream of the schema (`newQueryBuilder`/`queries` roots, store row types).
|
|
136
|
+
*
|
|
137
|
+
* Runtime-validated identity: each def must name a table already in the schema and match its
|
|
138
|
+
* runtime shape exactly (same columns, kinds, primary key, and locality) — refinement narrows TS
|
|
139
|
+
* types, never what's on the wire. Composes with {@link extendSchema} in either order. */
|
|
140
|
+
export declare function refineSchema<S extends ColsMap, const T extends readonly RefinableTable<S>[]>(base: Schema<S>, opts: {
|
|
141
|
+
tables: T;
|
|
142
|
+
}): Schema<RefinedColsMap<S, T>>;
|
|
95
143
|
/** Whether `table` is a {@link TableMeta.local local-only} table in `schema` (an unknown table
|
|
96
144
|
* reads as non-local). The single locality predicate the backends key off. */
|
|
97
145
|
export declare function isLocalTable<S extends ColsMap>(schema: Schema<S>, table: string): boolean;
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
// (WASM-CLIENT-DESIGN.md §6). Schema metadata is stored under a `unique symbol` key so it
|
|
7
7
|
// never collides with a column name.
|
|
8
8
|
import { fieldCondition } from "./operators.js";
|
|
9
|
-
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
9
|
+
const makeCol = (type) => ({ type, nullable: () => makeCol(type) });
|
|
10
|
+
export const string = () => makeCol("string");
|
|
11
|
+
export const number = () => makeCol("number");
|
|
12
|
+
export const boolean = () => makeCol("boolean");
|
|
13
|
+
export const json = () => makeCol("json");
|
|
13
14
|
/** Metadata key on a {@link TableDef} (a `unique symbol`, so no column name collides). */
|
|
14
15
|
export const SCHEMA = Symbol("rindle.schema");
|
|
15
16
|
/** `table("issue").columns({ id: string(), … }).primaryKey("id")`. Pass `{ local: true }` for a
|
|
@@ -92,6 +93,66 @@ function addTableMeta(tables, m, caller) {
|
|
|
92
93
|
}
|
|
93
94
|
tables[m.name] = m;
|
|
94
95
|
}
|
|
96
|
+
/** Narrow a generated table's column TYPES without touching its runtime shape.
|
|
97
|
+
*
|
|
98
|
+
* Returns the SAME def, re-typed (identity, after validating that every refined column exists and
|
|
99
|
+
* keeps its kind) — so conditions built from it, `rel(...)`s anchored on it, and `Row<typeof t>`
|
|
100
|
+
* all see the narrowed types. Pass the result to {@link refineSchema} so query roots narrow too. */
|
|
101
|
+
export function refineTable(base, cols) {
|
|
102
|
+
const meta = base[SCHEMA];
|
|
103
|
+
for (const [name, col] of Object.entries(cols)) {
|
|
104
|
+
if (col === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
const baseCol = meta.columns[name];
|
|
107
|
+
if (baseCol === undefined) {
|
|
108
|
+
throw new Error(`refineTable: table "${meta.name}" has no column "${name}".`);
|
|
109
|
+
}
|
|
110
|
+
if (col.type !== baseCol.type) {
|
|
111
|
+
throw new Error(`refineTable: column "${meta.name}.${name}" is ${baseCol.type}, not ${col.type} — a refinement ` +
|
|
112
|
+
`may only narrow the TS type WITHIN a column's kind, never change the kind itself.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return base;
|
|
116
|
+
}
|
|
117
|
+
/** Swap {@link refineTable}-narrowed table defs into a generated schema, re-typing those tables for
|
|
118
|
+
* everything downstream of the schema (`newQueryBuilder`/`queries` roots, store row types).
|
|
119
|
+
*
|
|
120
|
+
* Runtime-validated identity: each def must name a table already in the schema and match its
|
|
121
|
+
* runtime shape exactly (same columns, kinds, primary key, and locality) — refinement narrows TS
|
|
122
|
+
* types, never what's on the wire. Composes with {@link extendSchema} in either order. */
|
|
123
|
+
export function refineSchema(base, opts) {
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (const t of opts.tables) {
|
|
126
|
+
const m = t[SCHEMA];
|
|
127
|
+
const baseMeta = base.tables[m.name];
|
|
128
|
+
if (baseMeta === undefined) {
|
|
129
|
+
throw new Error(`refineSchema: schema has no table "${m.name}" — refinement re-types existing tables only ` +
|
|
130
|
+
`(add local-only tables with extendSchema).`);
|
|
131
|
+
}
|
|
132
|
+
if (seen.has(m.name)) {
|
|
133
|
+
throw new Error(`refineSchema: table "${m.name}" is refined twice.`);
|
|
134
|
+
}
|
|
135
|
+
seen.add(m.name);
|
|
136
|
+
assertRefinementMatches(m, baseMeta);
|
|
137
|
+
}
|
|
138
|
+
return base;
|
|
139
|
+
}
|
|
140
|
+
/** The refined def must be the same table the schema already carries — identical column set, kinds,
|
|
141
|
+
* primary key, and locality — so swapping its TYPE in cannot change any runtime behavior. */
|
|
142
|
+
function assertRefinementMatches(m, baseMeta) {
|
|
143
|
+
const cols = Object.keys(m.columns);
|
|
144
|
+
const baseCols = Object.keys(baseMeta.columns);
|
|
145
|
+
const matches = cols.length === baseCols.length &&
|
|
146
|
+
cols.every((c) => baseMeta.columns[c] !== undefined && m.columns[c].type === baseMeta.columns[c].type) &&
|
|
147
|
+
m.primaryKey.length === baseMeta.primaryKey.length &&
|
|
148
|
+
m.primaryKey.every((k, i) => baseMeta.primaryKey[i] === k) &&
|
|
149
|
+
(m.local === true) === (baseMeta.local === true);
|
|
150
|
+
if (!matches) {
|
|
151
|
+
throw new Error(`refineSchema: table "${m.name}" does not match the schema's table of that name — pass the ` +
|
|
152
|
+
`output of refineTable over the SAME generated def (identical columns, kinds, primary key, ` +
|
|
153
|
+
`and locality).`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
95
156
|
/** Whether `table` is a {@link TableMeta.local local-only} table in `schema` (an unknown table
|
|
96
157
|
* reads as non-local). The single locality predicate the backends key off. */
|
|
97
158
|
export function isLocalTable(schema, table) {
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,EAAE;AACF,6EAA6E;AAC7E,2FAA2F;AAC3F,sFAAsF;AACtF,0FAA0F;AAC1F,qCAAqC;AAGrC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,EAAE;AACF,6EAA6E;AAC7E,2FAA2F;AAC3F,sFAAsF;AACtF,0FAA0F;AAC1F,qCAAqC;AAGrC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAuBhD,MAAM,OAAO,GAAG,CAAI,IAAa,EAAiB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAW,IAAI,CAAC,EAAE,CAAC,CAAC;AAEzG,MAAM,CAAC,MAAM,MAAM,GAAG,GAA6C,EAAE,CAAC,OAAO,CAAI,QAAQ,CAAC,CAAC;AAC3F,MAAM,CAAC,MAAM,MAAM,GAAG,GAA6C,EAAE,CAAC,OAAO,CAAI,QAAQ,CAAC,CAAC;AAC3F,MAAM,CAAC,MAAM,OAAO,GAAG,GAAwB,EAAE,CAAC,OAAO,CAAU,SAAS,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,IAAI,GAAG,GAA+B,EAAE,CAAC,OAAO,CAAI,MAAM,CAAC,CAAC;AAEzE,0FAA0F;AAC1F,MAAM,CAAC,MAAM,MAAM,GAAkB,MAAM,CAAC,eAAe,CAAC,CAAC;AAyC7D;oFACoF;AACpF,MAAM,UAAU,KAAK,CAAmB,IAAO,EAAE,IAAmB;IAClE,OAAO;QACL,OAAO,CAAoB,IAAO;YAChC,OAAO;gBACL,UAAU,CAA6B,GAAG,IAAS;oBACjD,OAAO,YAAY,CAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC3F,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAsC,IAAqB;IAC9E,OAAO,IAAI,KAAK,CAAC,EAAsC,EAAE;QACvD,GAAG,CAAC,OAAO,EAAE,IAAI;YACf,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC;YACjC,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO,CAAC,GAAY,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YACjF,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAA8B,CAAC;AAClC,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,CAAW;IACnC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AACnB,CAAC;AAgBD;;;;;;mFAMmF;AACnF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAU,CAAC;AAEvE,0GAA0G;AAC1G,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,YAAY,CAAsC,IAEjE;IACC,MAAM,MAAM,GAA8B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACpB,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,MAAM,EAAyB,CAAC;AAC3C,CAAC;AAED;;;;;;mEAMmE;AACnE,MAAM,UAAU,YAAY,CAC1B,IAAe,EACf,IAAmB;IAEnB,MAAM,MAAM,GAA8B,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC7D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACpB,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,wBAAwB,CAAC,CAAC,IAAI,+EAA+E,CAC9G,CAAC;QACJ,CAAC;QACD,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,EAAE,MAAM,EAA6B,CAAC;AAC/C,CAAC;AAED,SAAS,YAAY,CAAC,MAAiC,EAAE,CAAY,EAAE,MAAuC;IAC5G,2FAA2F;IAC3F,8EAA8E;IAC9E,IAAI,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,YAAY,CAAC,CAAC,IAAI,6BAA6B,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;YAC9F,mGAAmG,CACtG,CAAC;IACJ,CAAC;IACD,yFAAyF;IACzF,gFAAgF;IAChF,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,sBAAsB,CAAC,CAAC,IAAI,iCAAiC,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AA0BD;;;;qGAIqG;AACrG,MAAM,UAAU,WAAW,CACzB,IAAoB,EACpB,IAAO;IAEP,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAyC,EAAE,CAAC;QACvF,IAAI,GAAG,KAAK,SAAS;YAAE,SAAS;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,IAAI,oBAAoB,IAAI,IAAI,CAAC,CAAC;QAChF,CAAC;QACD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,wBAAwB,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,OAAO,CAAC,IAAI,SAAS,GAAG,CAAC,IAAI,kBAAkB;gBAC9F,mFAAmF,CACtF,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAiD,CAAC;AAC3D,CAAC;AAeD;;;;;2FAK2F;AAC3F,MAAM,UAAU,YAAY,CAC1B,IAAe,EACf,IAAmB;IAEnB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,sCAAsC,CAAC,CAAC,IAAI,+CAA+C;gBACzF,4CAA4C,CAC/C,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,IAAI,qBAAqB,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjB,uBAAuB,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAA+C,CAAC;AACzD,CAAC;AAED;8FAC8F;AAC9F,SAAS,uBAAuB,CAAC,CAAY,EAAE,QAAmB;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;QAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtG,CAAC,CAAC,UAAU,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,CAAC,MAAM;QAClD,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,wBAAwB,CAAC,CAAC,IAAI,8DAA8D;YAC1F,4FAA4F;YAC5F,gBAAgB,CACnB,CAAC;IACJ,CAAC;AACH,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,YAAY,CAAoB,MAAiB,EAAE,KAAa;IAC9E,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;AAC9C,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAoB,MAAiB;IAClE,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,oGAAoG;AACpG,EAAE;AACF,mGAAmG;AACnG,sGAAsG;AACtG,6FAA6F;AAE7F,mGAAmG;AACnG,MAAM,kBAAkB,GAAkB,MAAM,CAAC,qBAAqB,CAAC,CAAC;AAqBxE;;;;GAIG;AACH,MAAM,UAAU,GAAG,CACjB,OAAsB,EACtB,KAAoB,EACpB,OAA8D;IAE9D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAsB,CAAW,CAAC,CAAC;IAC/E,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,CAAC;AAC1F,CAAC;AAED;yGACyG;AACzG,MAAM,UAAU,mBAAmB,CAA4C,IAAO;IACpF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,CAAU;IACvC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAA8B,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;AAC7G,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;oFAQoF;AACpF,MAAM,UAAU,sBAAsB,CACpC,MAAiB;IAEjB,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;SAC9B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;SAC5C,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
package/dist/ssr.d.ts
CHANGED
|
@@ -68,6 +68,20 @@ export declare class ServerStore<S extends ColsMap> {
|
|
|
68
68
|
/** The dehydrated first-paint cache for every preloaded query — embed in the HTML, then
|
|
69
69
|
* `store.hydrate(...)` it in the browser. */
|
|
70
70
|
dehydrate(): DehydratedState;
|
|
71
|
+
/**
|
|
72
|
+
* Loader-phase convenience over {@link preload} + {@link dehydrate}: preload EVERY query (reads run
|
|
73
|
+
* concurrently) and return the dehydrated first-paint cache. Composition keeps this to one read per
|
|
74
|
+
* composed root query — no request waterfall (SSR-DESIGN.md §6.2).
|
|
75
|
+
*
|
|
76
|
+
* A failed read degrades to NO seed for that one query — `onError` fires and the browser's live
|
|
77
|
+
* engine fills it in right after hydration — rather than rejecting the whole batch (which would trip
|
|
78
|
+
* the route's error boundary). The seed is a first-paint optimization; the live `subscribe` is the
|
|
79
|
+
* source of truth, so a missing seed never affects correctness. Without `onError` a failed read is
|
|
80
|
+
* swallowed silently — pass one to log.
|
|
81
|
+
*/
|
|
82
|
+
preloadAll(queries: Array<Query<any, any, any>>, opts?: {
|
|
83
|
+
onError?: (query: Query<any, any, any>, err: unknown) => void;
|
|
84
|
+
}): Promise<DehydratedState>;
|
|
71
85
|
}
|
|
72
86
|
/** Construct a {@link ServerStore} — the one-shot REST Store for server-side rendering. */
|
|
73
87
|
export declare function createServerStore<S extends ColsMap>(schema: Schema<S>, opts: ServerStoreOptions): ServerStore<S>;
|
package/dist/ssr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEvF;;wFAEwF;AACxF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;4EAU4E;AAC5E,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KAC3F,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,OAAO;IAC5C,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI;IACxE,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IACpC,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAG7C,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,oGAAoG;IACpG,KAAK,EAAE,cAAc,CAAC;IACtB;;+EAE2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;6DAGyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,OAAO;IACxC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;gBAE9B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,kBAAkB;IAMvD;kFAC8E;IACxE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBzD;kDAC8C;IAC9C,SAAS,IAAI,eAAe;
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEvF;;wFAEwF;AACxF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;4EAU4E;AAC5E,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KAC3F,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,OAAO;IAC5C,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI;IACxE,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IACpC,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAG7C,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,oGAAoG;IACpG,KAAK,EAAE,cAAc,CAAC;IACtB;;+EAE2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;6DAGyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,WAAW,CAAC,CAAC,SAAS,OAAO;IACxC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;gBAE9B,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,kBAAkB;IAMvD;kFAC8E;IACxE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBzD;kDAC8C;IAC9C,SAAS,IAAI,eAAe;IAI5B;;;;;;;;;;OAUG;IACG,UAAU,CACd,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EACpC,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;KAAO,GAC3E,OAAO,CAAC,eAAe,CAAC;CAM5B;AAED,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EACjD,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,IAAI,EAAE,kBAAkB,GACvB,WAAW,CAAC,CAAC,CAAC,CAEhB"}
|
package/dist/ssr.js
CHANGED
|
@@ -66,6 +66,21 @@ export class ServerStore {
|
|
|
66
66
|
dehydrate() {
|
|
67
67
|
return this.store.dehydrate();
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Loader-phase convenience over {@link preload} + {@link dehydrate}: preload EVERY query (reads run
|
|
71
|
+
* concurrently) and return the dehydrated first-paint cache. Composition keeps this to one read per
|
|
72
|
+
* composed root query — no request waterfall (SSR-DESIGN.md §6.2).
|
|
73
|
+
*
|
|
74
|
+
* A failed read degrades to NO seed for that one query — `onError` fires and the browser's live
|
|
75
|
+
* engine fills it in right after hydration — rather than rejecting the whole batch (which would trip
|
|
76
|
+
* the route's error boundary). The seed is a first-paint optimization; the live `subscribe` is the
|
|
77
|
+
* source of truth, so a missing seed never affects correctness. Without `onError` a failed read is
|
|
78
|
+
* swallowed silently — pass one to log.
|
|
79
|
+
*/
|
|
80
|
+
async preloadAll(queries, opts = {}) {
|
|
81
|
+
await Promise.all(queries.map((query) => this.preload(query).catch((err) => opts.onError?.(query, err))));
|
|
82
|
+
return this.dehydrate();
|
|
83
|
+
}
|
|
69
84
|
}
|
|
70
85
|
/** Construct a {@link ServerStore} — the one-shot REST Store for server-side rendering. */
|
|
71
86
|
export function createServerStore(schema, opts) {
|
package/dist/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,gGAAgG;AAChG,EAAE;AACF,+EAA+E;AAC/E,iGAAiG;AACjG,mGAAmG;AACnG,kGAAkG;AAClG,4FAA4F;AAC5F,EAAE;AACF,gGAAgG;AAChG,mCAAmC;AAEnC,OAAO,EAAE,mBAAmB,EAAc,MAAM,YAAY,CAAC;AAE7D,OAAO,EAA4C,KAAK,EAAE,MAAM,YAAY,CAAC;AA0B7E;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IACzB,aAAa,CAAC,IAAa,EAAE,IAAa,EAAE,OAAqB,IAAS,CAAC;IAC3E,eAAe,CAAC,IAAa,IAAS,CAAC;IACvC,MAAM,CAAC,UAAsB;QAC3B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC,CAAC;IACzG,CAAC;IACD,OAAO,CAAC,QAAwD,IAAS,CAAC;CAC3E;AAgBD;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACb,KAAK,CAAW;IACR,MAAM,CAAY;IAClB,IAAI,CAAqB;IAE1C,YAAY,MAAiB,EAAE,IAAwB;QACrD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,cAAc,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;kFAC8E;IAC9E,KAAK,CAAC,OAAO,CAAC,KAA2B;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxB,gGAAgG;QAChG,+FAA+F;QAC/F,gGAAgG;QAChG,qDAAqD;QACrD,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,+FAA+F;QAC/F,+FAA+F;QAC/F,uFAAuF;QACvF,qDAAqD;QACrD,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAClG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YACnC,GAAG;YACH,GAAG,KAAK;YACR,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa;YACtC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;SACvB,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;kDAC8C;IAC9C,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;CACF;AAED,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAC/B,MAAiB,EACjB,IAAwB;IAExB,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
|
1
|
+
{"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,gGAAgG;AAChG,EAAE;AACF,+EAA+E;AAC/E,iGAAiG;AACjG,mGAAmG;AACnG,kGAAkG;AAClG,4FAA4F;AAC5F,EAAE;AACF,gGAAgG;AAChG,mCAAmC;AAEnC,OAAO,EAAE,mBAAmB,EAAc,MAAM,YAAY,CAAC;AAE7D,OAAO,EAA4C,KAAK,EAAE,MAAM,YAAY,CAAC;AA0B7E;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IACzB,aAAa,CAAC,IAAa,EAAE,IAAa,EAAE,OAAqB,IAAS,CAAC;IAC3E,eAAe,CAAC,IAAa,IAAS,CAAC;IACvC,MAAM,CAAC,UAAsB;QAC3B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC,CAAC;IACzG,CAAC;IACD,OAAO,CAAC,QAAwD,IAAS,CAAC;CAC3E;AAgBD;;;;GAIG;AACH,MAAM,OAAO,WAAW;IACb,KAAK,CAAW;IACR,MAAM,CAAY;IAClB,IAAI,CAAqB;IAE1C,YAAY,MAAiB,EAAE,IAAwB;QACrD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,cAAc,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED;kFAC8E;IAC9E,KAAK,CAAC,OAAO,CAAC,KAA2B;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QACxB,gGAAgG;QAChG,+FAA+F;QAC/F,gGAAgG;QAChG,qDAAqD;QACrD,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACtC,+FAA+F;QAC/F,+FAA+F;QAC/F,uFAAuF;QACvF,qDAAqD;QACrD,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAClG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YACnC,GAAG;YACH,GAAG,KAAK;YACR,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,aAAa;YACtC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;SACvB,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;kDAC8C;IAC9C,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;IAChC,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,UAAU,CACd,OAAoC,EACpC,OAA0E,EAAE;QAE5E,MAAM,OAAO,CAAC,GAAG,CACf,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CACvF,CAAC;QACF,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;IAC1B,CAAC;CACF;AAED,2FAA2F;AAC3F,MAAM,UAAU,iBAAiB,CAC/B,MAAiB,EACjB,IAAwB;IAExB,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// comparator, Backend seam, and the ArrayView contract. (The ArrayView folding impl + the
|
|
3
3
|
// Store/backend wiring land in later slices.)
|
|
4
4
|
|
|
5
|
-
export * from "./schema.ts"; // table/columns/primaryKey, createSchema/extendSchema, string/number/boolean/json, Col, RowOf, Row, TableDef, tableSpec, SCHEMA
|
|
5
|
+
export * from "./schema.ts"; // table/columns/primaryKey, createSchema/extendSchema, refineTable/refineSchema, string/number/boolean/json, Col, RowOf, Row, TableDef, tableSpec, SCHEMA
|
|
6
6
|
export * from "./operators.ts"; // eq/ne/gt/ge/lt/le/like/notLike/ilike/notIlike/inList/notInList/is/isNot, and/or, Pred, Cond, Arg
|
|
7
7
|
export * from "./query.ts"; // Query, QueryRoot, queries/newQueryBuilder, defineQuery/NamedQuery, exists, notExists, defineFragment/Fragment/FragmentRef/isFragment
|
|
8
8
|
export * from "./view.ts"; // ArrayView, SingularArrayView, FlatArrayView, SingularView, ViewTypes
|
|
@@ -12,6 +12,29 @@ export * from "./ssr.ts"; // createServerStore, ServerStore, OneShotBackend, One
|
|
|
12
12
|
export { resolveChange, subRow } from "./resolve.ts"; // positional FlatChange → named rows (inverse of the wire encoder)
|
|
13
13
|
export type { NamedRow, ResolvedChange } from "./resolve.ts";
|
|
14
14
|
|
|
15
|
+
export type { KeyedRow, MutationOp, ServerWriteTx } from "./mutation-ops.ts"; // isomorphic mutation op vocabulary
|
|
16
|
+
// shared (generator) mutator seam — one body, sync on the client + async on the server
|
|
17
|
+
export {
|
|
18
|
+
driveMutationAsync,
|
|
19
|
+
driveMutationSync,
|
|
20
|
+
isGeneratorMutator,
|
|
21
|
+
isoTx,
|
|
22
|
+
shared,
|
|
23
|
+
} from "./mutation-ops.ts";
|
|
24
|
+
export type {
|
|
25
|
+
ArgSchema,
|
|
26
|
+
AsyncEffectExec,
|
|
27
|
+
BatchEffect,
|
|
28
|
+
IsoTx,
|
|
29
|
+
MutationGen,
|
|
30
|
+
MutatorCtx,
|
|
31
|
+
ReadEffect,
|
|
32
|
+
SharedMutator,
|
|
33
|
+
SharedMutatorWithArgs,
|
|
34
|
+
SyncEffectExec,
|
|
35
|
+
YieldEffect,
|
|
36
|
+
} from "./mutation-ops.ts";
|
|
37
|
+
|
|
15
38
|
export { stableKey } from "./key.ts"; // canonical viewKey for an AST (shared with @rindle/react)
|
|
16
39
|
|
|
17
40
|
export { COMPARATOR_VERSION, compareNumber, compareRows, compareString, compareValue } from "./compare.ts";
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Isomorphic mutation ops — the backend-agnostic "rindle mutation" vocabulary
|
|
2
|
+
// (MUTATORS-ISOMORPHIC plan). A mutator names WHAT it writes (insert/update/upsert/
|
|
3
|
+
// insertIgnore/delete over a keyed row), not HOW — the server renders dialect SQL and the
|
|
4
|
+
// client applies to its local engine. This lives in `@rindle/client` (the leaf both tiers
|
|
5
|
+
// import); the SQL renderer that consumes it is server-only (`@rindle/api-server`).
|
|
6
|
+
|
|
7
|
+
import type { WireValue } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
/** A keyed row: column name → cell. The ergonomic write shape (validated against the schema at
|
|
10
|
+
* runtime). JSON columns carry their raw JSON string (a {@link WireValue}), never a parsed object. */
|
|
11
|
+
export type KeyedRow = Record<string, WireValue>;
|
|
12
|
+
|
|
13
|
+
/** One structured write intent — the discriminated union that mirrors the write half of the client
|
|
14
|
+
* `MutationTx` 1:1. Keyed (column-name addressed), so it is independent of column order. */
|
|
15
|
+
export type MutationOp =
|
|
16
|
+
| { kind: "insert"; table: string; row: KeyedRow } // a FULL row (every column present)
|
|
17
|
+
| { kind: "upsert"; table: string; row: KeyedRow } // full row; replace non-pk cols on pk conflict
|
|
18
|
+
| { kind: "insertIgnore"; table: string; row: KeyedRow } // full row; do nothing on pk conflict
|
|
19
|
+
| { kind: "update"; table: string; row: KeyedRow } // pk cols + the non-pk cols to change
|
|
20
|
+
| { kind: "delete"; table: string; pk: KeyedRow }; // pk cols only
|
|
21
|
+
|
|
22
|
+
/** The ASYNC server-side write surface a mutator runs against. Semantically the twin of the client's
|
|
23
|
+
* synchronous `MutationTx` write methods — same names, same keyed-row arguments — but every op is a
|
|
24
|
+
* Promise so a Postgres backend can execute it live against an open transaction (read-your-writes).
|
|
25
|
+
* A rindle/daemon backend resolves writes immediately (accumulate) and serves `row` from committed
|
|
26
|
+
* state. `insert` requires every column; `update`/`delete` require the pk columns. */
|
|
27
|
+
export interface ServerWriteTx {
|
|
28
|
+
/** Insert a FULL row (every column named; missing or unknown columns throw). */
|
|
29
|
+
insert(table: string, row: KeyedRow): Promise<void>;
|
|
30
|
+
/** Update the row identified by the pk columns; only the named non-pk columns change. A missing
|
|
31
|
+
* row is a NO-OP. */
|
|
32
|
+
update(table: string, row: KeyedRow): Promise<void>;
|
|
33
|
+
/** Insert, or fully replace when the pk already exists (a FULL row, like `insert`). */
|
|
34
|
+
upsert(table: string, row: KeyedRow): Promise<void>;
|
|
35
|
+
/** Insert a FULL row, or do nothing if the pk already exists (the SQL twin of the client
|
|
36
|
+
* `if (!tx.row(pk)) tx.insert(row)` pattern). */
|
|
37
|
+
insertIgnore(table: string, row: KeyedRow): Promise<void>;
|
|
38
|
+
/** Delete the row identified by the pk columns. A missing row is a NO-OP. */
|
|
39
|
+
delete(table: string, pk: KeyedRow): Promise<void>;
|
|
40
|
+
/** Read one row by primary key, through the OPEN transaction on both backends
|
|
41
|
+
* (read-your-writes: live on Postgres; via an interactive mutation session on the daemon,
|
|
42
|
+
* DAEMON-INTERACTIVE-TXN-DESIGN.md §5.3). */
|
|
43
|
+
row(table: string, pk: KeyedRow): Promise<KeyedRow | undefined>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --------------------------------------------------------------------------- shared (generator) mutators
|
|
47
|
+
//
|
|
48
|
+
// The isomorphic mutator seam (MUTATORS-ISOMORPHIC plan §"one body, two tiers"): a mutator is a
|
|
49
|
+
// GENERATOR that `yield`s effects instead of an async/sync function. A generator is neither sync nor
|
|
50
|
+
// async — the tier's DRIVER decides — so ONE body runs synchronously against the browser wasm engine
|
|
51
|
+
// AND against a live async Postgres transaction on the server. Writes are `yield`ed and pipelined by
|
|
52
|
+
// the driver (never individually awaited by the body); a `yield tx.row(...)` is the one thing that
|
|
53
|
+
// suspends, evaluating to the read row. `yield tx.all([...])` fans reads out (Promise.all on the
|
|
54
|
+
// server, in-order on the client) — order-preserving on both tiers, so the body stays deterministic.
|
|
55
|
+
|
|
56
|
+
/** A point read a generator mutator yields; the driver resolves it and feeds the row back through
|
|
57
|
+
* `gen.next(row)`. Read-your-writes on every tier: the client reads its local engine, and both
|
|
58
|
+
* server backends read the open transaction (the daemon via an interactive mutation session). */
|
|
59
|
+
export type ReadEffect = { kind: "row"; table: string; pk: KeyedRow };
|
|
60
|
+
|
|
61
|
+
/** A fan-out a generator mutator yields: run several effects "together". The server driver resolves
|
|
62
|
+
* them with `Promise.all`; the client driver runs them in array order (already synchronous). Results
|
|
63
|
+
* return in the SAME order on both tiers, so the mutator stays deterministic. The `yield` evaluates
|
|
64
|
+
* to `(KeyedRow | undefined)[]` at runtime (cast it — the generator's single next-type is a row). */
|
|
65
|
+
export type BatchEffect = { kind: "all"; effects: readonly YieldEffect[] };
|
|
66
|
+
|
|
67
|
+
/** Everything a generator mutator may `yield`: a write {@link MutationOp}, a {@link ReadEffect}, or a
|
|
68
|
+
* {@link BatchEffect}. */
|
|
69
|
+
export type YieldEffect = MutationOp | ReadEffect | BatchEffect;
|
|
70
|
+
|
|
71
|
+
/** The tier-AGNOSTIC effect factory a generator mutator writes against. Every method just BUILDS an
|
|
72
|
+
* effect to `yield` — it performs no I/O and holds no state, so the single {@link isoTx} instance is
|
|
73
|
+
* shared by every mutator on both tiers; only the driver differs. `insert`/`upsert`/`insertIgnore`
|
|
74
|
+
* take a FULL row; `update`/`delete` take the pk columns (`update` adds the columns to change). */
|
|
75
|
+
export interface IsoTx {
|
|
76
|
+
insert(table: string, row: KeyedRow): MutationOp;
|
|
77
|
+
update(table: string, row: KeyedRow): MutationOp;
|
|
78
|
+
upsert(table: string, row: KeyedRow): MutationOp;
|
|
79
|
+
insertIgnore(table: string, row: KeyedRow): MutationOp;
|
|
80
|
+
delete(table: string, pk: KeyedRow): MutationOp;
|
|
81
|
+
row(table: string, pk: KeyedRow): ReadEffect;
|
|
82
|
+
all(effects: readonly YieldEffect[]): BatchEffect;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The one shared effect factory (stateless — see {@link IsoTx}). */
|
|
86
|
+
export const isoTx: IsoTx = {
|
|
87
|
+
insert: (table, row) => ({ kind: "insert", table, row }),
|
|
88
|
+
update: (table, row) => ({ kind: "update", table, row }),
|
|
89
|
+
upsert: (table, row) => ({ kind: "upsert", table, row }),
|
|
90
|
+
insertIgnore: (table, row) => ({ kind: "insertIgnore", table, row }),
|
|
91
|
+
delete: (table, pk) => ({ kind: "delete", table, pk }),
|
|
92
|
+
row: (table, pk) => ({ kind: "row", table, pk }),
|
|
93
|
+
all: (effects) => ({ kind: "all", effects }),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** The minimal per-invocation context a shared mutator sees on BOTH tiers: the acting principal. The
|
|
97
|
+
* server injects its AUTHENTICATED user; the client injects its local user. (The server's own
|
|
98
|
+
* `MutationContext` is a superset of this.) */
|
|
99
|
+
export interface MutatorCtx {
|
|
100
|
+
user: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** What a generator mutator IS: `yield tx.<op>()` on every side effect; a `yield tx.row()` expression
|
|
104
|
+
* evaluates to the row. Neither sync nor async — the tier's driver decides, which is what lets one
|
|
105
|
+
* body run synchronously on the client and against a live async transaction on the server. */
|
|
106
|
+
export type MutationGen = Generator<YieldEffect, void, KeyedRow | undefined>;
|
|
107
|
+
|
|
108
|
+
/** A generator (isomorphic) mutator, shared verbatim by both tiers: the client trusts typed `args`,
|
|
109
|
+
* the server parses untrusted args into `Args` before invoking. */
|
|
110
|
+
export type SharedMutator<Args, Ctx extends MutatorCtx = MutatorCtx> = (
|
|
111
|
+
tx: IsoTx,
|
|
112
|
+
args: Args,
|
|
113
|
+
ctx: Ctx,
|
|
114
|
+
) => MutationGen;
|
|
115
|
+
|
|
116
|
+
/** The minimal arg validator a shared mutator can carry so the SERVER can parse UNTRUSTED wire args
|
|
117
|
+
* before driving it (the client trusts its typed callsites and never calls this). Structural on
|
|
118
|
+
* purpose — a zod schema satisfies it as-is — so `@rindle/client` stays validator-library-agnostic. */
|
|
119
|
+
export interface ArgSchema<Args> {
|
|
120
|
+
parse(raw: unknown): Args;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A shared mutator that CARRIES its own arg validator, co-located at the def site (`shared(schema,
|
|
124
|
+
* gen)`). The client registers it exactly like a bare generator mutator — the `.args` validator is
|
|
125
|
+
* inert there (typed callsites skip the parse); the server ({@link runSharedMutation}, via the
|
|
126
|
+
* api-server's `sharedApiMutators`) reads `.args` to parse untrusted wire args before driving the
|
|
127
|
+
* SAME body. */
|
|
128
|
+
export type SharedMutatorWithArgs<Args, Ctx extends MutatorCtx = MutatorCtx> = SharedMutator<Args, Ctx> & {
|
|
129
|
+
args: ArgSchema<Args>;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** Co-locate a shared (generator) mutator with the validator for its args — pairing the arg SHAPE
|
|
133
|
+
* with the body that consumes it at ONE site, so neither tier restates it (the client derives its
|
|
134
|
+
* callsite type from `Args`; the server parses untrusted args through `.args`). Returns the SAME
|
|
135
|
+
* generator function with an `args` property attached (`Object.assign` mutates + returns it), so the
|
|
136
|
+
* registered value is byte-for-byte what the client drove before: {@link isGeneratorMutator} still
|
|
137
|
+
* detects it and it still `satisfies ClientRegistry`. */
|
|
138
|
+
export function shared<Args, Ctx extends MutatorCtx = MutatorCtx>(
|
|
139
|
+
args: ArgSchema<Args>,
|
|
140
|
+
run: SharedMutator<Args, Ctx>,
|
|
141
|
+
): SharedMutatorWithArgs<Args, Ctx> {
|
|
142
|
+
return Object.assign(run, { args });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** True iff `fn` is a generator function (a shared/isomorphic mutator) rather than a plain function —
|
|
146
|
+
* the driver accepts both forms. Detected structurally (native `GeneratorFunction`). */
|
|
147
|
+
export function isGeneratorMutator(fn: unknown): fn is (...args: never[]) => MutationGen {
|
|
148
|
+
return (
|
|
149
|
+
typeof fn === "function" &&
|
|
150
|
+
(fn as { constructor?: { name?: string } }).constructor?.name === "GeneratorFunction"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** A tier's SYNCHRONOUS effect executor (the browser wasm engine): apply a write, resolve a read —
|
|
155
|
+
* both immediate. */
|
|
156
|
+
export interface SyncEffectExec {
|
|
157
|
+
apply(op: MutationOp): void;
|
|
158
|
+
read(table: string, pk: KeyedRow): KeyedRow | undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Drive a generator mutator SYNCHRONOUSLY (the client, inside the wasm write transaction). Each
|
|
162
|
+
* yielded write applies immediately; each read is resolved and fed back; a batch runs in order. */
|
|
163
|
+
export function driveMutationSync(gen: MutationGen, exec: SyncEffectExec): void {
|
|
164
|
+
const run = (eff: YieldEffect): KeyedRow | undefined => {
|
|
165
|
+
if (eff.kind === "row") return exec.read(eff.table, eff.pk);
|
|
166
|
+
if (eff.kind === "all") return eff.effects.map(run) as unknown as KeyedRow | undefined;
|
|
167
|
+
exec.apply(eff);
|
|
168
|
+
return undefined;
|
|
169
|
+
};
|
|
170
|
+
for (let step = gen.next(); !step.done; step = gen.next(run(step.value)));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** A tier's ASYNCHRONOUS effect executor (the server transaction): every op is a Promise. */
|
|
174
|
+
export interface AsyncEffectExec {
|
|
175
|
+
apply(op: MutationOp): Promise<void>;
|
|
176
|
+
read(table: string, pk: KeyedRow): Promise<KeyedRow | undefined>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Drive a generator mutator ASYNCHRONOUSLY (the server, against the open transaction). Writes are
|
|
180
|
+
* awaited (harmless: a single interactive Postgres connection serializes statements anyway, and the
|
|
181
|
+
* daemon backend resolves them instantly), reads suspend, and a batch fans out with `Promise.all`. */
|
|
182
|
+
export async function driveMutationAsync(gen: MutationGen, exec: AsyncEffectExec): Promise<void> {
|
|
183
|
+
const run = async (eff: YieldEffect): Promise<KeyedRow | undefined> => {
|
|
184
|
+
if (eff.kind === "row") return exec.read(eff.table, eff.pk);
|
|
185
|
+
if (eff.kind === "all") return (await Promise.all(eff.effects.map(run))) as unknown as KeyedRow | undefined;
|
|
186
|
+
await exec.apply(eff);
|
|
187
|
+
return undefined;
|
|
188
|
+
};
|
|
189
|
+
for (let step = gen.next(); !step.done; step = gen.next(await run(step.value)));
|
|
190
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -10,7 +10,9 @@ import type { Arg, Cond } from "./operators.ts";
|
|
|
10
10
|
import { fieldCondition } from "./operators.ts";
|
|
11
11
|
import type { ColType } from "./types.ts";
|
|
12
12
|
|
|
13
|
-
/** A column descriptor. `type` drives the comparator + JSON parsing; `__t` is a phantom.
|
|
13
|
+
/** A column descriptor. `type` drives the comparator + JSON parsing; `__t` is a phantom. A
|
|
14
|
+
* nullable column is a `Col<T | null>` — that is the ONLY thing nullability changes at the type
|
|
15
|
+
* level, so `RowOf` (and the field-condition factory) widen automatically. */
|
|
14
16
|
export interface Col<T> {
|
|
15
17
|
readonly type: ColType;
|
|
16
18
|
readonly __t?: T;
|
|
@@ -19,10 +21,21 @@ export type ColT<X> = X extends Col<infer T> ? T : never;
|
|
|
19
21
|
export type AnyCols = Record<string, Col<unknown>>;
|
|
20
22
|
export type RowOf<C extends AnyCols> = { [K in keyof C]: ColT<C[K]> };
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/** The chainable form returned by the column factories: a {@link Col} plus `.nullable()`.
|
|
25
|
+
*
|
|
26
|
+
* `.nullable()` widens the column's value type to `T | null` — its `Row<…>` field becomes
|
|
27
|
+
* `T | null`, and (design 206 Phase 2) it may be omitted from an insert. `rindle schema gen` emits
|
|
28
|
+
* `.nullable()` for every nullable (non-`NOT NULL`) SQL column; you can also call it by hand on a
|
|
29
|
+
* local-only table's columns. It is idempotent and stays chainable. */
|
|
30
|
+
export interface ColBuilder<T> extends Col<T> {
|
|
31
|
+
nullable(): ColBuilder<T | null>;
|
|
32
|
+
}
|
|
33
|
+
const makeCol = <T>(type: ColType): ColBuilder<T> => ({ type, nullable: () => makeCol<T | null>(type) });
|
|
34
|
+
|
|
35
|
+
export const string = <T extends string = string>(): ColBuilder<T> => makeCol<T>("string");
|
|
36
|
+
export const number = <T extends number = number>(): ColBuilder<T> => makeCol<T>("number");
|
|
37
|
+
export const boolean = (): ColBuilder<boolean> => makeCol<boolean>("boolean");
|
|
38
|
+
export const json = <T = unknown>(): ColBuilder<T> => makeCol<T>("json");
|
|
26
39
|
|
|
27
40
|
/** Metadata key on a {@link TableDef} (a `unique symbol`, so no column name collides). */
|
|
28
41
|
export const SCHEMA: unique symbol = Symbol("rindle.schema");
|
|
@@ -175,6 +188,118 @@ function addTableMeta(tables: Record<string, TableMeta>, m: TableMeta, caller: "
|
|
|
175
188
|
tables[m.name] = m;
|
|
176
189
|
}
|
|
177
190
|
|
|
191
|
+
// ----------------------------- refinement (narrowing generated column types) ---------------------
|
|
192
|
+
//
|
|
193
|
+
// SQL carries a column's KIND (string/number/boolean/json) but not a refinement WITHIN a kind — the
|
|
194
|
+
// element type of `json<T>()`, or a string/number literal union. `refineTable` re-types those
|
|
195
|
+
// columns on a generated `TableDef` (so its field conditions, `rel(...)`s, and `Row<typeof t>` all
|
|
196
|
+
// narrow), and `refineSchema` swaps the refined defs into the generated schema (so query roots and
|
|
197
|
+
// result rows narrow too). Both are runtime-validated IDENTITIES: a refinement narrows the phantom
|
|
198
|
+
// TS type but can never change a column's runtime kind, so the def still matches the daemon's wire
|
|
199
|
+
// schema exactly. Keep refinements in a hand-written module (beside `extendSchema`'s local tables);
|
|
200
|
+
// `schema.gen.ts` stays a pure, wholesale-regenerated artifact.
|
|
201
|
+
|
|
202
|
+
/** Per-column narrowings for {@link refineTable}: each entry must keep the column's kind and narrow
|
|
203
|
+
* its TS type (`Col<T2>` with `T2 extends T`) — `json<Meta>()` on a json column, a literal-union
|
|
204
|
+
* `string<"a" | "b">()` on a string column. Kind changes are rejected (cross-kind at compile time,
|
|
205
|
+
* same-phantom kind flips like `string()` on a json column at runtime). */
|
|
206
|
+
export type ColRefinements<C extends AnyCols> = {
|
|
207
|
+
readonly [K in keyof C]?: Col<ColT<C[K]>>;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/** `C` with the columns named in `R` re-typed to their refined `Col`s. */
|
|
211
|
+
export type RefinedCols<C extends AnyCols, R extends ColRefinements<C>> = {
|
|
212
|
+
[K in keyof C]: K extends keyof R ? (R[K] extends Col<unknown> ? R[K] : C[K]) : C[K];
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** Narrow a generated table's column TYPES without touching its runtime shape.
|
|
216
|
+
*
|
|
217
|
+
* Returns the SAME def, re-typed (identity, after validating that every refined column exists and
|
|
218
|
+
* keeps its kind) — so conditions built from it, `rel(...)`s anchored on it, and `Row<typeof t>`
|
|
219
|
+
* all see the narrowed types. Pass the result to {@link refineSchema} so query roots narrow too. */
|
|
220
|
+
export function refineTable<N extends string, C extends AnyCols, R extends ColRefinements<C>>(
|
|
221
|
+
base: TableDef<N, C>,
|
|
222
|
+
cols: R,
|
|
223
|
+
): TableDef<N, RefinedCols<C, R>> {
|
|
224
|
+
const meta = base[SCHEMA];
|
|
225
|
+
for (const [name, col] of Object.entries(cols) as [string, Col<unknown> | undefined][]) {
|
|
226
|
+
if (col === undefined) continue;
|
|
227
|
+
const baseCol = meta.columns[name];
|
|
228
|
+
if (baseCol === undefined) {
|
|
229
|
+
throw new Error(`refineTable: table "${meta.name}" has no column "${name}".`);
|
|
230
|
+
}
|
|
231
|
+
if (col.type !== baseCol.type) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`refineTable: column "${meta.name}.${name}" is ${baseCol.type}, not ${col.type} — a refinement ` +
|
|
234
|
+
`may only narrow the TS type WITHIN a column's kind, never change the kind itself.`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return base as unknown as TableDef<N, RefinedCols<C, R>>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** A table def acceptable to {@link refineSchema} over `Schema<S>`: its NAME must be one of `S`'s
|
|
242
|
+
* tables (a def for an unknown table is a compile error at the call site). */
|
|
243
|
+
export type RefinableTable<S extends ColsMap> = {
|
|
244
|
+
readonly [SCHEMA]: TableMeta<Extract<keyof S, string>, AnyCols>;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/** `S` with each table named in `T` re-typed to that def's (refined) columns. (The inner
|
|
248
|
+
* `extends AnyCols` guard is how the checker proves the remapped `SchemaOf<T>[K]` is a column
|
|
249
|
+
* map while `T` is still generic; it always holds for a concrete `T`.) */
|
|
250
|
+
export type RefinedColsMap<S extends ColsMap, T extends readonly AnyTable[]> = {
|
|
251
|
+
[K in keyof S]: K extends keyof SchemaOf<T> ? (SchemaOf<T>[K] extends AnyCols ? SchemaOf<T>[K] : S[K]) : S[K];
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/** Swap {@link refineTable}-narrowed table defs into a generated schema, re-typing those tables for
|
|
255
|
+
* everything downstream of the schema (`newQueryBuilder`/`queries` roots, store row types).
|
|
256
|
+
*
|
|
257
|
+
* Runtime-validated identity: each def must name a table already in the schema and match its
|
|
258
|
+
* runtime shape exactly (same columns, kinds, primary key, and locality) — refinement narrows TS
|
|
259
|
+
* types, never what's on the wire. Composes with {@link extendSchema} in either order. */
|
|
260
|
+
export function refineSchema<S extends ColsMap, const T extends readonly RefinableTable<S>[]>(
|
|
261
|
+
base: Schema<S>,
|
|
262
|
+
opts: { tables: T },
|
|
263
|
+
): Schema<RefinedColsMap<S, T>> {
|
|
264
|
+
const seen = new Set<string>();
|
|
265
|
+
for (const t of opts.tables) {
|
|
266
|
+
const m = t[SCHEMA];
|
|
267
|
+
const baseMeta = base.tables[m.name];
|
|
268
|
+
if (baseMeta === undefined) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`refineSchema: schema has no table "${m.name}" — refinement re-types existing tables only ` +
|
|
271
|
+
`(add local-only tables with extendSchema).`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
if (seen.has(m.name)) {
|
|
275
|
+
throw new Error(`refineSchema: table "${m.name}" is refined twice.`);
|
|
276
|
+
}
|
|
277
|
+
seen.add(m.name);
|
|
278
|
+
assertRefinementMatches(m, baseMeta);
|
|
279
|
+
}
|
|
280
|
+
return base as unknown as Schema<RefinedColsMap<S, T>>;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** The refined def must be the same table the schema already carries — identical column set, kinds,
|
|
284
|
+
* primary key, and locality — so swapping its TYPE in cannot change any runtime behavior. */
|
|
285
|
+
function assertRefinementMatches(m: TableMeta, baseMeta: TableMeta): void {
|
|
286
|
+
const cols = Object.keys(m.columns);
|
|
287
|
+
const baseCols = Object.keys(baseMeta.columns);
|
|
288
|
+
const matches =
|
|
289
|
+
cols.length === baseCols.length &&
|
|
290
|
+
cols.every((c) => baseMeta.columns[c] !== undefined && m.columns[c].type === baseMeta.columns[c].type) &&
|
|
291
|
+
m.primaryKey.length === baseMeta.primaryKey.length &&
|
|
292
|
+
m.primaryKey.every((k, i) => baseMeta.primaryKey[i] === k) &&
|
|
293
|
+
(m.local === true) === (baseMeta.local === true);
|
|
294
|
+
if (!matches) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`refineSchema: table "${m.name}" does not match the schema's table of that name — pass the ` +
|
|
297
|
+
`output of refineTable over the SAME generated def (identical columns, kinds, primary key, ` +
|
|
298
|
+
`and locality).`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
178
303
|
/** Whether `table` is a {@link TableMeta.local local-only} table in `schema` (an unknown table
|
|
179
304
|
* reads as non-local). The single locality predicate the backends key off. */
|
|
180
305
|
export function isLocalTable<S extends ColsMap>(schema: Schema<S>, table: string): boolean {
|
package/src/ssr.ts
CHANGED
|
@@ -111,6 +111,27 @@ export class ServerStore<S extends ColsMap> {
|
|
|
111
111
|
dehydrate(): DehydratedState {
|
|
112
112
|
return this.store.dehydrate();
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Loader-phase convenience over {@link preload} + {@link dehydrate}: preload EVERY query (reads run
|
|
117
|
+
* concurrently) and return the dehydrated first-paint cache. Composition keeps this to one read per
|
|
118
|
+
* composed root query — no request waterfall (SSR-DESIGN.md §6.2).
|
|
119
|
+
*
|
|
120
|
+
* A failed read degrades to NO seed for that one query — `onError` fires and the browser's live
|
|
121
|
+
* engine fills it in right after hydration — rather than rejecting the whole batch (which would trip
|
|
122
|
+
* the route's error boundary). The seed is a first-paint optimization; the live `subscribe` is the
|
|
123
|
+
* source of truth, so a missing seed never affects correctness. Without `onError` a failed read is
|
|
124
|
+
* swallowed silently — pass one to log.
|
|
125
|
+
*/
|
|
126
|
+
async preloadAll(
|
|
127
|
+
queries: Array<Query<any, any, any>>,
|
|
128
|
+
opts: { onError?: (query: Query<any, any, any>, err: unknown) => void } = {},
|
|
129
|
+
): Promise<DehydratedState> {
|
|
130
|
+
await Promise.all(
|
|
131
|
+
queries.map((query) => this.preload(query).catch((err) => opts.onError?.(query, err))),
|
|
132
|
+
);
|
|
133
|
+
return this.dehydrate();
|
|
134
|
+
}
|
|
114
135
|
}
|
|
115
136
|
|
|
116
137
|
/** Construct a {@link ServerStore} — the one-shot REST Store for server-side rendering. */
|