@atscript/db-memory 0.1.149 → 0.1.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, ResolvedRelationFilter, TDbDeleteResult, TDbFieldMeta, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TFieldOps, TViewCapability } from "@atscript/db";
1
+ import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, ResolvedRelationFilter, TDbDeleteResult, TDbFieldMeta, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateOptions, TDbUpdateResult, TFieldOps, TViewCapability } from "@atscript/db";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/lib/memory-adapter.d.ts
@@ -33,6 +33,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
33
33
  private _db;
34
34
  /** Memoized physical PK field names — stable for the adapter's lifetime. */
35
35
  private _pkFieldsCache?;
36
+ /** Memoized compiled readers of {@link _physicalPkFields}. */
37
+ private _pkReadersCache?;
36
38
  /**
37
39
  * Memoized map of PHYSICAL field name → optional `start` for every field
38
40
  * carrying `@db.default.increment`. Stable per adapter (see
@@ -123,6 +125,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
123
125
  * `pkKey`/projection — recomputing would re-read `this._table.*` and re-allocate.
124
126
  */
125
127
  private _physicalPkFields;
128
+ /** Compiled readers of {@link _physicalPkFields} (memoized like it). */
129
+ private _pkReaders;
126
130
  /**
127
131
  * PHYSICAL field name → optional `start` for every `@db.default.increment`
128
132
  * field. Discovered lazily from the table's field descriptors — each carries
@@ -175,6 +179,16 @@ declare class MemoryAdapter extends BaseDbAdapter {
175
179
  * `['a:b','c']` differ, and `1` differs from `'1'`.
176
180
  */
177
181
  private pkKey;
182
+ /**
183
+ * The storage key a filter pins by exact equality on EVERY primary-key field
184
+ * — `{ id: v }`, `{ id: { $eq: v } }`, also inside (nested) `$and` — or
185
+ * `undefined`. Only a string / number / boolean / `Date` value pins: for
186
+ * those, a row the filter matches is stored exactly under this key (strict
187
+ * equality ⇒ equal `JSON.stringify`), so a read can look the row up and
188
+ * still run the full predicate on it. Unavailable once the table holds an
189
+ * {@link MemoryTableState.opaquePk opaque} key.
190
+ */
191
+ private _pinnedKey;
178
192
  /**
179
193
  * Builds a DEFINED inserted-id for a table with NO single `@meta.id` — a
180
194
  * composite (or single non-meta) primary key, where
@@ -251,8 +265,9 @@ declare class MemoryAdapter extends BaseDbAdapter {
251
265
  /**
252
266
  * Clones the payload in, applies the version default, generates any
253
267
  * `@db.default.increment` values, enforces PK + unique constraints, stores
254
- * the row, and returns the resolved inserted id. Clone-in (`structuredClone`)
255
- * is what makes post-insert mutation of the caller's object never leak into
268
+ * the row, and returns the resolved inserted id. Clone-in ({@link cloneValue},
269
+ * `structuredClone`-equivalent) is what makes post-insert mutation of the
270
+ * caller's object never leak into
256
271
  * the store. Called once per item by both `insertOne` and `insertMany`, so
257
272
  * increment values advance sequentially across a batch.
258
273
  */
@@ -261,12 +276,21 @@ declare class MemoryAdapter extends BaseDbAdapter {
261
276
  * Enforces every recorded unique index against the current store. A row is
262
277
  * exempted from an index (present-only semantics) when ANY of that index's
263
278
  * optional fields is absent/`null`. Otherwise a stored row with an equal
264
- * tuple → `CONFLICT`.
279
+ * tuple → `CONFLICT`. Returns the row's tuple key per index (for
280
+ * {@link _storeRow}).
265
281
  *
266
282
  * `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
267
283
  * a row updating its own unique value does not false-conflict with itself.
268
284
  */
269
285
  private _enforceUniqueIndexes;
286
+ /**
287
+ * Stores `row` under `key` and records it in the unique indexes (`tuples`:
288
+ * its precomputed tuple keys). The ONLY way a row enters the store, so the
289
+ * indexes and {@link MemoryTableState.opaquePk} never drift from it.
290
+ */
291
+ private _storeRow;
292
+ /** Removes the row stored under `key` (and its unique-index entries). */
293
+ private _removeRow;
270
294
  insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
271
295
  insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
272
296
  supportsInsertIgnore(): boolean;
@@ -311,7 +335,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
311
335
  * because this adapter reports no {@link supportsNativePatch}), so they must
312
336
  * nest into the stored document — MERGING siblings — exactly like Mongo's
313
337
  * `$set: { "profile.city": v }`, not create a literal dotted key. Top-level
314
- * (dot-free) keys behave as a plain assignment. `data` is `structuredClone`d
338
+ * (dot-free) keys behave as a plain assignment. `data` is deep-cloned
315
339
  * first so nested subtrees from the caller never alias into the store.
316
340
  * - `ops.inc` / `ops.mul`: numeric increment / multiply on the (dot-path)
317
341
  * target, coercing a missing or non-numeric current value to `0` (parity with
@@ -323,10 +347,11 @@ declare class MemoryAdapter extends BaseDbAdapter {
323
347
  */
324
348
  private _applyUpdate;
325
349
  /**
326
- * Places `next` into the store under its (possibly changed) {@link pkKey},
327
- * re-keying when a mutation/replace moved the primary key. A collision on the
328
- * NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
329
- * touching the Map so a failed re-key leaves the store unchanged.
350
+ * Places `next` into the store under its (possibly changed) {@link pkKey} in
351
+ * place of `oldRow` (stored under `oldKey`), re-keying when a mutation/replace
352
+ * moved the primary key. A collision on the NEW key (some other row already
353
+ * owns it) throws `CONFLICT`. Throws BEFORE touching the Map so a failed
354
+ * re-key leaves the store unchanged. `tuples` are `next`'s unique-index keys.
330
355
  */
331
356
  private _commitRow;
332
357
  /**
@@ -335,22 +360,24 @@ declare class MemoryAdapter extends BaseDbAdapter {
335
360
  * `row`), enforces unique indexes on the result EXCLUDING the row's own key (so
336
361
  * a row keeping/rewriting its own unique value never self-conflicts), then
337
362
  * commits. Nothing is written to the store until both the unique and PK checks
338
- * pass, so a conflict leaves the store untouched.
363
+ * pass, so a conflict leaves the store untouched. `keepVersion` skips the
364
+ * bump (a version-exempt patch, since 0.1.150).
339
365
  */
340
366
  private _commitUpdate;
341
367
  replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
342
- updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
368
+ updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
343
369
  deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
344
370
  /**
345
371
  * Stable multi-key comparator from `$sort`, with a final tie-break on
346
372
  * {@link pkKey} for a deterministic total order. Returns the input unchanged
347
373
  * (insertion order) when there is no `$sort`. Delegates to the shared pure
348
- * {@link sortRows}, injecting {@link pkKey} as the total-order tie-break.
374
+ * {@link sortRows}, injecting {@link pkKey} as the total-order tie-break;
375
+ * `topK` asks for the head of that order only (`$skip + $limit`).
349
376
  */
350
377
  private _sortRows;
351
378
  /**
352
- * Projects a stored row per `$select` and returns a fresh, deep-cloned object
353
- * so the store can never be mutated through a returned value.
379
+ * Compiles the `$select` projection of one read: each returned row is a
380
+ * fresh, deep-cloned object so the store can never be mutated through it.
354
381
  *
355
382
  * - No projection → a full clone.
356
383
  * - INCLUSION form (`{ field: 1 }`) → a new object with exactly the selected
@@ -361,7 +388,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
361
388
  * Top-level and nested dot-paths are supported; exotic Mongo projection
362
389
  * quirks (array positional, `$slice`, etc.) are intentionally NOT replicated.
363
390
  */
364
- private _projectAndClone;
391
+ private _projector;
365
392
  /**
366
393
  * Reads the pagination/sort/projection controls with their intended types.
367
394
  * `DbControls` carries a `[key: `$${string}`]: unknown` index signature, and
@@ -375,6 +402,10 @@ declare class MemoryAdapter extends BaseDbAdapter {
375
402
  * shares. Goes through the {@link _loadRows} seam exactly ONCE per call, so a
376
403
  * reader (and provider read-through mode) has one place that materializes the
377
404
  * working set — one provider invocation per logical read.
405
+ *
406
+ * Stored mode reads the table's Map in place (no snapshot copy), resolves a
407
+ * filter pinning the whole primary key by direct lookup, and stops after
408
+ * `limit` matches when given (an unsorted `findOne`).
378
409
  */
379
410
  private _filteredRows;
380
411
  findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
@@ -415,7 +446,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
415
446
  supportsAggregateExpressions(): boolean;
416
447
  /** Managed views render nothing (non-goal) — both are accepted like `aggregateFns()`. */
417
448
  viewCapabilities(): ReadonlySet<TViewCapability>;
418
- updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
449
+ updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
419
450
  replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
420
451
  deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
421
452
  /**
@@ -520,13 +551,16 @@ declare function buildMemoryPredicate(filter: FilterExpr, relationSets?: Relatio
520
551
  * rows with equal sort keys still order deterministically. When ABSENT the
521
552
  * sort falls back to preserving input order among equal keys (via each row's
522
553
  * original index), so a consumer with no primary key keeps insertion order.
554
+ * - `topK` (since 0.1.151), when supplied, asks for only the first `topK` rows
555
+ * of that order (e.g. `$skip + $limit`): a small one is selected in one pass
556
+ * instead of sorting everything. The rows returned are exactly the head of
557
+ * the full sort — same comparator, same stable tie handling.
523
558
  *
524
- * NEVER mutates the input array: `.map` decorates into a fresh array and
525
- * `.toSorted` returns another new sorted array (unlike `.sort`, which reorders
526
- * in place). The `tieBreak`/index is computed ONCE per row (O(n)), not inside
527
- * the O(n log n) comparator.
559
+ * NEVER mutates the input array. Each row's sort keys are read ONCE (O(n)) and
560
+ * its `tieBreak` only when two rows tie on every key — not inside the
561
+ * O(n log n) comparator.
528
562
  */
529
- declare function sortRows(rows: Record<string, unknown>[], $sort?: Partial<Record<string, 1 | -1>>, tieBreak?: (row: Record<string, unknown>) => string | number): Record<string, unknown>[];
563
+ declare function sortRows(rows: Record<string, unknown>[], $sort?: Partial<Record<string, 1 | -1>>, tieBreak?: (row: Record<string, unknown>) => string | number, topK?: number): Record<string, unknown>[];
530
564
  /** Options for {@link projectRow}. */
531
565
  interface ProjectRowOptions {
532
566
  /**
@@ -560,6 +594,9 @@ interface ProjectRowOptions {
560
594
  *
561
595
  * Top-level and nested dot-paths are supported; exotic Mongo projection quirks
562
596
  * (array positional, `$slice`, etc.) are intentionally NOT replicated.
597
+ *
598
+ * Clones are `structuredClone`-equivalent. To project many rows the same way,
599
+ * compile once with {@link compileProjection}.
563
600
  */
564
601
  declare function projectRow(row: Record<string, unknown>, projection?: Record<string, 0 | 1>, opts?: ProjectRowOptions): Record<string, unknown>;
565
602
  //#endregion
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, ResolvedRelationFilter, TDbDeleteResult, TDbFieldMeta, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TFieldOps, TViewCapability } from "@atscript/db";
1
+ import { AggregateFn, BaseDbAdapter, BucketUnit, DbQuery, DbSpace, FilterExpr, ResolvedRelationFilter, TDbDeleteResult, TDbFieldMeta, TDbInsertIgnoreSlot, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateOptions, TDbUpdateResult, TFieldOps, TViewCapability } from "@atscript/db";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/lib/memory-adapter.d.ts
@@ -33,6 +33,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
33
33
  private _db;
34
34
  /** Memoized physical PK field names — stable for the adapter's lifetime. */
35
35
  private _pkFieldsCache?;
36
+ /** Memoized compiled readers of {@link _physicalPkFields}. */
37
+ private _pkReadersCache?;
36
38
  /**
37
39
  * Memoized map of PHYSICAL field name → optional `start` for every field
38
40
  * carrying `@db.default.increment`. Stable per adapter (see
@@ -123,6 +125,8 @@ declare class MemoryAdapter extends BaseDbAdapter {
123
125
  * `pkKey`/projection — recomputing would re-read `this._table.*` and re-allocate.
124
126
  */
125
127
  private _physicalPkFields;
128
+ /** Compiled readers of {@link _physicalPkFields} (memoized like it). */
129
+ private _pkReaders;
126
130
  /**
127
131
  * PHYSICAL field name → optional `start` for every `@db.default.increment`
128
132
  * field. Discovered lazily from the table's field descriptors — each carries
@@ -175,6 +179,16 @@ declare class MemoryAdapter extends BaseDbAdapter {
175
179
  * `['a:b','c']` differ, and `1` differs from `'1'`.
176
180
  */
177
181
  private pkKey;
182
+ /**
183
+ * The storage key a filter pins by exact equality on EVERY primary-key field
184
+ * — `{ id: v }`, `{ id: { $eq: v } }`, also inside (nested) `$and` — or
185
+ * `undefined`. Only a string / number / boolean / `Date` value pins: for
186
+ * those, a row the filter matches is stored exactly under this key (strict
187
+ * equality ⇒ equal `JSON.stringify`), so a read can look the row up and
188
+ * still run the full predicate on it. Unavailable once the table holds an
189
+ * {@link MemoryTableState.opaquePk opaque} key.
190
+ */
191
+ private _pinnedKey;
178
192
  /**
179
193
  * Builds a DEFINED inserted-id for a table with NO single `@meta.id` — a
180
194
  * composite (or single non-meta) primary key, where
@@ -251,8 +265,9 @@ declare class MemoryAdapter extends BaseDbAdapter {
251
265
  /**
252
266
  * Clones the payload in, applies the version default, generates any
253
267
  * `@db.default.increment` values, enforces PK + unique constraints, stores
254
- * the row, and returns the resolved inserted id. Clone-in (`structuredClone`)
255
- * is what makes post-insert mutation of the caller's object never leak into
268
+ * the row, and returns the resolved inserted id. Clone-in ({@link cloneValue},
269
+ * `structuredClone`-equivalent) is what makes post-insert mutation of the
270
+ * caller's object never leak into
256
271
  * the store. Called once per item by both `insertOne` and `insertMany`, so
257
272
  * increment values advance sequentially across a batch.
258
273
  */
@@ -261,12 +276,21 @@ declare class MemoryAdapter extends BaseDbAdapter {
261
276
  * Enforces every recorded unique index against the current store. A row is
262
277
  * exempted from an index (present-only semantics) when ANY of that index's
263
278
  * optional fields is absent/`null`. Otherwise a stored row with an equal
264
- * tuple → `CONFLICT`.
279
+ * tuple → `CONFLICT`. Returns the row's tuple key per index (for
280
+ * {@link _storeRow}).
265
281
  *
266
282
  * `excludeKey` (when given) skips the row stored under that {@link pkKey} — so
267
283
  * a row updating its own unique value does not false-conflict with itself.
268
284
  */
269
285
  private _enforceUniqueIndexes;
286
+ /**
287
+ * Stores `row` under `key` and records it in the unique indexes (`tuples`:
288
+ * its precomputed tuple keys). The ONLY way a row enters the store, so the
289
+ * indexes and {@link MemoryTableState.opaquePk} never drift from it.
290
+ */
291
+ private _storeRow;
292
+ /** Removes the row stored under `key` (and its unique-index entries). */
293
+ private _removeRow;
270
294
  insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
271
295
  insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
272
296
  supportsInsertIgnore(): boolean;
@@ -311,7 +335,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
311
335
  * because this adapter reports no {@link supportsNativePatch}), so they must
312
336
  * nest into the stored document — MERGING siblings — exactly like Mongo's
313
337
  * `$set: { "profile.city": v }`, not create a literal dotted key. Top-level
314
- * (dot-free) keys behave as a plain assignment. `data` is `structuredClone`d
338
+ * (dot-free) keys behave as a plain assignment. `data` is deep-cloned
315
339
  * first so nested subtrees from the caller never alias into the store.
316
340
  * - `ops.inc` / `ops.mul`: numeric increment / multiply on the (dot-path)
317
341
  * target, coercing a missing or non-numeric current value to `0` (parity with
@@ -323,10 +347,11 @@ declare class MemoryAdapter extends BaseDbAdapter {
323
347
  */
324
348
  private _applyUpdate;
325
349
  /**
326
- * Places `next` into the store under its (possibly changed) {@link pkKey},
327
- * re-keying when a mutation/replace moved the primary key. A collision on the
328
- * NEW key (some other row already owns it) throws `CONFLICT`. Throws BEFORE
329
- * touching the Map so a failed re-key leaves the store unchanged.
350
+ * Places `next` into the store under its (possibly changed) {@link pkKey} in
351
+ * place of `oldRow` (stored under `oldKey`), re-keying when a mutation/replace
352
+ * moved the primary key. A collision on the NEW key (some other row already
353
+ * owns it) throws `CONFLICT`. Throws BEFORE touching the Map so a failed
354
+ * re-key leaves the store unchanged. `tuples` are `next`'s unique-index keys.
330
355
  */
331
356
  private _commitRow;
332
357
  /**
@@ -335,22 +360,24 @@ declare class MemoryAdapter extends BaseDbAdapter {
335
360
  * `row`), enforces unique indexes on the result EXCLUDING the row's own key (so
336
361
  * a row keeping/rewriting its own unique value never self-conflicts), then
337
362
  * commits. Nothing is written to the store until both the unique and PK checks
338
- * pass, so a conflict leaves the store untouched.
363
+ * pass, so a conflict leaves the store untouched. `keepVersion` skips the
364
+ * bump (a version-exempt patch, since 0.1.150).
339
365
  */
340
366
  private _commitUpdate;
341
367
  replaceOne(filter: FilterExpr, data: Record<string, unknown>, expectedVersion?: number): Promise<TDbUpdateResult>;
342
- updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number): Promise<TDbUpdateResult>;
368
+ updateOne(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, expectedVersion?: number, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
343
369
  deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
344
370
  /**
345
371
  * Stable multi-key comparator from `$sort`, with a final tie-break on
346
372
  * {@link pkKey} for a deterministic total order. Returns the input unchanged
347
373
  * (insertion order) when there is no `$sort`. Delegates to the shared pure
348
- * {@link sortRows}, injecting {@link pkKey} as the total-order tie-break.
374
+ * {@link sortRows}, injecting {@link pkKey} as the total-order tie-break;
375
+ * `topK` asks for the head of that order only (`$skip + $limit`).
349
376
  */
350
377
  private _sortRows;
351
378
  /**
352
- * Projects a stored row per `$select` and returns a fresh, deep-cloned object
353
- * so the store can never be mutated through a returned value.
379
+ * Compiles the `$select` projection of one read: each returned row is a
380
+ * fresh, deep-cloned object so the store can never be mutated through it.
354
381
  *
355
382
  * - No projection → a full clone.
356
383
  * - INCLUSION form (`{ field: 1 }`) → a new object with exactly the selected
@@ -361,7 +388,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
361
388
  * Top-level and nested dot-paths are supported; exotic Mongo projection
362
389
  * quirks (array positional, `$slice`, etc.) are intentionally NOT replicated.
363
390
  */
364
- private _projectAndClone;
391
+ private _projector;
365
392
  /**
366
393
  * Reads the pagination/sort/projection controls with their intended types.
367
394
  * `DbControls` carries a `[key: `$${string}`]: unknown` index signature, and
@@ -375,6 +402,10 @@ declare class MemoryAdapter extends BaseDbAdapter {
375
402
  * shares. Goes through the {@link _loadRows} seam exactly ONCE per call, so a
376
403
  * reader (and provider read-through mode) has one place that materializes the
377
404
  * working set — one provider invocation per logical read.
405
+ *
406
+ * Stored mode reads the table's Map in place (no snapshot copy), resolves a
407
+ * filter pinning the whole primary key by direct lookup, and stops after
408
+ * `limit` matches when given (an unsorted `findOne`).
378
409
  */
379
410
  private _filteredRows;
380
411
  findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
@@ -415,7 +446,7 @@ declare class MemoryAdapter extends BaseDbAdapter {
415
446
  supportsAggregateExpressions(): boolean;
416
447
  /** Managed views render nothing (non-goal) — both are accepted like `aggregateFns()`. */
417
448
  viewCapabilities(): ReadonlySet<TViewCapability>;
418
- updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps): Promise<TDbUpdateResult>;
449
+ updateMany(filter: FilterExpr, data: Record<string, unknown>, ops?: TFieldOps, opts?: TDbUpdateOptions): Promise<TDbUpdateResult>;
419
450
  replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
420
451
  deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
421
452
  /**
@@ -520,13 +551,16 @@ declare function buildMemoryPredicate(filter: FilterExpr, relationSets?: Relatio
520
551
  * rows with equal sort keys still order deterministically. When ABSENT the
521
552
  * sort falls back to preserving input order among equal keys (via each row's
522
553
  * original index), so a consumer with no primary key keeps insertion order.
554
+ * - `topK` (since 0.1.151), when supplied, asks for only the first `topK` rows
555
+ * of that order (e.g. `$skip + $limit`): a small one is selected in one pass
556
+ * instead of sorting everything. The rows returned are exactly the head of
557
+ * the full sort — same comparator, same stable tie handling.
523
558
  *
524
- * NEVER mutates the input array: `.map` decorates into a fresh array and
525
- * `.toSorted` returns another new sorted array (unlike `.sort`, which reorders
526
- * in place). The `tieBreak`/index is computed ONCE per row (O(n)), not inside
527
- * the O(n log n) comparator.
559
+ * NEVER mutates the input array. Each row's sort keys are read ONCE (O(n)) and
560
+ * its `tieBreak` only when two rows tie on every key — not inside the
561
+ * O(n log n) comparator.
528
562
  */
529
- declare function sortRows(rows: Record<string, unknown>[], $sort?: Partial<Record<string, 1 | -1>>, tieBreak?: (row: Record<string, unknown>) => string | number): Record<string, unknown>[];
563
+ declare function sortRows(rows: Record<string, unknown>[], $sort?: Partial<Record<string, 1 | -1>>, tieBreak?: (row: Record<string, unknown>) => string | number, topK?: number): Record<string, unknown>[];
530
564
  /** Options for {@link projectRow}. */
531
565
  interface ProjectRowOptions {
532
566
  /**
@@ -560,6 +594,9 @@ interface ProjectRowOptions {
560
594
  *
561
595
  * Top-level and nested dot-paths are supported; exotic Mongo projection quirks
562
596
  * (array positional, `$slice`, etc.) are intentionally NOT replicated.
597
+ *
598
+ * Clones are `structuredClone`-equivalent. To project many rows the same way,
599
+ * compile once with {@link compileProjection}.
563
600
  */
564
601
  declare function projectRow(row: Record<string, unknown>, projection?: Record<string, 0 | 1>, opts?: ProjectRowOptions): Record<string, unknown>;
565
602
  //#endregion