@jarenjs/linq 0.49.2 → 0.66.1
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/ARCHITECTURE.md +227 -0
- package/README.md +650 -17
- package/docs/APP-PEN.md +1143 -0
- package/docs/CONTRACT-PEN.md +1221 -0
- package/docs/DB-CLIENT.md +882 -0
- package/docs/FLOW-PEN.md +1033 -0
- package/docs/FORMS-PEN.md +940 -0
- package/docs/JSLT-PEN.md +955 -0
- package/docs/LINQ-FORMAT.md +778 -383
- package/docs/MIGRATION-PEN.md +781 -0
- package/docs/MODEL-PEN.md +1092 -0
- package/docs/QUERY-PEN.md +1724 -0
- package/docs/SCHEMA-PEN.md +1218 -0
- package/package.json +57 -4
- package/src/app/action.js +251 -0
- package/src/app/capture.js +63 -0
- package/src/app/define.js +255 -0
- package/src/app/index.js +20 -0
- package/src/app/patch.js +277 -0
- package/src/app/sub.js +106 -0
- package/src/async.js +377 -75
- package/src/capture-root.js +82 -0
- package/src/concurrency.js +48 -11
- package/src/contract/define.js +282 -0
- package/src/contract/http.js +247 -0
- package/src/contract/index.js +23 -0
- package/src/contract/operation.js +338 -0
- package/src/db/handle.js +89 -0
- package/src/db/include.js +351 -0
- package/src/db/index.js +24 -0
- package/src/db/ledger.js +195 -0
- package/src/db/live.js +43 -0
- package/src/db/membership.js +37 -0
- package/src/db/open.js +130 -0
- package/src/document.js +143 -13
- package/src/effect.js +65 -0
- package/src/errors.js +78 -6
- package/src/expression.js +463 -36
- package/src/federate.js +531 -0
- package/src/flow/capture.js +33 -0
- package/src/flow/dag.js +316 -0
- package/src/flow/fsm.js +323 -0
- package/src/flow/index.js +22 -0
- package/src/forms/index.js +43 -0
- package/src/forms/rules.js +170 -0
- package/src/forms/submit.js +177 -0
- package/src/index.js +5 -2
- package/src/jslt/body.js +226 -0
- package/src/jslt/index.js +18 -0
- package/src/jslt/rules.js +202 -0
- package/src/json-boundary.js +90 -0
- package/src/migration/define.js +318 -0
- package/src/migration/index.js +15 -0
- package/src/migration/steps.js +244 -0
- package/src/model/collection.js +273 -0
- package/src/model/define.js +125 -0
- package/src/model/entity.js +307 -0
- package/src/model/index.js +47 -0
- package/src/model/relation.js +85 -0
- package/src/provider.js +137 -20
- package/src/schema/brand.js +31 -0
- package/src/schema/builders.js +526 -0
- package/src/schema/check.js +29 -0
- package/src/schema/emit.js +394 -0
- package/src/schema/factories.js +239 -0
- package/src/schema/index.js +37 -0
- package/src/schema-of.js +24 -0
- package/src/sequence.js +233 -103
- package/src/sources.js +10 -3
- package/types/app.d.ts +293 -0
- package/types/contract.d.ts +468 -0
- package/types/db.d.ts +359 -0
- package/types/flow.d.ts +285 -0
- package/types/forms.d.ts +253 -0
- package/types/index.d.ts +296 -26
- package/types/jslt.d.ts +193 -0
- package/types/migration.d.ts +201 -0
- package/types/model.d.ts +526 -0
- package/types/schema.d.ts +494 -0
package/types/db.d.ts
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored declarations for `@jarenjs/linq/db` — the store's front
|
|
3
|
+
* door. `open(model, options)` answers a `Client` typed from the model
|
|
4
|
+
* pen's phantom (`InferMeta<>`); a JSON model is the honest wide map
|
|
5
|
+
* unless the caller names one (`open<EntityMetaMap>(json, options)`).
|
|
6
|
+
* Every entity handle is the store's typed entity set — its unit of
|
|
7
|
+
* work, its provider members — plus the chain start (every `AsyncSequence`
|
|
8
|
+
* member, over the handle), the graph builder (`include` emits a `load`
|
|
9
|
+
* spec, and the result is `Loaded<>` by what it included), membership
|
|
10
|
+
* (`link`/`unlink` over exactly the many-to-many members) and `live`.
|
|
11
|
+
* The types this file imports from `@jarenjs/db` and `@jarenjs/validate`
|
|
12
|
+
* resolve where the optional peers are installed — a consumer of this
|
|
13
|
+
* subpath has them; no other subpath of the package refers to them.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { AsyncSequence, AsyncExplanation, Expr, ExprBase, BoolExpr, OrderOptions } from './index.js';
|
|
17
|
+
import type { ModelDocument, CollectionSpec, InferMeta } from './model.js';
|
|
18
|
+
import type {
|
|
19
|
+
EntityMeta, MetaMap, TypedEntitySet, TypedStore, TypedLoadSpec, Loaded,
|
|
20
|
+
} from '@jarenjs/db/typed';
|
|
21
|
+
import type {
|
|
22
|
+
Collection, ExecuteOptions, LiveOptions, LiveQuery, LoadExplanation, OpenStoreOptions,
|
|
23
|
+
SavepointController, SaveReport, StoreCapabilities, TransactionStore,
|
|
24
|
+
EntityCursorOptions, QueryCursor, LoadContinuation, Page,
|
|
25
|
+
} from '@jarenjs/db';
|
|
26
|
+
import type { JarenValidator } from '@jarenjs/validate';
|
|
27
|
+
import type { Runtime } from '@jarenjs/core/runtime';
|
|
28
|
+
|
|
29
|
+
// ————— open —————
|
|
30
|
+
|
|
31
|
+
export interface OpenOptions extends OpenStoreOptions {
|
|
32
|
+
/** Wired as the store's `compileSchema`; `defaultValidator()` when
|
|
33
|
+
* absent; `null` opens the store unvalidated (`capabilities.validated
|
|
34
|
+
* === false`, the store's declared downgrade). An explicit
|
|
35
|
+
* `compileSchema` wins over all three. */
|
|
36
|
+
validator?: JarenValidator | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `new JarenValidator({ collectErrors: true })` with the string and
|
|
40
|
+
* date-time formats registered — the configuration the Zod-migration
|
|
41
|
+
* recipe reproduces, so `s.string().email()` asserts out of the box. */
|
|
42
|
+
export function defaultValidator(): JarenValidator;
|
|
43
|
+
|
|
44
|
+
/** The entity metadata a model implies: a pen model's `InferMeta<>`; a
|
|
45
|
+
* JSON literal is never inferred (the wide map). */
|
|
46
|
+
export type MetaOf<M> = '__entities' extends keyof M ? InferMeta<M> : Record<string, EntityMeta>;
|
|
47
|
+
|
|
48
|
+
/** The collection document shapes a pen model declares (a JSON model's
|
|
49
|
+
* are `unknown`). */
|
|
50
|
+
export type CollectionsOf<M> = M extends ModelDocument<any, infer C>
|
|
51
|
+
? { [K in keyof C]: C[K] extends CollectionSpec<infer D> ? D : unknown }
|
|
52
|
+
: Record<string, unknown>;
|
|
53
|
+
|
|
54
|
+
/** The client a model opens to, with its metadata proven to be a map. */
|
|
55
|
+
export type ClientOf<M> = MetaOf<M> extends infer E
|
|
56
|
+
? (E extends MetaMap<E> ? Client<E, CollectionsOf<M>> : never)
|
|
57
|
+
: never;
|
|
58
|
+
|
|
59
|
+
/** Open a store behind a client. A pen model types the client from its
|
|
60
|
+
* phantom; a JSON model with a named map (`open<EntityMetaMap>(json,
|
|
61
|
+
* options)`) types it from the map; a bare JSON model is the wide map. */
|
|
62
|
+
export function open<M extends { readonly $model: '0.1' }>(model: M, options: OpenOptions): Promise<ClientOf<M>>;
|
|
63
|
+
export function open<E extends MetaMap<E> = Record<string, EntityMeta>>(model: object, options: OpenOptions): Promise<Client<E>>;
|
|
64
|
+
|
|
65
|
+
// ————— the client —————
|
|
66
|
+
|
|
67
|
+
/** A live query whose maintained rows are typed by the chain's item. */
|
|
68
|
+
export type TypedLiveQuery<T> = Omit<LiveQuery, 'result'> & {
|
|
69
|
+
readonly result: { readonly rows: readonly T[] };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** The chain start: every `AsyncSequence` member, delegated to
|
|
73
|
+
* `fromAsync(handle)`; `explain()` without a document explains the empty
|
|
74
|
+
* chain (the handle's own `explain(document)` is the store's). */
|
|
75
|
+
export type ChainStart<T> = Omit<AsyncSequence<T, {}>, 'explain'> & {
|
|
76
|
+
explain(): AsyncExplanation;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** A collection handle: the collection, the chain start, and a `live`
|
|
80
|
+
* that takes a chain (its document and bound params) or a document. */
|
|
81
|
+
export type CollectionHandle<T> = Omit<Collection<T>, 'live' | 'explain'> & ChainStart<T> & {
|
|
82
|
+
explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
|
|
83
|
+
live<R = T>(source?: AsyncSequence<R, any> | object, options?: LiveOptions): Promise<TypedLiveQuery<R>>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type TargetMeta<E extends MetaMap<E>, M extends EntityMeta, K extends keyof M['relations']> =
|
|
87
|
+
E[M['relations'][K]['entity'] & keyof E];
|
|
88
|
+
|
|
89
|
+
/** What `(u) => u.posts` answers at the type level: the member's name. */
|
|
90
|
+
export type Picked<K> = { readonly __member: K };
|
|
91
|
+
/** The pick callback's argument: exactly the relation members. */
|
|
92
|
+
export type RelationPicker<M extends EntityMeta> = {
|
|
93
|
+
readonly [K in keyof M['relations'] & string]: Picked<K>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** An ordering key over a row: a key callback, or the key with its
|
|
97
|
+
* direction and `$empty`/`$collation` options. */
|
|
98
|
+
export type OrderKey<D> =
|
|
99
|
+
| ((it: Expr<D>) => unknown)
|
|
100
|
+
| { readonly key: (it: Expr<D>) => unknown; readonly desc?: boolean;
|
|
101
|
+
readonly empty?: 'least' | 'greatest'; readonly collation?: string };
|
|
102
|
+
|
|
103
|
+
/** One include's spec (MODEL-FORMAT §10.4): `true` loads the rows,
|
|
104
|
+
* `{ count: true }` the number, and an object the clauses the store's
|
|
105
|
+
* `load` reads — every callback over the TARGET row, nested includes
|
|
106
|
+
* over the target's relations. */
|
|
107
|
+
export type IncludeSpec<E extends MetaMap<E>, M extends EntityMeta> =
|
|
108
|
+
| true
|
|
109
|
+
| { readonly count: true }
|
|
110
|
+
| {
|
|
111
|
+
readonly where?: (it: Expr<M['doc']>) => BoolExpr | boolean;
|
|
112
|
+
readonly orderBy?: OrderKey<M['doc']> | readonly OrderKey<M['doc']>[];
|
|
113
|
+
readonly take?: number;
|
|
114
|
+
readonly skip?: number;
|
|
115
|
+
/** The per-root bounds (MODEL-FORMAT §10.4): rows of this relation
|
|
116
|
+
* per parent and serialised bytes per parent; crossing one is the
|
|
117
|
+
* store's `JD2073`, never a truncated graph. `Infinity` spells the
|
|
118
|
+
* unbounded case (emitted as `null`). */
|
|
119
|
+
readonly maxRows?: number;
|
|
120
|
+
readonly maxBytes?: number;
|
|
121
|
+
readonly include?: Includes<E, M>;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** A nested include record: relation member → its spec. */
|
|
125
|
+
export type Includes<E extends MetaMap<E>, M extends EntityMeta> = {
|
|
126
|
+
readonly [K in keyof M['relations'] & string]?: IncludeSpec<E, TargetMeta<E, M, K>>;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** The order-key VALUE types a graph declared, in order: `orderBy` starts
|
|
130
|
+
* the tuple, every `thenBy` appends to it. */
|
|
131
|
+
export type OrderOf<S> = S extends { order: infer O extends readonly unknown[] } ? O : [];
|
|
132
|
+
type WithOrder<S, O extends readonly unknown[]> = Omit<S, 'order'> & { order: O };
|
|
133
|
+
|
|
134
|
+
/** The continuation a page over this graph emits, and `after()` takes:
|
|
135
|
+
* the declared order-key values as a tuple whose shape follows
|
|
136
|
+
* `orderBy`/`thenBy` — a two-key ordering needs a two-value `keys` —
|
|
137
|
+
* plus the row's primary key, the tie-breaker the store appends, and
|
|
138
|
+
* the ordering's identity. Unsigned and structural (MODEL-FORMAT §10.5):
|
|
139
|
+
* signing, scoping and expiry are the host's. */
|
|
140
|
+
export type Continuation<S, M extends EntityMeta> = LoadContinuation & {
|
|
141
|
+
readonly keys: OrderOf<S>;
|
|
142
|
+
readonly key: M['key'];
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/** A page's options over this graph (the store's `PageOptions`, the
|
|
146
|
+
* continuation typed by the declared ordering). */
|
|
147
|
+
export interface GraphPageOptions<S, M extends EntityMeta> extends EntityCursorOptions {
|
|
148
|
+
limit?: number;
|
|
149
|
+
after?: Continuation<S, M>;
|
|
150
|
+
maxBytes?: number;
|
|
151
|
+
consistency?: 'live' | 'snapshot';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The graph over one entity set: an immutable builder of the `load`
|
|
155
|
+
* spec, typed by what it included and by what it ordered by. `S`
|
|
156
|
+
* accumulates the include specification `Loaded<>` reads and the
|
|
157
|
+
* `order` tuple `after()`/`page()` are typed by. */
|
|
158
|
+
export interface Graph<E extends MetaMap<E>, M extends EntityMeta, S> {
|
|
159
|
+
/** Include one more relation member. (`NoInfer` keeps the spec's
|
|
160
|
+
* callbacks contextually typed while `I` is inferred from the literal —
|
|
161
|
+
* without it TypeScript fixes `I` to its default before typing them.) */
|
|
162
|
+
include<K extends keyof M['relations'] & string, const I extends IncludeSpec<E, TargetMeta<E, M, K>> = true>(
|
|
163
|
+
pick: (u: RelationPicker<M>) => Picked<K>, spec?: I | NoInfer<IncludeSpec<E, TargetMeta<E, M, K>>>,
|
|
164
|
+
): Graph<E, M, S & { include: { [P in K]: I } }>;
|
|
165
|
+
/** Filter the root rows; consecutive calls conjoin. */
|
|
166
|
+
where(predicate: (it: Expr<M['doc']>) => BoolExpr | boolean): Graph<E, M, S>;
|
|
167
|
+
/** Start the ordering: the key's value type opens the `order` tuple. */
|
|
168
|
+
orderBy<V>(key: (it: Expr<M['doc']>) => ExprBase<V>, options?: OrderOptions): Graph<E, M, WithOrder<S, [V]>>;
|
|
169
|
+
orderByDescending<V>(key: (it: Expr<M['doc']>) => ExprBase<V>, options?: OrderOptions): Graph<E, M, WithOrder<S, [V]>>;
|
|
170
|
+
/** Extend the ordering: the key's value type is appended to the tuple. */
|
|
171
|
+
thenBy<V>(key: (it: Expr<M['doc']>) => ExprBase<V>, options?: OrderOptions): Graph<E, M, WithOrder<S, [...OrderOf<S>, V]>>;
|
|
172
|
+
thenByDescending<V>(key: (it: Expr<M['doc']>) => ExprBase<V>, options?: OrderOptions): Graph<E, M, WithOrder<S, [...OrderOf<S>, V]>>;
|
|
173
|
+
take(count: number): Graph<E, M, S>;
|
|
174
|
+
skip(count: number): Graph<E, M, S>;
|
|
175
|
+
/** Resume after a continuation (§10.5): the value a `page()` over this
|
|
176
|
+
* ordering emitted — its `keys` tuple follows the declared ordering,
|
|
177
|
+
* its `key` is the row's primary key. A bare key is not a continuation. */
|
|
178
|
+
after(cursor: Continuation<S, M>): Graph<E, M, S>;
|
|
179
|
+
/** One bounded page over the composite keyset: `{ items, continuation,
|
|
180
|
+
* hasMore, snapshot }`, never more than `limit` roots or `maxBytes`
|
|
181
|
+
* serialised bytes; `snapshot` is true only over an immutable ordering
|
|
182
|
+
* (the primary key), and `consistency: 'snapshot'` over any other is
|
|
183
|
+
* the store's `JD0036`. Untracked unless `tracking: true`. */
|
|
184
|
+
page(options?: GraphPageOptions<S, M>): Promise<Page<Loaded<E, M, S>, Continuation<S, M>>>;
|
|
185
|
+
/** The include depth bound (§10.4, default 3). */
|
|
186
|
+
maxDepth(depth: number): Graph<E, M, S>;
|
|
187
|
+
/** The same graph, loaded without registering snapshots. */
|
|
188
|
+
asNoTracking(): Graph<E, M, S>;
|
|
189
|
+
/** The emitted `load` spec — plain frozen JSON, a snapshot. */
|
|
190
|
+
toSpec(): TypedLoadSpec<E, M>;
|
|
191
|
+
/** The same document, so `JSON.stringify(graph)` is the spec. */
|
|
192
|
+
toJSON(): TypedLoadSpec<E, M>;
|
|
193
|
+
/** `load(spec)`: the store's one statement, typed by the includes. */
|
|
194
|
+
toArray(): Promise<Array<Loaded<E, M, S>>>;
|
|
195
|
+
/** `loadCursor(spec, options)`: one root graph per pull, its includes
|
|
196
|
+
* attached and bounded, from the same one statement; `return()`
|
|
197
|
+
* releases it. Untracked unless `tracking: true` is spelled per call. */
|
|
198
|
+
cursor(options?: EntityCursorOptions): QueryCursor<Loaded<E, M, S>>;
|
|
199
|
+
/** `explainLoad(spec)`: the SQL, the includes, the pagination strategy. */
|
|
200
|
+
explain(): LoadExplanation;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** An entity handle: the typed entity set, the chain start, and the
|
|
204
|
+
* client's own members. `link`/`unlink` are the typed set's — over
|
|
205
|
+
* exactly the many-to-many members. */
|
|
206
|
+
export type EntityHandle<E extends MetaMap<E>, M extends EntityMeta> =
|
|
207
|
+
TypedEntitySet<E, M> & ChainStart<M['doc']> & {
|
|
208
|
+
/** Open a graph: include one relation member with an optional spec. */
|
|
209
|
+
include<K extends keyof M['relations'] & string, const I extends IncludeSpec<E, TargetMeta<E, M, K>> = true>(
|
|
210
|
+
pick: (u: RelationPicker<M>) => Picked<K>, spec?: I | NoInfer<IncludeSpec<E, TargetMeta<E, M, K>>>,
|
|
211
|
+
): Graph<E, M, { include: { [P in K]: I } }>;
|
|
212
|
+
/** Open a graph with nothing included: the root clauses, the keyset
|
|
213
|
+
* continuation and the page over the rows alone. */
|
|
214
|
+
graph(): Graph<E, M, {}>;
|
|
215
|
+
/** A live query over a chain of this set (the whole set when none
|
|
216
|
+
* is given), through the store's entity-root registration. */
|
|
217
|
+
live<T = M['doc']>(source?: AsyncSequence<T, any> | object, options?: LiveOptions): Promise<TypedLiveQuery<T>>;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/** Present exactly when the model declares entities, as on the store. */
|
|
221
|
+
export interface EntityClientMembers {
|
|
222
|
+
/** The store's unit of work, flushed. */
|
|
223
|
+
saveChanges(): Promise<SaveReport>;
|
|
224
|
+
/** Register an entity-root chain (or document) as a live query: the
|
|
225
|
+
* store's re-run maintenance, declared; `JD0050` without capture. */
|
|
226
|
+
live<T>(source: AsyncSequence<T, any> | object, options?: LiveOptions): Promise<TypedLiveQuery<T>>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ————— the ledger —————
|
|
230
|
+
|
|
231
|
+
/** The record `createDbLedger` keeps per `(op, scope, key)`: exactly
|
|
232
|
+
* `idempotencyLedgerModel`'s (`@jarenjs/contract/ledger`), the
|
|
233
|
+
* `generation` the fence verifies included. */
|
|
234
|
+
export interface DbLedgerRecord {
|
|
235
|
+
id: string;
|
|
236
|
+
generation: string;
|
|
237
|
+
op: string;
|
|
238
|
+
scope: string;
|
|
239
|
+
key: string;
|
|
240
|
+
hash: string;
|
|
241
|
+
status: 'started' | 'committed' | 'failed';
|
|
242
|
+
response: unknown;
|
|
243
|
+
retryable: boolean | null;
|
|
244
|
+
createdAt: number;
|
|
245
|
+
updatedAt: number;
|
|
246
|
+
expiresAt: number;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The ref a `new` claim hands back: the record's id and the generation
|
|
250
|
+
* the claim minted — portable across processes, stale once the key
|
|
251
|
+
* expires or is reclaimed. */
|
|
252
|
+
export interface DbLedgerRef {
|
|
253
|
+
readonly id: string;
|
|
254
|
+
readonly generation: string;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export type DbClaimResult =
|
|
258
|
+
| { state: 'new'; ref: DbLedgerRef }
|
|
259
|
+
| { state: 'replay'; response: unknown }
|
|
260
|
+
| { state: 'in-progress' }
|
|
261
|
+
| { state: 'mismatch' };
|
|
262
|
+
|
|
263
|
+
export interface DbLedgerOptions {
|
|
264
|
+
/** The declared collection; `'ledger'` (the model's) by default. */
|
|
265
|
+
collection?: string;
|
|
266
|
+
/** The retention of a key; 86,400,000 ms by default. */
|
|
267
|
+
ttlMs?: number;
|
|
268
|
+
/** The host's runtime record: its `now` is the clock, its `uuid`
|
|
269
|
+
* mints every generation. */
|
|
270
|
+
runtime?: Partial<Runtime>;
|
|
271
|
+
/** The clock; wins over the runtime's. Given neither, the ledger
|
|
272
|
+
* follows the instants the binding passes. */
|
|
273
|
+
now?: () => number;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** The ledger the http binding calls, over the store: structurally the
|
|
277
|
+
* contract package's `Ledger`, every method asynchronous. A `commit` or
|
|
278
|
+
* `fail` whose ref settles no started record rejects `JL2007`. */
|
|
279
|
+
export interface DbLedger {
|
|
280
|
+
claim(claim: { op: string; scope: string; key: string; hash: string; now?: number }): Promise<DbClaimResult>;
|
|
281
|
+
commit(ref: unknown, response: unknown, now?: number): Promise<void>;
|
|
282
|
+
fail(ref: unknown, retryable: boolean, response?: unknown, now?: number): Promise<void>;
|
|
283
|
+
lookup(key: { op: string; scope: string; key: string; now?: number }): Promise<DbLedgerRecord | null>;
|
|
284
|
+
/** Drop every expired record; answers how many. */
|
|
285
|
+
sweep(now?: number): Promise<number>;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** What `createDbLedger` needs of a client: the declared collections
|
|
289
|
+
* and a transaction — the root client (claims take the write lock up
|
|
290
|
+
* front, `mode: 'immediate'`) or the one a transaction callback
|
|
291
|
+
* received (claims and settlements nest in that transaction). */
|
|
292
|
+
export type LedgerClient =
|
|
293
|
+
| Pick<Client<any, any>, 'collections' | 'transaction' | 'close'>
|
|
294
|
+
| Pick<TransactionClientOf<any, any>, 'collections' | 'transaction'>;
|
|
295
|
+
|
|
296
|
+
/** The contract ledger over a declared collection of the client's
|
|
297
|
+
* store — no import of `@jarenjs/contract`, no driver, the client's
|
|
298
|
+
* own surface only (DB-CLIENT.md §2.6). A `TypeError` names a client
|
|
299
|
+
* that is not one, a collection the model does not declare, a bad
|
|
300
|
+
* `ttlMs` or `now`. */
|
|
301
|
+
export function createDbLedger(client: LedgerClient, options?: DbLedgerOptions): DbLedger;
|
|
302
|
+
|
|
303
|
+
/** How a transaction relates to the client's unit of work: `'own'` (the
|
|
304
|
+
* default) gives the callback a tracker of its own, so two handlers on
|
|
305
|
+
* one client hold two records for the same entity key; `'shared'` opts
|
|
306
|
+
* back into the client's, for a caller who staged changes outside the
|
|
307
|
+
* transaction and means to save them inside it. */
|
|
308
|
+
export interface TransactionOptions {
|
|
309
|
+
readonly unitOfWork?: 'own' | 'shared';
|
|
310
|
+
/** `'immediate'` takes the write lock up front (`BEGIN IMMEDIATE`),
|
|
311
|
+
* so a body that reads before it writes never meets the read→write
|
|
312
|
+
* upgrade busy the handler cannot retry; `'deferred'` (the default)
|
|
313
|
+
* is the savepoint as always. A nested `tx.transaction()` is a
|
|
314
|
+
* savepoint whichever mode the root chose. */
|
|
315
|
+
readonly mode?: 'deferred' | 'immediate';
|
|
316
|
+
/** Abandons the call while it is still QUEUED: the callback never
|
|
317
|
+
* runs and no statement is issued (`JD2064`). */
|
|
318
|
+
readonly signal?: AbortSignal;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The client a transaction callback receives: the same handles, the
|
|
323
|
+
* same inference, over the store that is INSIDE the transaction.
|
|
324
|
+
*
|
|
325
|
+
* `tx.entities.X` and `tx.collections.Y` run as the transaction's owner
|
|
326
|
+
* and `tx.transaction(...)` nests, while the outer client's handles are
|
|
327
|
+
* an unrelated caller — one awaited from in here waits for the commit it
|
|
328
|
+
* is part of, which `JD0012` names rather than hangs on.
|
|
329
|
+
*/
|
|
330
|
+
export type TransactionClientOf<E extends MetaMap<E>, C = Record<string, unknown>> = {
|
|
331
|
+
/** The scope-bound store — the escape hatch, still inside. It carries
|
|
332
|
+
* no `close`: a transaction never owns the connection's lifetime. */
|
|
333
|
+
readonly store: TransactionStore;
|
|
334
|
+
readonly capabilities: StoreCapabilities;
|
|
335
|
+
readonly entities: { readonly [K in keyof E & string]: EntityHandle<E, E[K]> };
|
|
336
|
+
readonly collections: { readonly [K in keyof C & string]: CollectionHandle<C[K]> };
|
|
337
|
+
/** Nest through this transaction's savepoint. */
|
|
338
|
+
transaction<R>(fn: (tx: TransactionClientOf<E, C>) => R | Promise<R>): Promise<Awaited<R>>;
|
|
339
|
+
/** Named partial rollback (MODEL-FORMAT §5.2) — the store's
|
|
340
|
+
* `tx.savepoints`, forwarded unchanged: create, roll back to and
|
|
341
|
+
* release a checkpoint by label without a sentinel exception. Only a
|
|
342
|
+
* live transaction has it; the root client deliberately has no twin,
|
|
343
|
+
* and a handle kept past its callback is `JD2070`. */
|
|
344
|
+
readonly savepoints: SavepointController;
|
|
345
|
+
} & ([keyof E] extends [never] ? {} : EntityClientMembers);
|
|
346
|
+
|
|
347
|
+
/** The client: one frozen record of handles per declared name, the
|
|
348
|
+
* store beneath it, and the pass-throughs. */
|
|
349
|
+
export type Client<E extends MetaMap<E>, C = Record<string, unknown>> = {
|
|
350
|
+
/** The store itself — the escape hatch, typed. */
|
|
351
|
+
readonly store: TypedStore<E>;
|
|
352
|
+
readonly capabilities: StoreCapabilities;
|
|
353
|
+
readonly entities: { readonly [K in keyof E & string]: EntityHandle<E, E[K]> };
|
|
354
|
+
readonly collections: { readonly [K in keyof C & string]: CollectionHandle<C[K]> };
|
|
355
|
+
/** A transaction, with a typed client of its own. */
|
|
356
|
+
transaction<R>(fn: (tx: TransactionClientOf<E, C>) => R | Promise<R>,
|
|
357
|
+
options?: TransactionOptions): Promise<Awaited<R>>;
|
|
358
|
+
close(options?: { graceMs?: number }): Promise<void>;
|
|
359
|
+
} & ([keyof E] extends [never] ? {} : EntityClientMembers);
|
package/types/flow.d.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-authored declarations for `@jarenjs/linq/flow` — the flow pen's
|
|
3
|
+
* type contract, kept to the same line as `index.d.ts`: the common path
|
|
4
|
+
* is precisely typed, the exotic path is honestly `unknown`, nothing is
|
|
5
|
+
* ever a WRONG type.
|
|
6
|
+
*
|
|
7
|
+
* State ids, event names and node ids are LITERAL types read from the
|
|
8
|
+
* declarations themselves, so `to('nope')` and `edge('nope', 'out')` do
|
|
9
|
+
* not compile — before they are `JL0102`, and long before the engine's
|
|
10
|
+
* `JF0006`/`JF0013`. A transition that never named its target does not
|
|
11
|
+
* compile either: `on()` answers a builder that has no `__to` phantom
|
|
12
|
+
* until `.to()` gives it one.
|
|
13
|
+
*
|
|
14
|
+
* The honest limits, both of them the same limit TypeScript has (a
|
|
15
|
+
* function's type arguments are all-or-none, so a partially inferred
|
|
16
|
+
* `defineFsm<Ctx>` cannot exist):
|
|
17
|
+
*
|
|
18
|
+
* - a guard's `s.context` is typed by ANNOTATION — `.when((s: Scope<Cart>)
|
|
19
|
+
* => …)` — because `on()` is evaluated before `defineFsm()` sees the
|
|
20
|
+
* `context` builder. What `defineFsm({ context })` types is the
|
|
21
|
+
* MACHINE (`ContextOf<typeof machine>`), which is what a host passing
|
|
22
|
+
* `step(state, event, { context })` needs. `s.payload` needs no
|
|
23
|
+
* annotation: `on(from, event, { payload })` declares it on the same
|
|
24
|
+
* call.
|
|
25
|
+
* - a task name is checked against a registry by annotating the node
|
|
26
|
+
* map — `{ … } satisfies NodesFor<Tasks>` — and `typedTasks(dag,
|
|
27
|
+
* registry)` checks the other direction (one handler per declared
|
|
28
|
+
* task name), so the registry `compileDag` resolves and the document
|
|
29
|
+
* agree at compile time. The runtime check stays `JF0018`.
|
|
30
|
+
*
|
|
31
|
+
* Every claim here has a runtime twin in `test/linq/flow-pen.test.js`
|
|
32
|
+
* and a compile-level pin in `test/consumer/linq-flow.ts`; FLOW-PEN.md
|
|
33
|
+
* is the normative mapping table.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { ExprBase, MemberExpr, StringExpr, UnknownExpr } from './index.js';
|
|
37
|
+
import type { BuilderLike, Infer, Json } from './schema.js';
|
|
38
|
+
|
|
39
|
+
// ——— the evaluation scopes ———
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* FLOW-FORMAT §3's step scope, as a guard or an effect's `with` reads
|
|
43
|
+
* it. `Ctx` and `Payload` are the honest top until they are annotated
|
|
44
|
+
* (`Scope<Cart>`) or declared (`on(from, event, { payload })`).
|
|
45
|
+
*/
|
|
46
|
+
export interface Scope<Ctx = unknown, Payload = unknown> {
|
|
47
|
+
/** The current state id — the transition's `from`. */
|
|
48
|
+
readonly state: StringExpr;
|
|
49
|
+
/** The event name being stepped. */
|
|
50
|
+
readonly event: StringExpr;
|
|
51
|
+
/** The caller's payload, or `null` when absent. */
|
|
52
|
+
readonly payload: MemberExpr<Payload>;
|
|
53
|
+
/** The host's extended data, or `null` when absent. */
|
|
54
|
+
readonly context: MemberExpr<Ctx>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What a captured member may answer: an expression, or plain JSON. */
|
|
58
|
+
export type Captured = unknown;
|
|
59
|
+
|
|
60
|
+
/** A query-valued member: a callback captured over its scope, or a
|
|
61
|
+
* query document written by hand. The scope type is a TYPE PARAMETER
|
|
62
|
+
* with an honest-top default, so annotating the callback's argument
|
|
63
|
+
* (`(s: Scope<Cart>) => …`) is what types it — exactly as `body()`'s
|
|
64
|
+
* value is typed in the JSLT pen. */
|
|
65
|
+
export type QueryMember<S> = ((scope: S) => Captured) | Json;
|
|
66
|
+
|
|
67
|
+
// ——— the fsm surface ———
|
|
68
|
+
|
|
69
|
+
/** One `{ run, with? }` effect descriptor (§2). The engine never runs
|
|
70
|
+
* it: a fired transition RETURNS descriptors as data. */
|
|
71
|
+
export interface EffectDeclaration<Run extends string = string> {
|
|
72
|
+
readonly run: Run;
|
|
73
|
+
readonly with?: Json;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** One state declaration (§2), carrying its id as a literal. */
|
|
77
|
+
export interface StateDeclaration<Id extends string = string> {
|
|
78
|
+
readonly id: Id;
|
|
79
|
+
readonly entry?: readonly EffectDeclaration[];
|
|
80
|
+
readonly exit?: readonly EffectDeclaration[];
|
|
81
|
+
readonly final?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The ids a `states` member declares: a bare string is the format's
|
|
85
|
+
* own shorthand for `{ id }`. */
|
|
86
|
+
export type StateIdOf<S> = S extends string ? S
|
|
87
|
+
: S extends StateDeclaration<infer Id> ? Id : never;
|
|
88
|
+
|
|
89
|
+
/** A transition whose target is still open: `on()` answers one, and it
|
|
90
|
+
* is NOT a transition until `.to()` names a state. */
|
|
91
|
+
export interface PendingTransition<From extends string, E extends string, P> {
|
|
92
|
+
/** The guard (§3), captured over the step scope. A plain STRING is
|
|
93
|
+
* `JL0102`: §3 makes a non-`$` literal vacuously true. */
|
|
94
|
+
when<S extends Scope<any, any> = Scope<unknown, P>>(
|
|
95
|
+
guard: (scope: S) => Captured): PendingTransition<From, E, P>;
|
|
96
|
+
when(guard: Json): PendingTransition<From, E, P>;
|
|
97
|
+
/** The state this transition enters. */
|
|
98
|
+
to<To extends string>(state: To): Transition<From, To, E, P>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A complete transition, carrying its ends and event as literals. */
|
|
102
|
+
export interface Transition<From extends string, To extends string, E extends string, P>
|
|
103
|
+
extends PendingTransition<From, E, P> {
|
|
104
|
+
readonly __from: From;
|
|
105
|
+
readonly __to: To;
|
|
106
|
+
/** The event this transition answers; `never` for a wildcard. */
|
|
107
|
+
readonly __event: E;
|
|
108
|
+
when<S extends Scope<any, any> = Scope<unknown, P>>(
|
|
109
|
+
guard: (scope: S) => Captured): Transition<From, To, E, P>;
|
|
110
|
+
when(guard: Json): Transition<From, To, E, P>;
|
|
111
|
+
/** The transition's own effects, fired between exit and entry (§4). */
|
|
112
|
+
effects(effects: readonly EffectDeclaration[]): Transition<From, To, E, P>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Any complete transition. */
|
|
116
|
+
export type AnyTransition = Transition<string, string, string, any>;
|
|
117
|
+
|
|
118
|
+
/** The declared states of a transition list, and the events it names. */
|
|
119
|
+
export type TransitionFrom<T> = T extends Transition<infer F, any, any, any> ? F : never;
|
|
120
|
+
export type TransitionTo<T> = T extends Transition<any, infer To, any, any> ? To : never;
|
|
121
|
+
export type TransitionEvent<T> = T extends Transition<any, any, infer E, any> ? E : never;
|
|
122
|
+
|
|
123
|
+
/** A `jaren-fsm` 0.1 document as the pen writes it, carrying the
|
|
124
|
+
* machine's states, events and context type as phantoms. */
|
|
125
|
+
export interface Fsm<
|
|
126
|
+
States extends string = string, Events extends string = string, Ctx = unknown,
|
|
127
|
+
> {
|
|
128
|
+
/** Phantoms: declared, never present at runtime. */
|
|
129
|
+
readonly __states: States;
|
|
130
|
+
readonly __events: Events;
|
|
131
|
+
readonly __context: Ctx;
|
|
132
|
+
readonly $fsm: '0.1';
|
|
133
|
+
readonly initial: States | null;
|
|
134
|
+
readonly states: readonly (States | StateDeclaration<States>)[];
|
|
135
|
+
readonly transitions: readonly {
|
|
136
|
+
readonly from: States; readonly event?: Events; readonly guard?: Json;
|
|
137
|
+
readonly to: States; readonly effects?: readonly EffectDeclaration[];
|
|
138
|
+
}[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The state ids a machine declares. */
|
|
142
|
+
export type StatesOf<F> = F extends Fsm<infer S, any, any> ? S : never;
|
|
143
|
+
/** The named events a machine answers (wildcards excluded). */
|
|
144
|
+
export type EventsOf<F> = F extends Fsm<any, infer E, any> ? E : never;
|
|
145
|
+
/** The host data a machine's guards read — what `step(…, { context })` takes. */
|
|
146
|
+
export type ContextOf<F> = F extends Fsm<any, any, infer C> ? C : never;
|
|
147
|
+
|
|
148
|
+
/** One effect descriptor: the handler name, and the props it resolves.
|
|
149
|
+
* The scope is the honest top until it is annotated (`(s: Scope<Cart,
|
|
150
|
+
* Approval>) => …`): an `effect()` is written before the transition
|
|
151
|
+
* that carries it exists. */
|
|
152
|
+
export function effect<
|
|
153
|
+
const Run extends string, S extends Scope<any, any> = Scope<unknown, unknown>,
|
|
154
|
+
>(run: Run, props?: ((scope: S) => Captured) | Json): EffectDeclaration<Run>;
|
|
155
|
+
|
|
156
|
+
/** One state declaration; a bare string in `states` is the shorthand. */
|
|
157
|
+
export function state<const Id extends string>(
|
|
158
|
+
id: Id,
|
|
159
|
+
options?: {
|
|
160
|
+
readonly entry?: readonly EffectDeclaration[];
|
|
161
|
+
readonly exit?: readonly EffectDeclaration[];
|
|
162
|
+
readonly final?: boolean;
|
|
163
|
+
},
|
|
164
|
+
): StateDeclaration<Id>;
|
|
165
|
+
|
|
166
|
+
/** One transition, left open until `.to()` names its target. Document
|
|
167
|
+
* order is the whole priority scheme (§4). */
|
|
168
|
+
export function on<const From extends string, const E extends string>(
|
|
169
|
+
from: From, event: E): PendingTransition<From, E, unknown>;
|
|
170
|
+
/** A transition whose event declares its payload SCHEMA — a type only:
|
|
171
|
+
* the format carries no payload schema and the pen emits nothing for it. */
|
|
172
|
+
export function on<const From extends string, const E extends string, B extends BuilderLike<any, any, any>>(
|
|
173
|
+
from: From, event: E, options: { readonly payload: B }):
|
|
174
|
+
PendingTransition<From, E, Infer<B>>;
|
|
175
|
+
/** The wildcard: a transition answering any event (§2). */
|
|
176
|
+
export function on<const From extends string>(
|
|
177
|
+
from: From, event?: null): PendingTransition<From, never, unknown>;
|
|
178
|
+
|
|
179
|
+
/** Write a `jaren-fsm` 0.1 document (§2). */
|
|
180
|
+
export function defineFsm<
|
|
181
|
+
const St extends readonly (string | StateDeclaration<string>)[],
|
|
182
|
+
const T extends readonly Transition<StateIdOf<St[number]>, StateIdOf<St[number]>, string, any>[],
|
|
183
|
+
const B extends BuilderLike<any, any, any> | undefined = undefined,
|
|
184
|
+
>(spec: {
|
|
185
|
+
/** A declared state id, or null when the document chooses none. */
|
|
186
|
+
readonly initial: StateIdOf<St[number]> | null;
|
|
187
|
+
readonly states: St;
|
|
188
|
+
readonly transitions: T;
|
|
189
|
+
/** The host data a guard reads (§3) — a TYPE only, never emitted. */
|
|
190
|
+
readonly context?: B;
|
|
191
|
+
}): Fsm<
|
|
192
|
+
StateIdOf<St[number]>,
|
|
193
|
+
Extract<TransitionEvent<T[number]>, string>,
|
|
194
|
+
B extends BuilderLike<any, any, any> ? Infer<B> : unknown>;
|
|
195
|
+
|
|
196
|
+
// ——— the dag surface ———
|
|
197
|
+
|
|
198
|
+
/** The closed kind vocabulary §6 fixes. */
|
|
199
|
+
export type NodeKind = 'input' | 'output' | 'const' | 'query' | 'jslt' | 'task';
|
|
200
|
+
|
|
201
|
+
/** One node declaration, carrying its kind and — for a task — the
|
|
202
|
+
* handler name it will need, as literals. */
|
|
203
|
+
export interface NodeDeclaration<K extends NodeKind = NodeKind, Run extends string = never> {
|
|
204
|
+
readonly __kind: K;
|
|
205
|
+
readonly __run: Run;
|
|
206
|
+
/** Declare this node's value durable (§7.6). */
|
|
207
|
+
checkpoint(): NodeDeclaration<K, Run>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** A node of any kind. */
|
|
211
|
+
export type AnyNode = NodeDeclaration<NodeKind, any>;
|
|
212
|
+
|
|
213
|
+
/** A node map whose task nodes name handlers a registry provides:
|
|
214
|
+
* `{ … } satisfies NodesFor<Tasks>` makes `task('nope')` a compile error. */
|
|
215
|
+
export type NodesFor<Tasks> = Readonly<Record<string,
|
|
216
|
+
| NodeDeclaration<Exclude<NodeKind, 'task'>, never>
|
|
217
|
+
| NodeDeclaration<'task', Extract<keyof Tasks, string>>>>;
|
|
218
|
+
|
|
219
|
+
/** One edge (§6), carrying its two ends as literals. */
|
|
220
|
+
export interface EdgeDeclaration<From extends string = string, To extends string = string> {
|
|
221
|
+
readonly __from: From;
|
|
222
|
+
readonly __to: To;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** A `jaren-dag` 0.1 document as the pen writes it, carrying its node
|
|
226
|
+
* ids and the task names it declares as phantoms. */
|
|
227
|
+
export interface Dag<Ids extends string = string, Tasks extends string = never> {
|
|
228
|
+
/** Phantoms: declared, never present at runtime. */
|
|
229
|
+
readonly __nodes: Ids;
|
|
230
|
+
readonly __tasks: Tasks;
|
|
231
|
+
readonly $dag: '0.1';
|
|
232
|
+
readonly nodes: { readonly [id: string]: { readonly kind: NodeKind } & Json };
|
|
233
|
+
readonly edges: readonly {
|
|
234
|
+
readonly from: Ids; readonly to: Ids; readonly port?: string; readonly select?: Json;
|
|
235
|
+
}[];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The node ids a graph declares. */
|
|
239
|
+
export type NodesOf<D> = D extends Dag<infer Ids, any> ? Ids : never;
|
|
240
|
+
/** The handler names a graph's task nodes require. */
|
|
241
|
+
export type TasksOf<D> = D extends Dag<any, infer T> ? T : never;
|
|
242
|
+
|
|
243
|
+
/** A dag task handler (§7.2): `handler({ with, input }, signal)`. */
|
|
244
|
+
export type TaskHandler = (
|
|
245
|
+
props: { with: unknown; input: unknown }, signal: AbortSignal) => unknown;
|
|
246
|
+
|
|
247
|
+
/** The `input` node: the run's input value; no inbound edge. */
|
|
248
|
+
export function input(): NodeDeclaration<'input'>;
|
|
249
|
+
/** The `output` node: its input scope IS the run's result. */
|
|
250
|
+
export function output(): NodeDeclaration<'output'>;
|
|
251
|
+
/** A `const` node: its literal value, delivered by reference. */
|
|
252
|
+
export function constant(value: Json): NodeDeclaration<'const'>;
|
|
253
|
+
/** A `query` node: a Jaren JSON Query over the node's input scope (§6.1). */
|
|
254
|
+
export function query<V extends ExprBase<unknown> = UnknownExpr>(
|
|
255
|
+
document: ((value: V) => Captured) | Json): NodeDeclaration<'query'>;
|
|
256
|
+
/** A `jslt` node: a stylesheet over the node's input scope. */
|
|
257
|
+
export function jslt(document: Json | { readonly rules: readonly unknown[] }):
|
|
258
|
+
NodeDeclaration<'jslt'>;
|
|
259
|
+
/** A `task` node: a registered async handler, named here and resolved
|
|
260
|
+
* by the registry `compileDag` is given (`JF0018` when it cannot). */
|
|
261
|
+
export function task<const Run extends string, V extends ExprBase<unknown> = UnknownExpr>(
|
|
262
|
+
run: Run, props?: ((value: V) => Captured) | Json): NodeDeclaration<'task', Run>;
|
|
263
|
+
|
|
264
|
+
/** One edge (§6). `select` is applied to the source value before delivery. */
|
|
265
|
+
export function edge<
|
|
266
|
+
const From extends string, const To extends string,
|
|
267
|
+
V extends ExprBase<unknown> = UnknownExpr,
|
|
268
|
+
>(
|
|
269
|
+
from: From, to: To,
|
|
270
|
+
options?: { readonly port?: string; readonly select?: ((value: V) => Captured) | Json },
|
|
271
|
+
): EdgeDeclaration<From, To>;
|
|
272
|
+
|
|
273
|
+
/** Write a `jaren-dag` 0.1 document (§6). */
|
|
274
|
+
export function defineDag<
|
|
275
|
+
const N extends Readonly<Record<string, AnyNode>>,
|
|
276
|
+
const E extends readonly EdgeDeclaration<Extract<keyof N, string>, Extract<keyof N, string>>[],
|
|
277
|
+
>(spec: { readonly nodes: N; readonly edges: E }): Dag<
|
|
278
|
+
Extract<keyof N, string>,
|
|
279
|
+
N[keyof N] extends never ? never : Extract<
|
|
280
|
+
{ [K in keyof N]: N[K] extends NodeDeclaration<'task', infer R> ? R : never }[keyof N], string>>;
|
|
281
|
+
|
|
282
|
+
/** Bind a task registry to the graph it serves. Identity at runtime; a
|
|
283
|
+
* missing or misspelled handler name is a type error. */
|
|
284
|
+
export function typedTasks<D extends Dag<any, any>, T extends { [K in TasksOf<D>]: TaskHandler }>(
|
|
285
|
+
dag: D, tasks: T): T;
|