@classytic/mongokit 3.40.0 → 3.41.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.
Files changed (46) hide show
  1. package/README.md +71 -0
  2. package/dist/{PaginationEngine-D-P19Y9m.d.mts → PaginationEngine-CRHyuUtw.d.mts} +17 -3
  3. package/dist/{PaginationEngine-Dvl8jcRY.mjs → PaginationEngine-DZnkbSrr.mjs} +299 -140
  4. package/dist/{Repository-BMgPSkqZ.mjs → Repository-DlhPbXlG.mjs} +571 -175
  5. package/dist/{Repository-CR8Y9g2E.d.mts → Repository-pnLw1rG5.d.mts} +57 -16
  6. package/dist/actions/index.d.mts +1 -1
  7. package/dist/actions/index.mjs +1 -1
  8. package/dist/adapter/index.mjs +1 -1
  9. package/dist/{adapter-BrVfPFSW.mjs → adapter-CkMoMF83.mjs} +1 -1
  10. package/dist/ai/index.d.mts +1 -1
  11. package/dist/better-auth/index.mjs +2 -2
  12. package/dist/{create-repository-CAle1MSU.d.mts → create-repository-BUx9vfG9.d.mts} +5 -5
  13. package/dist/{create-repository-BzBUMvd1.mjs → create-repository-Bg0H5rte.mjs} +2 -2
  14. package/dist/{index-CmjNyulu.d.mts → index-SYF8N28D.d.mts} +37 -19
  15. package/dist/index.d.mts +69 -15
  16. package/dist/index.mjs +57 -11
  17. package/dist/join-scope-DSAznE8E.mjs +168 -0
  18. package/dist/{mongooseToJsonSchema-waUVQwK4.mjs → mongooseToJsonSchema-C3k4ide0.mjs} +5 -1
  19. package/dist/{multi-tenant.plugin-CBgCo9im.d.mts → multi-tenant.plugin-a3UhQARj.d.mts} +2 -2
  20. package/dist/{operations-CrRIoc8Z.d.mts → operations-XiFgG5US.d.mts} +36 -13
  21. package/dist/outbox/index.d.mts +1 -1
  22. package/dist/outbox/index.mjs +1 -1
  23. package/dist/pagination/PaginationEngine.d.mts +2 -2
  24. package/dist/pagination/PaginationEngine.mjs +1 -1
  25. package/dist/pagination/utils/limits.d.mts +1 -1
  26. package/dist/{pagination-BpIv2bdS.d.mts → pagination-D6psses0.d.mts} +116 -13
  27. package/dist/{plugin-options-Zk46lD6F.d.mts → plugin-options-CRhIvSzx.d.mts} +2 -2
  28. package/dist/plugins/index.d.mts +3 -3
  29. package/dist/plugins/index.mjs +2 -2
  30. package/dist/query-defaults-BxUcSOoF.mjs +208 -0
  31. package/dist/read-model/index.d.mts +66 -0
  32. package/dist/read-model/index.mjs +193 -0
  33. package/dist/{repository-odl03582.d.mts → repository-DNHfjAZX.d.mts} +16 -2
  34. package/dist/sync/index.d.mts +15 -2
  35. package/dist/sync/index.mjs +38 -1
  36. package/dist/testkit/index.d.mts +40 -3
  37. package/dist/testkit/index.mjs +113 -2
  38. package/dist/{timestamp.plugin-DF0gJB6p.mjs → timestamp.plugin-DvZlnKEc.mjs} +50 -31
  39. package/dist/{to-plain-k7SjNlSS.d.mts → to-plain-CjgEInWV.d.mts} +2 -2
  40. package/dist/{transaction-B5L9glO0.mjs → transaction-DmiB4k2P.mjs} +155 -6
  41. package/dist/{update-DG0fCwRJ.mjs → update-WgeAUJ_I.mjs} +44 -22
  42. package/dist/utils/index.d.mts +1 -1
  43. package/dist/utils/index.mjs +1 -1
  44. package/dist/{validation-chain.plugin-DYp9E7K2.d.mts → validation-chain.plugin-CTcNJCmV.d.mts} +4 -4
  45. package/dist/{validation-chain.plugin-BLY2gLEk.mjs → validation-chain.plugin-D1WsGCYr.mjs} +15 -3
  46. package/package.json +10 -5
package/README.md CHANGED
@@ -170,6 +170,18 @@ const next = await repo.getAll({
170
170
  });
171
171
  ```
172
172
 
173
+ Every sort gets a unique `_id` tiebreaker and `hasNext` comes from a limit+1 fetch. Offset pages
174
+ report `{ total, totalIsEstimate, countedAt }` under every `countStrategy` (`exact`, `capped`,
175
+ `estimated`, `none`, `cached`); a `skip` past `maxOffset` (default 100,000) is refused with
176
+ `mongokit.pagination.offset_too_deep`. A cursor is bound to its collection, filter (tenant
177
+ included) and collation: replayed elsewhere it is `repo.cursor.scope_mismatch` (repo-core `CURSOR_ERROR_CODES`); set
178
+ `cursorSecret` so tampering is detected too.
179
+
180
+ ```ts
181
+ // Boot: the deployment's list policy.
182
+ configurePaginationDefaults({ defaultCountStrategy: 'capped', cursorSecret, maxOffset: 50_000 });
183
+ ```
184
+
173
185
  ### Transactions
174
186
 
175
187
  ```ts
@@ -189,6 +201,55 @@ await orderRepo.withTransaction(async (txRepo) => {
189
201
  });
190
202
  ```
191
203
 
204
+ ### Time Bounds And Concerns (`queryDefaults`)
205
+
206
+ ```ts
207
+ // Boot: every command is bounded; the boot check fails without it (and without CSOT timeoutMS).
208
+ await mongoose.connect(uri, { timeoutMS: 30_000 });
209
+ configureQueryDefaults({ maxTimeMS: 5_000, writeConcern: { w: 'majority' } });
210
+ configureAggregateDefaults({ maxTimeMs: 15_000 });
211
+ assertQueryDefaultsConfigured(mongoose.connection);
212
+
213
+ // Per repository, and per call (per call always wins):
214
+ const repo = new Repository(Order, plugins, {}, { queryDefaults: { comment: 'orders' } });
215
+ await repo.findAll(filter, { maxTimeMS: 500 });
216
+ ```
217
+
218
+ ### Joins Are Scoped
219
+
220
+ A `$lookup` / `$unionWith` / `$graphLookup` reads the joined collection under its own repository's
221
+ tenant and soft-delete scope. Take `from` from `Model.collection.name`: a name no model owns throws
222
+ `mongokit.join.unknown_collection`, a collection no repository governs throws
223
+ `mongokit.join.ungoverned_collection` (list a company-wide one in `unscopedJoins`).
224
+
225
+ ### Batch Reads (`getByIds`)
226
+
227
+ ```ts
228
+ const byId = await repo.getByIds(ids, { select: 'name sku' }); // Map, chunked $in
229
+ const rows = await repo.getByIds(ids, { preserveOrder: true, chunkSize: 5_000 }); // aligned, undefined = miss
230
+ ```
231
+
232
+ ### Read Models (`bulkUpsert`, `@classytic/mongokit/read-model`)
233
+
234
+ ```ts
235
+ const res = await totals.bulkUpsert(rows, { key: ['period', 'rate'], inc: ['tax'], organizationId });
236
+ // res.results[i].outcome: 'inserted' | 'updated' | 'unchanged' | 'failed' (+ code). Needs MongoDB 8.0.
237
+
238
+ import { applyIncrements, rebuildInto, reconcile } from '@classytic/mongokit/read-model';
239
+ await applyIncrements(totals, grains, { session, ledger, organizationId }); // inside the posting transaction
240
+ await rebuildInto(facts, totals, { pipeline, on: ['organizationId', 'period', 'rate'], scope: { organizationId } });
241
+ const { drift } = await reconcile(facts, totals, { pipeline, on, measures: ['tax'], scope: { organizationId } });
242
+ ```
243
+
244
+ ### Resumable Sweeps (`iterate`)
245
+
246
+ ```ts
247
+ for await (const { docs, checkpoint } of repo.iterate(filter, { batchSize: 1_000, after: saved, organizationId })) {
248
+ await process(docs);
249
+ await saveCheckpoint(checkpoint); // resume later with { after: checkpoint }
250
+ }
251
+ ```
252
+
192
253
  ### Change Streams
193
254
 
194
255
  ```ts
@@ -430,6 +491,16 @@ Atlas **`$search` / `$vectorSearch`** indexes are Atlas-only and don't exist on
430
491
 
431
492
  All accept `{ replset?, dbName?, uri? }`.
432
493
 
494
+ ### Query-plan gate
495
+
496
+ ```ts
497
+ import { assertQueryPlan } from '@classytic/mongokit/testkit';
498
+ // The connection must be opened with { monitorCommands: true }.
499
+ await assertQueryPlan(() => repo.getAll({ filters: { status: 'open' }, sort: { at: -1 } }), {
500
+ connection, minDocs: 1_000, leadingKeys: ['status', 'at'], // fails on COLLSCAN / unindexed SORT
501
+ });
502
+ ```
503
+
433
504
  ## Kernel Conformance — `@classytic/mongokit/kernel-conformance`
434
505
 
435
506
  An **executable contract suite** for domain kernels built on `defineModels()`. A kernel that
@@ -1,5 +1,6 @@
1
- import { c as MongokitPageExtras, f as CursorSecret, i as CountStrategy, l as OffsetPaginationOptions, p as AnyDocument, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig } from "./pagination-BpIv2bdS.mjs";
1
+ import { D as AnyDocument, c as MongokitPageExtras, f as CursorSecret, i as CountStrategy, l as OffsetPaginationOptions, s as KeysetPaginationOptions, t as AggregatePaginationOptions, u as PaginationConfig } from "./pagination-D6psses0.mjs";
2
2
  import { Model } from "mongoose";
3
+ import { TtlMemo } from "@classytic/repo-core/cache";
3
4
  import { AggregatePaginationResult, KeysetPaginationResult, OffsetPaginationResult } from "@classytic/repo-core/pagination";
4
5
  //#region src/pagination/PaginationEngine.d.ts
5
6
  /**
@@ -18,11 +19,17 @@ interface ResolvedPaginationConfig {
18
19
  defaultCountLimit: number;
19
20
  cursorSecret: CursorSecret | undefined;
20
21
  defaultMode: 'offset' | 'keyset' | undefined;
22
+ maxOffset: number;
23
+ countCacheTtlMs: number;
21
24
  }
22
25
  /**
23
26
  * Production-grade pagination engine for MongoDB
24
27
  * Supports offset, keyset (cursor), and aggregate pagination
25
28
  */
29
+ interface CountMemoKey {
30
+ key: string;
31
+ run: () => Promise<number>;
32
+ }
26
33
  declare class PaginationEngine<TDoc = AnyDocument> {
27
34
  readonly Model: Model<TDoc>;
28
35
  readonly config: ResolvedPaginationConfig;
@@ -37,6 +44,13 @@ declare class PaginationEngine<TDoc = AnyDocument> {
37
44
  * @param Model - Mongoose model to paginate
38
45
  * @param config - Pagination configuration
39
46
  */
47
+ /** Memoised capped counts for `countStrategy: 'cached'`, rebuilt when the TTL changes. */
48
+ private _memo;
49
+ /** The `cached` count memo (repo-core `createTtlMemo`: single flight, a failed count caches nothing). */
50
+ _countMemo(): TtlMemo<CountMemoKey, {
51
+ total: number;
52
+ countedAt: Date;
53
+ }>;
40
54
  constructor(Model: Model<TDoc, any, any, any>, config?: PaginationConfig);
41
55
  /** Memoized schema index lookup — avoids re-walking schema on every stream(). */
42
56
  private _getSchemaIndexes;
@@ -122,7 +136,7 @@ declare class PaginationEngine<TDoc = AnyDocument> {
122
136
  * limit: 20
123
137
  * });
124
138
  */
125
- aggregatePaginate(options?: AggregatePaginationOptions): Promise<AggregatePaginationResult<TDoc>>;
139
+ aggregatePaginate(options?: AggregatePaginationOptions): Promise<AggregatePaginationResult<TDoc, MongokitPageExtras>>;
126
140
  }
127
141
  //#endregion
128
- export { PaginationEngine as t };
142
+ export { PaginationEngine as n, CountMemoKey as t };