@atscript/db-memory 0.1.113
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/README.md +111 -0
- package/dist/index.cjs +978 -0
- package/dist/index.d.cts +484 -0
- package/dist/index.d.mts +484 -0
- package/dist/index.mjs +972 -0
- package/package.json +55 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TFieldOps } from "@atscript/db";
|
|
2
|
+
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/memory-adapter.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Provider (read-through) backing closure. Recomputes and returns the table's
|
|
7
|
+
* rows on demand — a fresh snapshot every call (sync or async). See
|
|
8
|
+
* {@link MemoryAdapter.setProvider} / {@link setMemoryProvider}.
|
|
9
|
+
*/
|
|
10
|
+
type MemoryProviderFn = () => Array<Record<string, unknown>> | Promise<Array<Record<string, unknown>>>;
|
|
11
|
+
/**
|
|
12
|
+
* In-memory {@link BaseDbAdapter} implementation.
|
|
13
|
+
*
|
|
14
|
+
* Runs in one of two modes:
|
|
15
|
+
* - STORED mode (default): storage is a plain `Map` living on the adapter
|
|
16
|
+
* instance — adapter instances are 1:1 with a readable (table/view), so the
|
|
17
|
+
* Map is this table's whole store. Documents are kept in their nested PHYSICAL
|
|
18
|
+
* shape (no flattening), which is why {@link supportsNestedObjects} is `true`.
|
|
19
|
+
* Full CRUD surface: inserts, reads, update / replace / delete with
|
|
20
|
+
* optimistic-concurrency CAS.
|
|
21
|
+
* - PROVIDER (read-through) mode: enabled via {@link setProvider} /
|
|
22
|
+
* {@link setMemoryProvider}. Reads are served from a runtime closure
|
|
23
|
+
* recomputed per request (e.g. a Redis/job-manager snapshot) so a
|
|
24
|
+
* runtime-owned entity with NO database can be observed as a READ-ONLY
|
|
25
|
+
* atscript table; all writes are rejected (see {@link _assertWritable}).
|
|
26
|
+
*/
|
|
27
|
+
declare class MemoryAdapter extends BaseDbAdapter {
|
|
28
|
+
/**
|
|
29
|
+
* The table's store, keyed by {@link pkKey}. Values are the stored rows in
|
|
30
|
+
* nested physical shape. An instance field — no `ensureTable` DDL needed.
|
|
31
|
+
*/
|
|
32
|
+
private rows;
|
|
33
|
+
/** Unique indexes recorded by {@link syncIndexes}. Enforced on insert. */
|
|
34
|
+
private uniqueIndexes;
|
|
35
|
+
/** Memoized physical PK field names — stable for the adapter's lifetime. */
|
|
36
|
+
private _pkFieldsCache?;
|
|
37
|
+
/**
|
|
38
|
+
* Memoized map of PHYSICAL field name → optional `start` for every field
|
|
39
|
+
* carrying `@db.default.increment`. Stable per adapter (see
|
|
40
|
+
* {@link _incrementFields}).
|
|
41
|
+
*/
|
|
42
|
+
private _incrementFieldsCache?;
|
|
43
|
+
/**
|
|
44
|
+
* Running per-field auto-increment counters (PHYSICAL name → last value
|
|
45
|
+
* handed out). Lives on the adapter INSTANCE, so it resets whenever a new
|
|
46
|
+
* DbSpace/adapter is built — correct for an in-memory store (parity with
|
|
47
|
+
* SQLite `:memory:`, whose sequence also restarts with a fresh DB). Never
|
|
48
|
+
* persisted.
|
|
49
|
+
*/
|
|
50
|
+
private _incrementCounters;
|
|
51
|
+
/**
|
|
52
|
+
* Provider (read-through) backing closure. When set, this adapter is
|
|
53
|
+
* PROVIDER-BACKED: reads recompute rows from this closure per request and all
|
|
54
|
+
* writes are rejected (see {@link _assertWritable}). `undefined` ⇒ stored mode.
|
|
55
|
+
*
|
|
56
|
+
* WHY late-binding only (no constructor provider option): a {@link DbSpace}'s
|
|
57
|
+
* zero-arg `TAdapterFactory` builds EVERY table's adapter with the SAME
|
|
58
|
+
* factory — INCLUDING the internal `__atscript_control` sync table. A provider
|
|
59
|
+
* injected at construction would therefore leak onto the control table and
|
|
60
|
+
* break schema sync. Provider mode must target ONE specific table's
|
|
61
|
+
* already-built adapter, so it is only settable AFTER construction via
|
|
62
|
+
* {@link setProvider}.
|
|
63
|
+
*/
|
|
64
|
+
private _provider?;
|
|
65
|
+
/**
|
|
66
|
+
* The in-memory store keeps documents nested (no flattening), so the generic
|
|
67
|
+
* layer should pass nested objects through as-is.
|
|
68
|
+
*/
|
|
69
|
+
supportsNestedObjects(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Parity with the Mongo adapter (`return !fd.encrypted`). The in-memory
|
|
72
|
+
* dot-path filter visitor CAN filter into nested objects AND array
|
|
73
|
+
* (`storage === 'json'`) fields, so JSON storage is NOT a filterability
|
|
74
|
+
* blocker here. The base default vetoes `storage === 'json'` — correct for SQL
|
|
75
|
+
* engines that cannot reach into a raw JSON column, but WRONG for this
|
|
76
|
+
* nested-object-capable adapter, and it would under-report `filterable` to
|
|
77
|
+
* `/meta` for UIs. Overriding fixes that. The `@db.encrypted` veto is
|
|
78
|
+
* core-supplied and absolute (equality/range over ciphertext is meaningless),
|
|
79
|
+
* so it is preserved.
|
|
80
|
+
*
|
|
81
|
+
* NOTE on the capabilities left at their base defaults ON PURPOSE:
|
|
82
|
+
* - `canSortField` — its conservative JSON veto (array sort-by-min/max-element
|
|
83
|
+
* is a footgun for generic UI sort headers) is deliberate, matching Mongo.
|
|
84
|
+
* - `supportsNativePatch` / `supportsNativeRelations` — stay `false`: core
|
|
85
|
+
* decomposes patches into dot-path `$set`s and loads relations app-level.
|
|
86
|
+
*/
|
|
87
|
+
canFilterField(fd: TDbFieldMeta): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Coerces a by-id value to the id field's declared leaf type. The framework
|
|
90
|
+
* calls `adapter.prepareId(value, fieldType)` when building by-id filters, so a
|
|
91
|
+
* URL id like `"21"` reaches this adapter as the STRING `"21"`. Memory does
|
|
92
|
+
* STRICT JS comparison like the Mongo adapter, so the strict `$eq` would then
|
|
93
|
+
* compare `"21" === 21` against a numeric PK and never match — every
|
|
94
|
+
* fetch/patch/delete/replace-by-id of a numeric-PK row would 404. Coercing the
|
|
95
|
+
* id to the field type here fixes that. SQL adapters can rely on the DB to
|
|
96
|
+
* coerce the bound parameter; memory cannot, so it MUST coerce here.
|
|
97
|
+
*
|
|
98
|
+
* Mirrors the Mongo adapter's `prepareId` MINUS its `objectId` branch (an
|
|
99
|
+
* in-memory store has no ObjectId ids): a leaf `designType` of `"number"`
|
|
100
|
+
* coerces via `Number(id)`, everything else via `String(id)`.
|
|
101
|
+
*/
|
|
102
|
+
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
103
|
+
/**
|
|
104
|
+
* Physical names of the primary-key field(s). Single `@meta.id` resolves via
|
|
105
|
+
* {@link AtscriptDbReadable.metaIdPhysical}; a composite key maps each logical
|
|
106
|
+
* PK path through `pathToPhysical` (falling back to the name itself). Memoized
|
|
107
|
+
* because it is stable per adapter (1:1 with a fixed table) yet read on every
|
|
108
|
+
* `pkKey`/projection — recomputing would re-read `this._table.*` and re-allocate.
|
|
109
|
+
*/
|
|
110
|
+
private _physicalPkFields;
|
|
111
|
+
/**
|
|
112
|
+
* PHYSICAL field name → optional `start` for every `@db.default.increment`
|
|
113
|
+
* field. Discovered lazily from the table's field descriptors — each carries
|
|
114
|
+
* both `physicalName` (the key the stored row uses) and `defaultValue`
|
|
115
|
+
* (sourced from `this._table.defaults`), so this resolves column renames
|
|
116
|
+
* correctly where iterating the logical-keyed `defaults` map would not.
|
|
117
|
+
* Memoized because it is stable per adapter (1:1 with a fixed table), like
|
|
118
|
+
* {@link _physicalPkFields}. An empty map ⇒ the insert fast-path skips all
|
|
119
|
+
* increment work.
|
|
120
|
+
*
|
|
121
|
+
* Mirrors the Mongo adapter's `_incrementFields`
|
|
122
|
+
* (`mongo-adapter.ts` — populated in `onFieldScanned`, keyed by physical
|
|
123
|
+
* name): core NEVER generates `increment` values (its `_applyDefaults`
|
|
124
|
+
* switch has no `increment` case, so the field reaches the adapter absent),
|
|
125
|
+
* whether or not the adapter claims it via `nativeDefaultFns()`. The adapter
|
|
126
|
+
* MUST fill it in. Mongo does not override `nativeDefaultFns()` /
|
|
127
|
+
* `supportsNativeValueDefaults()` for increment, so neither does this adapter.
|
|
128
|
+
*/
|
|
129
|
+
private _incrementFields;
|
|
130
|
+
/**
|
|
131
|
+
* Assigns `@db.default.increment` values onto `row` (PHYSICAL shape) IN
|
|
132
|
+
* PLACE — called from {@link _insertRow} BEFORE `pkKey`/uniqueness/inserted-id
|
|
133
|
+
* are computed so an increment PRIMARY KEY produces a real `insertedId` and
|
|
134
|
+
* stores under a real key. The memory analogue of the Mongo adapter's
|
|
135
|
+
* insert-time increment (Mongo uses an atomic `__atscript_counters`
|
|
136
|
+
* collection; an in-memory store just keeps the counter on the instance):
|
|
137
|
+
*
|
|
138
|
+
* - No value for the field → assign the next counter value. First use starts
|
|
139
|
+
* at `start ?? 1` (`max(counter, (start ?? 1) - 1) + 1`); thereafter it is
|
|
140
|
+
* the previous value + 1. Sequential across an `insertMany` batch because
|
|
141
|
+
* {@link _insertRow} runs per item in `insertedIds` order against the shared
|
|
142
|
+
* counter.
|
|
143
|
+
* - Explicit value present → keep it, but advance the counter to
|
|
144
|
+
* `max(counter, value)` so a later auto value can never collide with it.
|
|
145
|
+
*/
|
|
146
|
+
private _applyIncrements;
|
|
147
|
+
/**
|
|
148
|
+
* Builds the duplicate-primary-key {@link DbError}. The reported `path` is the
|
|
149
|
+
* physical `@meta.id` name, falling back to the first (logical) primary key,
|
|
150
|
+
* then `""`. Centralized so the insert and re-key paths raise an identical
|
|
151
|
+
* CONFLICT.
|
|
152
|
+
*/
|
|
153
|
+
private _pkConflict;
|
|
154
|
+
/**
|
|
155
|
+
* Derives the storage key from a row's PRIMARY KEY value(s), read by PHYSICAL
|
|
156
|
+
* name. Encoded as `JSON.stringify` of the ordered PK values so it is
|
|
157
|
+
* collision-proof across both value shapes and types — `['a','b:c']` and
|
|
158
|
+
* `['a:b','c']` differ, and `1` differs from `'1'`.
|
|
159
|
+
*/
|
|
160
|
+
private pkKey;
|
|
161
|
+
/**
|
|
162
|
+
* Builds a DEFINED inserted-id for a table with NO single `@meta.id` — a
|
|
163
|
+
* composite (or single non-meta) primary key, where
|
|
164
|
+
* {@link BaseDbAdapter._resolveInsertedId} would otherwise fall back to
|
|
165
|
+
* `undefined` (memory has no rowid/`_id` to hand back). Returns an object
|
|
166
|
+
* mapping each PRIMARY KEY PHYSICAL field name → its value in the stored row
|
|
167
|
+
* (e.g. `{ part1: "a", part2: "b" }`), read by physical name via
|
|
168
|
+
* {@link getPath} so it matches how {@link pkKey} derives the storage key and
|
|
169
|
+
* honours column renames. A single-field non-meta PK yields the one-key object
|
|
170
|
+
* form for consistency (documented shape).
|
|
171
|
+
*
|
|
172
|
+
* Passed as the `dbGeneratedId` fallback in {@link _insertRow} ONLY when
|
|
173
|
+
* {@link AtscriptDbReadable.metaIdPhysical} is null; single-`@meta.id` tables
|
|
174
|
+
* keep an `undefined` fallback, so their scalar `insertedId`
|
|
175
|
+
* (`row[metaIdPhysical]`) is byte-identical to before.
|
|
176
|
+
*/
|
|
177
|
+
private _compositeInsertedId;
|
|
178
|
+
/**
|
|
179
|
+
* Switches this adapter into PROVIDER-BACKED mode: `fn` is invoked on every
|
|
180
|
+
* read to recompute the table's rows (e.g. a Redis/job-manager snapshot), so a
|
|
181
|
+
* runtime-owned entity with NO database can be observed as a read-only
|
|
182
|
+
* atscript table (and still carry `@DbAction`s). Rows are recomputed per read
|
|
183
|
+
* (no caching); once set, the table is READ-ONLY — all writes throw (see
|
|
184
|
+
* {@link _assertWritable}).
|
|
185
|
+
*
|
|
186
|
+
* Late-binding by design: set AFTER construction only, never via the
|
|
187
|
+
* constructor — see the {@link _provider} field comment for why a constructor
|
|
188
|
+
* option would leak onto the shared control-table adapter and break sync.
|
|
189
|
+
*/
|
|
190
|
+
setProvider(fn: MemoryProviderFn): void;
|
|
191
|
+
/**
|
|
192
|
+
* Write guard for provider-backed (read-only) mode. Called first in every one
|
|
193
|
+
* of the 8 write methods so all mutation entry points reject identically. Uses
|
|
194
|
+
* `INVALID_QUERY` (moost-db's validation interceptor maps it to HTTP 400, NOT
|
|
195
|
+
* 500 — the same choice `aggregate()` makes) since there is no dedicated
|
|
196
|
+
* read-only error code.
|
|
197
|
+
*/
|
|
198
|
+
private _assertWritable;
|
|
199
|
+
/**
|
|
200
|
+
* Snapshot seam for reads. Returns the current rows.
|
|
201
|
+
*
|
|
202
|
+
* - Stored mode reads the instance Map directly (insertion order preserved).
|
|
203
|
+
* - Provider (read-through) mode calls {@link _provider} to recompute a fresh
|
|
204
|
+
* snapshot per read.
|
|
205
|
+
*
|
|
206
|
+
* Does NOT clone — cloning happens only on OUTPUT (see {@link _projectAndClone})
|
|
207
|
+
* so the store stays authoritative and cheap. That same clone-on-output path
|
|
208
|
+
* ALSO covers provider rows: every value handed back to a caller is a
|
|
209
|
+
* `structuredClone`, so a provider that returns objects it still holds is
|
|
210
|
+
* protected from mutation by `reconstructFromRead`/callers.
|
|
211
|
+
*
|
|
212
|
+
* A single logical read invokes the provider EXACTLY ONCE: `findMany`
|
|
213
|
+
* delegates to `findManyWithCount` (one `_filteredRows` ⇒ one `_loadRows`),
|
|
214
|
+
* and `findOne`/`count` each call `_filteredRows` once — so recompute-per-read
|
|
215
|
+
* AND single-snapshot-per-`findManyWithCount` both fall out for free.
|
|
216
|
+
*/
|
|
217
|
+
protected _loadRows(): Record<string, unknown>[] | Promise<Record<string, unknown>[]>;
|
|
218
|
+
/**
|
|
219
|
+
* Clones the payload in, applies the version default, generates any
|
|
220
|
+
* `@db.default.increment` values, enforces PK + unique constraints, stores
|
|
221
|
+
* the row, and returns the resolved inserted id. Clone-in (`structuredClone`)
|
|
222
|
+
* is what makes post-insert mutation of the caller's object never leak into
|
|
223
|
+
* the store. Called once per item by both `insertOne` and `insertMany`, so
|
|
224
|
+
* increment values advance sequentially across a batch.
|
|
225
|
+
*/
|
|
226
|
+
private _insertRow;
|
|
227
|
+
/**
|
|
228
|
+
* Enforces every recorded unique index against the current store. A row is
|
|
229
|
+
* exempted from an index (present-only semantics) when ANY of that index's
|
|
230
|
+
* optional fields is absent/`null`. Otherwise a stored row with an equal
|
|
231
|
+
* tuple → `CONFLICT`.
|
|
232
|
+
*
|
|
233
|
+
* `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
|
|
234
|
+
* a row updating its own unique value does not false-conflict with itself.
|
|
235
|
+
*/
|
|
236
|
+
private _enforceUniqueIndexes;
|
|
237
|
+
insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
|
|
238
|
+
insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
|
|
239
|
+
/**
|
|
240
|
+
* Selects the stored rows a write should touch, as `{ key, row }` pairs so
|
|
241
|
+
* callers can mutate in place and re-key. Stored mode scans the instance Map
|
|
242
|
+
* directly (writes are authoritative against the store, unlike reads which go
|
|
243
|
+
* through the {@link _loadRows} snapshot seam).
|
|
244
|
+
*
|
|
245
|
+
* A defined `expectedVersion` layers an OCC (compare-and-set) predicate on top
|
|
246
|
+
* of `filter`: a row matches only when `row[versionColumn] === expectedVersion`.
|
|
247
|
+
* A version MISMATCH is NOT an error — it simply yields zero matches, so the
|
|
248
|
+
* caller reports `matchedCount: 0`. Supplying `expectedVersion` for a table
|
|
249
|
+
* that has no version column is a misconfiguration and throws (mirrors the
|
|
250
|
+
* Mongo adapter's `_buildCasFilter`).
|
|
251
|
+
*
|
|
252
|
+
* When `many` is `false` at most the first match is returned.
|
|
253
|
+
*/
|
|
254
|
+
private _selectForWrite;
|
|
255
|
+
/**
|
|
256
|
+
* Sets `target`'s version column to `oldRow`'s version + 1, coercing a missing
|
|
257
|
+
* old version to `0`. No-op on an unversioned table. The OLD version is read
|
|
258
|
+
* from a pristine `oldRow` (not `target`) so the result is always
|
|
259
|
+
* `oldVersion + 1` regardless of what a patch/replace payload wrote onto
|
|
260
|
+
* `target`'s version column — the memory analogue of Mongo forcing
|
|
261
|
+
* `$inc: { version: 1 }` last. Shared by {@link _commitUpdate} (merge path) and
|
|
262
|
+
* {@link replaceOne} (full-replace path).
|
|
263
|
+
*/
|
|
264
|
+
private _bumpVersion;
|
|
265
|
+
/**
|
|
266
|
+
* Applies a merge-style update to `row` IN PLACE — the memory analogue of
|
|
267
|
+
* `buildMongoUpdateDoc`:
|
|
268
|
+
*
|
|
269
|
+
* - `$set` (`data`): each key is set onto the row via {@link setPath}. Keys are
|
|
270
|
+
* DOT-PATHS (the table layer decomposes nested patches into `"profile.city"`
|
|
271
|
+
* because this adapter reports no {@link supportsNativePatch}), so they must
|
|
272
|
+
* nest into the stored document — MERGING siblings — exactly like Mongo's
|
|
273
|
+
* `$set: { "profile.city": v }`, not create a literal dotted key. Top-level
|
|
274
|
+
* (dot-free) keys behave as a plain assignment. `data` is `structuredClone`d
|
|
275
|
+
* first so nested subtrees from the caller never alias into the store.
|
|
276
|
+
* - `ops.inc` / `ops.mul`: numeric increment / multiply on the (dot-path)
|
|
277
|
+
* target, coercing a missing or non-numeric current value to `0` (parity with
|
|
278
|
+
* Mongo's `$inc`/`$mul`).
|
|
279
|
+
*
|
|
280
|
+
* Does NOT bump the version column — {@link _commitUpdate} does that LAST via
|
|
281
|
+
* {@link _bumpVersion} (after this merge, reading the pristine old row), so the
|
|
282
|
+
* bump always wins over whatever `data`/`inc` wrote and lands on `oldVersion + 1`.
|
|
283
|
+
*/
|
|
284
|
+
private _applyUpdate;
|
|
285
|
+
/**
|
|
286
|
+
* Places `next` into the store under its (possibly changed) {@link pkKey},
|
|
287
|
+
* re-keying when a mutation/replace moved the primary key. A collision on the
|
|
288
|
+
* NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
|
|
289
|
+
* touching the Map so a failed re-key leaves the store unchanged.
|
|
290
|
+
*/
|
|
291
|
+
private _commitRow;
|
|
292
|
+
/**
|
|
293
|
+
* Update-then-commit for a single matched row: clones the pristine `row`,
|
|
294
|
+
* applies the merge update, bumps the version LAST (read from the pristine old
|
|
295
|
+
* `row`), enforces unique indexes on the result EXCLUDING the row's own key (so
|
|
296
|
+
* a row keeping/rewriting its own unique value never self-conflicts), then
|
|
297
|
+
* commits. Nothing is written to the store until both the unique and PK checks
|
|
298
|
+
* pass, so a conflict leaves the store untouched.
|
|
299
|
+
*/
|
|
300
|
+
private _commitUpdate;
|
|
301
|
+
replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
302
|
+
updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
|
|
303
|
+
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
304
|
+
/**
|
|
305
|
+
* Stable multi-key comparator from `$sort`, with a final tie-break on
|
|
306
|
+
* {@link pkKey} for a deterministic total order. Returns the input unchanged
|
|
307
|
+
* (insertion order) when there is no `$sort`. Delegates to the shared pure
|
|
308
|
+
* {@link sortRows}, injecting {@link pkKey} as the total-order tie-break.
|
|
309
|
+
*/
|
|
310
|
+
private _sortRows;
|
|
311
|
+
/** Applies `$skip` then `$limit` (both optional) via a single slice. */
|
|
312
|
+
private _paginate;
|
|
313
|
+
/**
|
|
314
|
+
* Projects a stored row per `$select` and returns a fresh, deep-cloned object
|
|
315
|
+
* so the store can never be mutated through a returned value.
|
|
316
|
+
*
|
|
317
|
+
* - No projection → a full clone.
|
|
318
|
+
* - INCLUSION form (`{ field: 1 }`) → a new object with only the selected
|
|
319
|
+
* paths PLUS the primary-key field(s) (mirrors Mongo including `_id`).
|
|
320
|
+
* - EXCLUSION form (`{ field: 0 }`) → a clone with those paths removed.
|
|
321
|
+
*
|
|
322
|
+
* Top-level and nested dot-paths are supported; exotic Mongo projection
|
|
323
|
+
* quirks (array positional, `$slice`, etc.) are intentionally NOT replicated.
|
|
324
|
+
*/
|
|
325
|
+
private _projectAndClone;
|
|
326
|
+
/**
|
|
327
|
+
* Reads the pagination/sort/projection controls with their intended types.
|
|
328
|
+
* `DbControls` carries a `[key: `$${string}`]: unknown` index signature, and
|
|
329
|
+
* `Omit`-ing `$select` from `UniqueryControls` widens `$sort`/`$skip`/`$limit`
|
|
330
|
+
* back to `unknown` — so the casts here restore the declared shapes at a
|
|
331
|
+
* single, documented boundary. `$select` keeps its explicit `UniquSelect` type.
|
|
332
|
+
*/
|
|
333
|
+
private _readControls;
|
|
334
|
+
/**
|
|
335
|
+
* The single "load a snapshot, apply the filter predicate" step every read
|
|
336
|
+
* shares. Goes through the {@link _loadRows} seam exactly ONCE per call, so a
|
|
337
|
+
* reader (and provider read-through mode) has one place that materializes the
|
|
338
|
+
* working set — one provider invocation per logical read.
|
|
339
|
+
*/
|
|
340
|
+
private _filteredRows;
|
|
341
|
+
findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
|
|
342
|
+
findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
343
|
+
count(query: DbQuery): Promise<number>;
|
|
344
|
+
/**
|
|
345
|
+
* Overridden so the filtered snapshot is computed ONCE — the base default
|
|
346
|
+
* runs `findMany` and `count` separately (two `_loadRows` snapshots). A
|
|
347
|
+
* single snapshot is both cheaper here and the correct semantics for
|
|
348
|
+
* provider (read-through) mode, where two separate reads could otherwise
|
|
349
|
+
* observe different snapshots and make count/data disagree.
|
|
350
|
+
*/
|
|
351
|
+
findManyWithCount(query: DbQuery): Promise<{
|
|
352
|
+
data: Array<Record<string, unknown>>;
|
|
353
|
+
count: number;
|
|
354
|
+
}>;
|
|
355
|
+
/**
|
|
356
|
+
* Aggregation (`$groupBy`) is a documented v1 non-goal for the in-memory
|
|
357
|
+
* adapter. The inherited base default throws a PLAIN `Error`, which a readable
|
|
358
|
+
* REST controller would surface as an unhandled HTTP 500 when a `?$groupBy=`
|
|
359
|
+
* query routes here. Throwing a typed {@link DbError} with `INVALID_QUERY`
|
|
360
|
+
* instead converts that into a clean client error — moost-db's validation
|
|
361
|
+
* interceptor maps `INVALID_QUERY` → HTTP 400.
|
|
362
|
+
*/
|
|
363
|
+
aggregate(_query: DbQuery): Promise<Array<Record<string, unknown>>>;
|
|
364
|
+
updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
|
|
365
|
+
replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
|
|
366
|
+
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
367
|
+
/**
|
|
368
|
+
* Records the model's `unique` indexes for insert-time enforcement. Idempotent
|
|
369
|
+
* (replaces the recorded set). Non-unique index types are ignored — an
|
|
370
|
+
* in-memory scan needs no plain/fulltext/geo index to answer queries.
|
|
371
|
+
*/
|
|
372
|
+
syncIndexes(): Promise<void>;
|
|
373
|
+
/**
|
|
374
|
+
* No-op: the store is the instance-level {@link rows} Map, which already
|
|
375
|
+
* exists. Safe to call repeatedly.
|
|
376
|
+
*/
|
|
377
|
+
ensureTable(): Promise<void>;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Ergonomic late-binding entry point for provider (read-through) mode. Resolves
|
|
381
|
+
* the ALREADY-BUILT {@link MemoryAdapter} backing `type` on `space` (reached
|
|
382
|
+
* after `getTable`/`syncSchema` has constructed it via `space.getAdapter`, which
|
|
383
|
+
* exists in core — so this helper needs NO core change) and installs `fn` as its
|
|
384
|
+
* provider, making that one table read-only and recomputed per read.
|
|
385
|
+
*
|
|
386
|
+
* Throws if the resolved adapter is not a {@link MemoryAdapter} (i.e. the space
|
|
387
|
+
* is backed by a different engine) so a misuse fails loudly, not silently.
|
|
388
|
+
*/
|
|
389
|
+
declare function setMemoryProvider(space: DbSpace, type: TAtscriptAnnotatedType, fn: MemoryProviderFn): void;
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/lib/memory-filter.d.ts
|
|
392
|
+
/**
|
|
393
|
+
* In-memory row predicate: given a document, decide whether it matches a
|
|
394
|
+
* filter. This is the single unit of currency the whole engine composes —
|
|
395
|
+
* leaves, logical nodes and the top-level filter all reduce to one of these.
|
|
396
|
+
*/
|
|
397
|
+
type Predicate = (row: Record<string, unknown>) => boolean;
|
|
398
|
+
/**
|
|
399
|
+
* Dot-path getter. Splits `path` on `.` and walks plain objects, returning the
|
|
400
|
+
* value at the end of the path or `undefined` if any intermediate segment is
|
|
401
|
+
* missing or is not a plain object.
|
|
402
|
+
*
|
|
403
|
+
* LIMITATION (v1, accepted): this does NOT descend into arrays. If a segment
|
|
404
|
+
* resolves to an array, traversal stops and `undefined` is returned — there is
|
|
405
|
+
* no positional/`$elemMatch`-style indexing. Array-of-object matching is a
|
|
406
|
+
* later concern; the SQL/Mongo adapters flatten differently and we do not want
|
|
407
|
+
* to fake a semantic the store can't back yet.
|
|
408
|
+
*/
|
|
409
|
+
/**
|
|
410
|
+
* Compiles a {@link FilterExpr} into an in-memory row predicate
|
|
411
|
+
* `(row) => boolean`, reusing the shared {@link walkFilter} walker so filter
|
|
412
|
+
* structure matches the SQL/Mongo adapters by construction.
|
|
413
|
+
*
|
|
414
|
+
* An empty/absent filter (for which `walkFilter` returns `undefined`) compiles
|
|
415
|
+
* to a match-everything predicate.
|
|
416
|
+
*/
|
|
417
|
+
declare function buildMemoryPredicate(filter: FilterExpr): Predicate;
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/lib/memory-engine.d.ts
|
|
420
|
+
/**
|
|
421
|
+
* Stable multi-key sort from `$sort`, applied over plain rows.
|
|
422
|
+
*
|
|
423
|
+
* - No `$sort` (or an empty one) → returns the input array UNCHANGED (same
|
|
424
|
+
* reference, insertion order preserved) — the fast path for an unsorted read.
|
|
425
|
+
* - `tieBreak`, when supplied, is the FINAL deterministic tie-break key for a
|
|
426
|
+
* TOTAL order — the adapter injects its {@link MemoryAdapter.pkKey} here so
|
|
427
|
+
* rows with equal sort keys still order deterministically. When ABSENT the
|
|
428
|
+
* sort falls back to preserving input order among equal keys (via each row's
|
|
429
|
+
* original index), so a consumer with no primary key keeps insertion order.
|
|
430
|
+
*
|
|
431
|
+
* NEVER mutates the input array: `.map` decorates into a fresh array and
|
|
432
|
+
* `.toSorted` returns another new sorted array (unlike `.sort`, which reorders
|
|
433
|
+
* in place). The `tieBreak`/index is computed ONCE per row (O(n)), not inside
|
|
434
|
+
* the O(n log n) comparator.
|
|
435
|
+
*/
|
|
436
|
+
declare function sortRows(rows: Record<string, unknown>[], $sort?: Partial<Record<string, 1 | -1>>, tieBreak?: (row: Record<string, unknown>) => string | number): Record<string, unknown>[];
|
|
437
|
+
/** Options for {@link projectRow}. */
|
|
438
|
+
interface ProjectRowOptions {
|
|
439
|
+
/**
|
|
440
|
+
* Physical field names ALWAYS kept by an inclusion projection (mirrors Mongo
|
|
441
|
+
* including `_id`). The adapter passes its primary-key field(s); a consumer
|
|
442
|
+
* with no PK (e.g. value-help) passes none. Ignored for exclusion / no
|
|
443
|
+
* projection.
|
|
444
|
+
*/
|
|
445
|
+
pkFields?: string[];
|
|
446
|
+
/**
|
|
447
|
+
* When `true`, the returned object is `structuredClone`d so it shares NO
|
|
448
|
+
* structure with `row` (mutating the output leaves the input intact) — what
|
|
449
|
+
* the adapter needs to keep its store authoritative. When `false`/absent the
|
|
450
|
+
* output may alias nested subtrees of `row` (cheaper; for callers that own
|
|
451
|
+
* their rows).
|
|
452
|
+
*/
|
|
453
|
+
clone?: boolean;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Projects a plain row per a `{ path: 0 | 1 }` projection map. Decoupled from
|
|
457
|
+
* `UniquSelect`: the caller passes the resolved projection map (e.g. from
|
|
458
|
+
* `$select.asProjection`) so the engine has no query-layer dependency.
|
|
459
|
+
*
|
|
460
|
+
* - No projection (undefined / empty) → the whole row (cloned per `clone`).
|
|
461
|
+
* - INCLUSION form (first entry is `1`) → a new object with only the selected
|
|
462
|
+
* paths PLUS `opts.pkFields`. Absent fields are omitted; a present-`null`
|
|
463
|
+
* (value === null) is kept.
|
|
464
|
+
* - EXCLUSION form (first entry is `0`) → a clone with those paths removed. This
|
|
465
|
+
* branch ALWAYS clones (it must own a copy to drop paths without mutating the
|
|
466
|
+
* input), so `clone: false` is a no-op here.
|
|
467
|
+
*
|
|
468
|
+
* Top-level and nested dot-paths are supported; exotic Mongo projection quirks
|
|
469
|
+
* (array positional, `$slice`, etc.) are intentionally NOT replicated.
|
|
470
|
+
*/
|
|
471
|
+
declare function projectRow(row: Record<string, unknown>, projection?: Record<string, 0 | 1>, opts?: ProjectRowOptions): Record<string, unknown>;
|
|
472
|
+
//#endregion
|
|
473
|
+
//#region src/lib/index.d.ts
|
|
474
|
+
/**
|
|
475
|
+
* Creates a {@link DbSpace} backed by an in-memory {@link MemoryAdapter}.
|
|
476
|
+
*
|
|
477
|
+
* Tables default to STORED mode (an instance-level `Map`). To make one table
|
|
478
|
+
* read-only and read-through from a runtime closure, call `setMemoryProvider`
|
|
479
|
+
* (re-exported from {@link ./memory-adapter}) on the space AFTER the table's
|
|
480
|
+
* adapter has been built.
|
|
481
|
+
*/
|
|
482
|
+
declare function createAdapter(): DbSpace;
|
|
483
|
+
//#endregion
|
|
484
|
+
export { MemoryAdapter, MemoryProviderFn, buildMemoryPredicate, createAdapter, projectRow, setMemoryProvider, sortRows };
|