@jarenjs/linq 0.56.0 → 0.67.0
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 +10 -0
- package/README.md +93 -2
- package/docs/APP-PEN.md +3 -3
- package/docs/CONTRACT-PEN.md +10 -6
- package/docs/DB-CLIENT.md +98 -19
- package/docs/FLOW-PEN.md +12 -5
- package/docs/FORMS-PEN.md +2 -2
- package/docs/JSLT-PEN.md +4 -4
- package/docs/LINQ-FORMAT.md +42 -34
- package/docs/MIGRATION-PEN.md +2 -2
- package/docs/MODEL-PEN.md +15 -6
- package/docs/QUERY-PEN.md +107 -19
- package/docs/SCHEMA-PEN.md +2 -2
- package/package.json +6 -6
- package/src/app/action.js +4 -8
- package/src/app/define.js +8 -13
- package/src/async.js +58 -10
- package/src/concurrency.js +40 -8
- package/src/contract/define.js +23 -10
- package/src/contract/index.js +5 -5
- package/src/contract/operation.js +10 -14
- package/src/db/handle.js +3 -0
- package/src/db/include.js +40 -5
- package/src/db/index.js +6 -0
- package/src/db/ledger.js +195 -0
- package/src/db/open.js +59 -11
- package/src/db/replication.js +20 -0
- package/src/errors.js +10 -1
- package/src/expression.js +30 -4
- package/src/federate.js +531 -0
- package/src/flow/dag.js +28 -14
- package/src/flow/fsm.js +6 -11
- package/src/index.js +1 -0
- package/src/jslt/rules.js +7 -12
- package/src/migration/define.js +9 -14
- package/src/migration/steps.js +5 -9
- package/src/model/collection.js +102 -0
- package/src/model/index.js +1 -1
- package/types/contract.d.ts +115 -18
- package/types/db.d.ts +189 -11
- package/types/index.d.ts +65 -0
- package/types/model.d.ts +34 -1
package/types/db.d.ts
CHANGED
|
@@ -13,16 +13,18 @@
|
|
|
13
13
|
* subpath has them; no other subpath of the package refers to them.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { AsyncSequence, AsyncExplanation, Expr, BoolExpr, OrderOptions } from './index.js';
|
|
16
|
+
import type { AsyncSequence, AsyncExplanation, Expr, ExprBase, BoolExpr, OrderOptions } from './index.js';
|
|
17
17
|
import type { ModelDocument, CollectionSpec, InferMeta } from './model.js';
|
|
18
18
|
import type {
|
|
19
19
|
EntityMeta, MetaMap, TypedEntitySet, TypedStore, TypedLoadSpec, Loaded,
|
|
20
20
|
} from '@jarenjs/db/typed';
|
|
21
21
|
import type {
|
|
22
22
|
Collection, ExecuteOptions, LiveOptions, LiveQuery, LoadExplanation, OpenStoreOptions,
|
|
23
|
-
|
|
23
|
+
SavepointController, SaveReport, StoreCapabilities, TransactionStore,
|
|
24
|
+
EntityCursorOptions, QueryCursor, LoadContinuation, Page,
|
|
24
25
|
} from '@jarenjs/db';
|
|
25
26
|
import type { JarenValidator } from '@jarenjs/validate';
|
|
27
|
+
import type { Runtime } from '@jarenjs/core/runtime';
|
|
26
28
|
|
|
27
29
|
// ————— open —————
|
|
28
30
|
|
|
@@ -110,6 +112,12 @@ export type IncludeSpec<E extends MetaMap<E>, M extends EntityMeta> =
|
|
|
110
112
|
readonly orderBy?: OrderKey<M['doc']> | readonly OrderKey<M['doc']>[];
|
|
111
113
|
readonly take?: number;
|
|
112
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;
|
|
113
121
|
readonly include?: Includes<E, M>;
|
|
114
122
|
};
|
|
115
123
|
|
|
@@ -118,9 +126,35 @@ export type Includes<E extends MetaMap<E>, M extends EntityMeta> = {
|
|
|
118
126
|
readonly [K in keyof M['relations'] & string]?: IncludeSpec<E, TargetMeta<E, M, K>>;
|
|
119
127
|
};
|
|
120
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
|
+
|
|
121
154
|
/** The graph over one entity set: an immutable builder of the `load`
|
|
122
|
-
* spec, typed by what it included. `S`
|
|
123
|
-
* specification `Loaded<>` reads
|
|
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. */
|
|
124
158
|
export interface Graph<E extends MetaMap<E>, M extends EntityMeta, S> {
|
|
125
159
|
/** Include one more relation member. (`NoInfer` keeps the spec's
|
|
126
160
|
* callbacks contextually typed while `I` is inferred from the literal —
|
|
@@ -130,14 +164,24 @@ export interface Graph<E extends MetaMap<E>, M extends EntityMeta, S> {
|
|
|
130
164
|
): Graph<E, M, S & { include: { [P in K]: I } }>;
|
|
131
165
|
/** Filter the root rows; consecutive calls conjoin. */
|
|
132
166
|
where(predicate: (it: Expr<M['doc']>) => BoolExpr | boolean): Graph<E, M, S>;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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]>>;
|
|
137
173
|
take(count: number): Graph<E, M, S>;
|
|
138
174
|
skip(count: number): Graph<E, M, S>;
|
|
139
|
-
/**
|
|
140
|
-
|
|
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>>>;
|
|
141
185
|
/** The include depth bound (§10.4, default 3). */
|
|
142
186
|
maxDepth(depth: number): Graph<E, M, S>;
|
|
143
187
|
/** The same graph, loaded without registering snapshots. */
|
|
@@ -148,6 +192,10 @@ export interface Graph<E extends MetaMap<E>, M extends EntityMeta, S> {
|
|
|
148
192
|
toJSON(): TypedLoadSpec<E, M>;
|
|
149
193
|
/** `load(spec)`: the store's one statement, typed by the includes. */
|
|
150
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>>;
|
|
151
199
|
/** `explainLoad(spec)`: the SQL, the includes, the pagination strategy. */
|
|
152
200
|
explain(): LoadExplanation;
|
|
153
201
|
}
|
|
@@ -161,6 +209,9 @@ export type EntityHandle<E extends MetaMap<E>, M extends EntityMeta> =
|
|
|
161
209
|
include<K extends keyof M['relations'] & string, const I extends IncludeSpec<E, TargetMeta<E, M, K>> = true>(
|
|
162
210
|
pick: (u: RelationPicker<M>) => Picked<K>, spec?: I | NoInfer<IncludeSpec<E, TargetMeta<E, M, K>>>,
|
|
163
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, {}>;
|
|
164
215
|
/** A live query over a chain of this set (the whole set when none
|
|
165
216
|
* is given), through the store's entity-root registration. */
|
|
166
217
|
live<T = M['doc']>(source?: AsyncSequence<T, any> | object, options?: LiveOptions): Promise<TypedLiveQuery<T>>;
|
|
@@ -175,6 +226,124 @@ export interface EntityClientMembers {
|
|
|
175
226
|
live<T>(source: AsyncSequence<T, any> | object, options?: LiveOptions): Promise<TypedLiveQuery<T>>;
|
|
176
227
|
}
|
|
177
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
|
+
|
|
178
347
|
/** The client: one frozen record of handles per declared name, the
|
|
179
348
|
* store beneath it, and the pass-throughs. */
|
|
180
349
|
export type Client<E extends MetaMap<E>, C = Record<string, unknown>> = {
|
|
@@ -183,6 +352,15 @@ export type Client<E extends MetaMap<E>, C = Record<string, unknown>> = {
|
|
|
183
352
|
readonly capabilities: StoreCapabilities;
|
|
184
353
|
readonly entities: { readonly [K in keyof E & string]: EntityHandle<E, E[K]> };
|
|
185
354
|
readonly collections: { readonly [K in keyof C & string]: CollectionHandle<C[K]> };
|
|
186
|
-
transaction
|
|
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>>;
|
|
187
358
|
close(options?: { graceMs?: number }): Promise<void>;
|
|
188
359
|
} & ([keyof E] extends [never] ? {} : EntityClientMembers);
|
|
360
|
+
|
|
361
|
+
/** Author a logical envelope using the DB's canonical format validator. */
|
|
362
|
+
export function defineReplication(header: Omit<import('@jarenjs/db').ReplicationEnvelope, '$replication' | 'operations'>): {
|
|
363
|
+
change(table: string, key: string, before: Record<string, unknown> | null, after: Record<string, unknown> | null): ReturnType<typeof defineReplication>;
|
|
364
|
+
toDocument(): import('@jarenjs/db').ReplicationEnvelope;
|
|
365
|
+
toJSON(): import('@jarenjs/db').ReplicationEnvelope;
|
|
366
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -475,6 +475,13 @@ export interface Provider<T = unknown> {
|
|
|
475
475
|
export interface AsyncProvider<T = unknown> {
|
|
476
476
|
readonly __item?: T;
|
|
477
477
|
execute(document: unknown, options: { externals: Record<string, unknown> }): unknown | Promise<unknown>;
|
|
478
|
+
/** The cursor protocol, optional: the same document as an item
|
|
479
|
+
* cursor, one item per pull, `return()` releasing whatever it holds.
|
|
480
|
+
* A `for await` over the chain hands its pushed document here when
|
|
481
|
+
* the provider offers it, so iteration never materialises what the
|
|
482
|
+
* provider can stream; a provider without it is run whole. */
|
|
483
|
+
cursor?(document: unknown, options: { externals: Record<string, unknown> }):
|
|
484
|
+
AsyncIterator<unknown> & { return(): Promise<unknown> };
|
|
478
485
|
readonly root?: string;
|
|
479
486
|
readonly roots?: readonly string[];
|
|
480
487
|
readonly scope?: unknown;
|
|
@@ -657,6 +664,13 @@ export interface MapAsyncOptions {
|
|
|
657
664
|
|
|
658
665
|
export interface AsyncExplanation {
|
|
659
666
|
barriers: { operator: string, reason: string }[];
|
|
667
|
+
/** What THIS surface does with the item stream: one item at a time, or
|
|
668
|
+
* a buffer at its first local barrier. Over a provider the pushed
|
|
669
|
+
* document's own class — a set residual, an external the database
|
|
670
|
+
* cannot bind — is the provider's `explain(document, { externals:
|
|
671
|
+
* bindings })` to report, and its cursor carries the same answer. */
|
|
672
|
+
streaming: 'row' | 'buffered';
|
|
673
|
+
barrier: { construct: string, reason: string } | null;
|
|
660
674
|
/** The relation hops the chain's callbacks navigated (as `Explanation`). */
|
|
661
675
|
hops: Hop[];
|
|
662
676
|
/** The values `params()` bound, by name (as `Explanation`). */
|
|
@@ -770,6 +784,57 @@ export function fromAsync<T>(
|
|
|
770
784
|
options?: LinqOptions,
|
|
771
785
|
): AsyncSequence<T, {}>;
|
|
772
786
|
|
|
787
|
+
/** One side of a federation, as `explain()` reports it (§12.1). */
|
|
788
|
+
export interface FederatedSide {
|
|
789
|
+
readonly source: string;
|
|
790
|
+
readonly root: string;
|
|
791
|
+
readonly estimatedRows: number | null;
|
|
792
|
+
/** The join key on this side, as the document spells it. */
|
|
793
|
+
readonly key: string;
|
|
794
|
+
/** The document this side's own source is asked. */
|
|
795
|
+
readonly document: unknown;
|
|
796
|
+
/** Whether this side is pulled row by row, or answered whole. */
|
|
797
|
+
readonly streaming: 'row' | 'buffered';
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** What a federated document will do, without doing any of it (§12.1). */
|
|
801
|
+
export interface FederationPlan {
|
|
802
|
+
readonly strategy: 'hash';
|
|
803
|
+
readonly budget: { readonly maxRows: number; readonly maxBytes: number };
|
|
804
|
+
readonly build: FederatedSide;
|
|
805
|
+
readonly probe: FederatedSide;
|
|
806
|
+
/** The join itself is the engine's, over the two reduced sides. */
|
|
807
|
+
readonly resident: { readonly document: unknown };
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/** One named source of a federation: an ordinary provider source whose
|
|
811
|
+
* root is `$.<name>[*]`, sharing one scope with its siblings — which is
|
|
812
|
+
* what admits the join the federation then executes. */
|
|
813
|
+
export interface FederatedSource<T = unknown> extends AsyncProvider<T> {
|
|
814
|
+
explain(document: unknown): FederationPlan;
|
|
815
|
+
readonly root: string;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/** The explicit cross-source boundary (§12.1).
|
|
819
|
+
*
|
|
820
|
+
* A query document reads one input, and an ordinary join across two
|
|
821
|
+
* unrelated sources stays `JL0005`. `federate()` is the one way to opt
|
|
822
|
+
* out of that, by naming the sources and the bounds together: each
|
|
823
|
+
* side's own filters and projection run at its source, the smaller side
|
|
824
|
+
* fills a bounded hash table, the other is probed against it, and the
|
|
825
|
+
* caller's own document decides over the two reduced sets. A side that
|
|
826
|
+
* reaches `maxRows` or `maxBytes` raises `JL2008` at the row that would
|
|
827
|
+
* have broken the bound. */
|
|
828
|
+
export function federate(spec: {
|
|
829
|
+
sources: Record<string, AsyncProvider | { provider: AsyncProvider; estimatedRows?: number }>;
|
|
830
|
+
maxRows: number;
|
|
831
|
+
maxBytes: number;
|
|
832
|
+
strategy?: 'hash';
|
|
833
|
+
}): {
|
|
834
|
+
source<T = unknown>(name: string): FederatedSource<T>;
|
|
835
|
+
readonly names: readonly string[];
|
|
836
|
+
};
|
|
837
|
+
|
|
773
838
|
/** The push→pull adapter for feed/end readers (§12). */
|
|
774
839
|
export function createPushQueue<T = unknown>(options?: { highWaterMark?: number }): {
|
|
775
840
|
feed(value: T): boolean;
|
package/types/model.d.ts
CHANGED
|
@@ -328,11 +328,44 @@ export type IndexPath<D> = ((doc: Expr<D>) => unknown) | string | readonly (((do
|
|
|
328
328
|
* a composite, or a JSONPath string; default name `by_<segments>`. */
|
|
329
329
|
export function index<D = unknown>(path: IndexPath<D>, options?: IndexOptions): IndexSpec<D>;
|
|
330
330
|
|
|
331
|
+
/** One node of an index expression: a member (a lambda or a JSONPath
|
|
332
|
+
* string), a JSON scalar, or a call to a function the HOST declares. */
|
|
333
|
+
export type ExpressionNode<D = unknown> =
|
|
334
|
+
| ((doc: Expr<D>) => unknown)
|
|
335
|
+
| string
|
|
336
|
+
| number
|
|
337
|
+
| boolean
|
|
338
|
+
| { member: ((doc: Expr<D>) => unknown) | string }
|
|
339
|
+
| { value: string | number | boolean }
|
|
340
|
+
| { call: string; args?: readonly ExpressionNode<D>[] };
|
|
341
|
+
|
|
342
|
+
/** One index over a COMPUTED value. */
|
|
343
|
+
export interface ExpressionIndexSpec<D = unknown> {
|
|
344
|
+
readonly __doc?: D;
|
|
345
|
+
readonly name: string;
|
|
346
|
+
readonly expression: unknown;
|
|
347
|
+
readonly unique?: boolean;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* An index over a computed value: a closed expression over declared
|
|
352
|
+
* members, JSON scalars and functions the host declares deterministic.
|
|
353
|
+
*
|
|
354
|
+
* The function is resolved where the declarations are —
|
|
355
|
+
* `openStore({ expressions })` — so a name this pen has never heard of
|
|
356
|
+
* is not an error here; a wrong arity and a missing declaration are
|
|
357
|
+
* `JD0004` at open, before any DDL. Default name `by_<call>_<members>`.
|
|
358
|
+
*/
|
|
359
|
+
export function expressionIndex<D = unknown>(
|
|
360
|
+
expression: ExpressionNode<D>,
|
|
361
|
+
options?: { name?: string; unique?: boolean },
|
|
362
|
+
): ExpressionIndexSpec<D>;
|
|
363
|
+
|
|
331
364
|
export interface CollectionOptions<D> {
|
|
332
365
|
/** An RFC 6901 pointer, a captured member path (`(d) => d.id` → `/id`), or `null` (the store allocates). */
|
|
333
366
|
key?: string | ((doc: Expr<D>) => unknown) | null;
|
|
334
367
|
identity?: 'caller' | 'uuid' | 'integer';
|
|
335
|
-
indexes?: readonly IndexSpec<D>[];
|
|
368
|
+
indexes?: readonly (IndexSpec<D> | ExpressionIndexSpec<D>)[];
|
|
336
369
|
renamedFrom?: string;
|
|
337
370
|
}
|
|
338
371
|
|