@classytic/mongokit 3.39.0 → 3.40.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.
Files changed (51) 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-DQ7beTCR.mjs → PaginationEngine-CfT2Cxkj.mjs} +320 -145
  4. package/dist/{Repository-D7fDpyFx.mjs → Repository-CYhSC9ut.mjs} +844 -794
  5. package/dist/{Repository-IxI983_m.d.mts → Repository-pnLw1rG5.d.mts} +113 -47
  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-CRXjDa3N.d.mts → create-repository-BUx9vfG9.d.mts} +5 -5
  13. package/dist/{create-repository-DOe9E_5F.mjs → create-repository-BYyWzLbt.mjs} +2 -2
  14. package/dist/idempotency/index.d.mts +23 -0
  15. package/dist/idempotency/index.mjs +134 -0
  16. package/dist/{index-CmjNyulu.d.mts → index-SYF8N28D.d.mts} +37 -19
  17. package/dist/index.d.mts +105 -39
  18. package/dist/index.mjs +332 -676
  19. package/dist/indexes/verify-indexes.d.mts +14 -1
  20. package/dist/indexes/verify-indexes.mjs +54 -7
  21. package/dist/join-scope-DSAznE8E.mjs +168 -0
  22. package/dist/{mongooseToJsonSchema-waUVQwK4.mjs → mongooseToJsonSchema-C3k4ide0.mjs} +5 -1
  23. package/dist/{multi-tenant.plugin-B-tzNeYL.d.mts → multi-tenant.plugin-a3UhQARj.d.mts} +19 -3
  24. package/dist/{operations-CrRIoc8Z.d.mts → operations-XiFgG5US.d.mts} +36 -13
  25. package/dist/outbox/index.d.mts +1 -1
  26. package/dist/outbox/index.mjs +6 -4
  27. package/dist/pagination/PaginationEngine.d.mts +2 -2
  28. package/dist/pagination/PaginationEngine.mjs +1 -1
  29. package/dist/pagination/utils/limits.d.mts +1 -1
  30. package/dist/{pagination-BpIv2bdS.d.mts → pagination-D6psses0.d.mts} +116 -13
  31. package/dist/{plugin-options-BuvtOSZf.d.mts → plugin-options-CRhIvSzx.d.mts} +16 -2
  32. package/dist/plugins/index.d.mts +8 -3
  33. package/dist/plugins/index.mjs +98 -25
  34. package/dist/query-defaults-BxUcSOoF.mjs +208 -0
  35. package/dist/read-model/index.d.mts +66 -0
  36. package/dist/read-model/index.mjs +193 -0
  37. package/dist/{repository-ZbLtL7Nk.d.mts → repository-DNHfjAZX.d.mts} +36 -4
  38. package/dist/sync/index.d.mts +53 -0
  39. package/dist/sync/index.mjs +371 -0
  40. package/dist/tenant/index.mjs +2 -0
  41. package/dist/testkit/index.d.mts +40 -3
  42. package/dist/testkit/index.mjs +113 -2
  43. package/dist/{timestamp.plugin-B5eiaItQ.mjs → timestamp.plugin-zfdLYZiO.mjs} +135 -32
  44. package/dist/{to-plain-B8ufk3Qz.d.mts → to-plain-CjgEInWV.d.mts} +2 -2
  45. package/dist/transaction-BrRYtH_b.mjs +655 -0
  46. package/dist/{update-c_01A0if.mjs → update-WgeAUJ_I.mjs} +212 -33
  47. package/dist/utils/index.d.mts +1 -1
  48. package/dist/utils/index.mjs +1 -1
  49. package/dist/{validation-chain.plugin-Bju1D1Sn.mjs → validation-chain.plugin-CHUHWMtk.mjs} +204 -279
  50. package/dist/{validation-chain.plugin-DvrLJIbJ.d.mts → validation-chain.plugin-CTcNJCmV.d.mts} +27 -5
  51. package/package.json +18 -4
@@ -0,0 +1,66 @@
1
+ import { r as Repository } from "../Repository-pnLw1rG5.mjs";
2
+ import { BulkUpsertResult } from "@classytic/repo-core/repository";
3
+ //#region src/read-model/index.d.ts
4
+ declare const READ_MODEL_ERROR_CODES: {
5
+ readonly MERGE_IN_TRANSACTION: "mongokit.read_model.merge_in_transaction";
6
+ readonly ON_NOT_UNIQUE: "mongokit.read_model.on_not_unique";
7
+ readonly ON_MISSING_SCOPE: "mongokit.read_model.on_missing_scope";
8
+ readonly DEDUPE_NEEDS_TRANSACTION: "mongokit.read_model.dedupe_needs_transaction";
9
+ readonly DEDUPE_KEY_MISSING: "mongokit.read_model.dedupe_key_missing";
10
+ readonly INCREMENT_FAILED: "mongokit.read_model.increment_failed";
11
+ readonly TOO_LARGE: "mongokit.read_model.too_large";
12
+ };
13
+ /** Caller scope forwarded to every repository call (tenant, bypass, attribution). */
14
+ type ReadModelScope = Record<string, unknown>;
15
+ interface IncrementGrain {
16
+ /** The read-model key (without the policy scope, which the repository adds). */
17
+ key: Record<string, unknown>;
18
+ inc: Record<string, number>;
19
+ setOnInsert?: Record<string, unknown>;
20
+ /** Identity of the event this grain comes from; required when a ledger is given. */
21
+ dedupeKey?: string;
22
+ }
23
+ /** Sum grains per key, apply them in one upsert; with a ledger, count each dedupeKey once. */
24
+ declare function applyIncrements<TDoc>(target: Repository<TDoc>, grains: readonly IncrementGrain[], options?: ReadModelScope & {
25
+ session?: unknown;
26
+ /** Repository over a collection unique on (scope, `dedupeKey`). */
27
+ ledger?: Repository<unknown>;
28
+ }): Promise<BulkUpsertResult>;
29
+ interface RebuildOptions {
30
+ /** Source stages producing one row per read-model grain (with every `on` field except scope fields). */
31
+ pipeline: readonly unknown[];
32
+ /** Fields identifying a grain; must equal a unique index of the target and include its tenant field. */
33
+ on: readonly string[];
34
+ whenMatched?: 'replace' | 'merge';
35
+ /** Caller scope (tenant etc.), applied to the source read and stamped on the target rows. */
36
+ scope?: ReadModelScope;
37
+ session?: unknown;
38
+ }
39
+ /** Rebuild the scope's slice of `target` from `source` with one `$merge`, then drop stale grains. */
40
+ declare function rebuildInto<TSrc, TDoc>(source: Repository<TSrc>, target: Repository<TDoc>, options: RebuildOptions): Promise<{
41
+ rebuildId: string;
42
+ removed: number;
43
+ }>;
44
+ interface ReconcileOptions {
45
+ pipeline: readonly unknown[];
46
+ on: readonly string[];
47
+ /** Fields compared between the recomputed and the stored row. */
48
+ measures: readonly string[];
49
+ scope?: ReadModelScope;
50
+ /** Refuse past this many rows on either side (both are held in memory). Default 100,000. */
51
+ maxRows?: number;
52
+ }
53
+ interface DriftRow {
54
+ key: Record<string, unknown>;
55
+ /** Recomputed measures, `null` when the source produces no such grain. */
56
+ expected: Record<string, unknown> | null;
57
+ /** Stored measures, `null` when the read model lacks the grain. */
58
+ actual: Record<string, unknown> | null;
59
+ }
60
+ /** Recompute the scope's grains from `source` and report every one that differs in `target`. */
61
+ declare function reconcile<TSrc, TDoc>(source: Repository<TSrc>, target: Repository<TDoc>, options: ReconcileOptions): Promise<{
62
+ drift: DriftRow[];
63
+ compared: number;
64
+ }>;
65
+ //#endregion
66
+ export { DriftRow, IncrementGrain, READ_MODEL_ERROR_CODES, ReadModelScope, RebuildOptions, ReconcileOptions, applyIncrements, rebuildInto, reconcile };
@@ -0,0 +1,193 @@
1
+ import { t as createError } from "../error-BuByZwKU.mjs";
2
+ import { i as scopeForCollection, o as tenantFieldsOf } from "../join-scope-DSAznE8E.mjs";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region src/read-model/index.ts
5
+ /**
6
+ * `@classytic/mongokit/read-model` — maintain and verify a derived (read) collection.
7
+ *
8
+ * - {@link applyIncrements}: grains summed per key, then ONE `bulkUpsert` (`$inc` + `$setOnInsert`)
9
+ * inside the caller's unit of work. With a `ledger`, each grain's `dedupeKey` is recorded in the
10
+ * SAME transaction and a key already recorded is skipped, so a replayed event counts once.
11
+ * - {@link rebuildInto}: the source pipeline (scoped by the source repository) ends in a `$merge`
12
+ * on a unique `on`; grains of the scope the source no longer produces are then removed.
13
+ * `$merge` cannot run in a transaction, so a session inside one is refused.
14
+ * - {@link reconcile}: recompute from the source and compare with the read model; returns drift.
15
+ */
16
+ const READ_MODEL_ERROR_CODES = {
17
+ MERGE_IN_TRANSACTION: "mongokit.read_model.merge_in_transaction",
18
+ ON_NOT_UNIQUE: "mongokit.read_model.on_not_unique",
19
+ ON_MISSING_SCOPE: "mongokit.read_model.on_missing_scope",
20
+ DEDUPE_NEEDS_TRANSACTION: "mongokit.read_model.dedupe_needs_transaction",
21
+ DEDUPE_KEY_MISSING: "mongokit.read_model.dedupe_key_missing",
22
+ INCREMENT_FAILED: "mongokit.read_model.increment_failed",
23
+ TOO_LARGE: "mongokit.read_model.too_large"
24
+ };
25
+ function isRecord(value) {
26
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27
+ }
28
+ function inTransaction(session) {
29
+ const s = session;
30
+ return typeof s?.inTransaction === "function" && s.inTransaction();
31
+ }
32
+ /** Sum grains per key, apply them in one upsert; with a ledger, count each dedupeKey once. */
33
+ async function applyIncrements(target, grains, options = {}) {
34
+ const { ledger, ...scope } = options;
35
+ let live = grains;
36
+ if (ledger) {
37
+ if (!inTransaction(scope.session)) throw createError(500, "[mongokit] applyIncrements: a dedupe ledger is only correct inside a transaction", { code: READ_MODEL_ERROR_CODES.DEDUPE_NEEDS_TRANSACTION });
38
+ if (grains.some((g) => !g.dedupeKey)) throw createError(400, "[mongokit] applyIncrements: every grain needs a dedupeKey when a ledger is given", { code: READ_MODEL_ERROR_CODES.DEDUPE_KEY_MISSING });
39
+ const keys = [...new Set(grains.flatMap((g) => g.dedupeKey ? [g.dedupeKey] : []))];
40
+ const seen = new Set((await ledger.findAll({ dedupeKey: { $in: keys } }, {
41
+ ...scope,
42
+ select: "dedupeKey",
43
+ lean: true
44
+ })).map((d) => d).filter(isRecord).map((d) => String(d.dedupeKey)));
45
+ const fresh = keys.filter((k) => !seen.has(k));
46
+ if (fresh.length > 0) await ledger.createMany(fresh.map((dedupeKey) => ({ dedupeKey })), scope);
47
+ const freshSet = new Set(fresh);
48
+ const taken = /* @__PURE__ */ new Set();
49
+ live = grains.filter((g) => {
50
+ const k = g.dedupeKey;
51
+ if (!k || !freshSet.has(k) || taken.has(k)) return false;
52
+ taken.add(k);
53
+ return true;
54
+ });
55
+ }
56
+ if (live.length === 0) return {
57
+ results: [],
58
+ inserted: 0,
59
+ updated: 0,
60
+ unchanged: 0,
61
+ failed: 0
62
+ };
63
+ const summed = /* @__PURE__ */ new Map();
64
+ for (const g of live) {
65
+ const id = JSON.stringify(Object.keys(g.key).sort().map((k) => [k, g.key[k]]));
66
+ const slot = summed.get(id) ?? {
67
+ key: g.key,
68
+ inc: {},
69
+ setOnInsert: { ...g.setOnInsert }
70
+ };
71
+ for (const [field, n] of Object.entries(g.inc)) slot.inc[field] = (slot.inc[field] ?? 0) + n;
72
+ summed.set(id, slot);
73
+ }
74
+ const rows = [...summed.values()].map((s) => ({
75
+ ...s.key,
76
+ ...s.inc,
77
+ ...s.setOnInsert
78
+ }));
79
+ const first = [...summed.values()][0];
80
+ const result = await target.bulkUpsert(rows, {
81
+ ...scope,
82
+ key: Object.keys(first?.key ?? {}),
83
+ inc: [...new Set([...summed.values()].flatMap((s) => Object.keys(s.inc)))],
84
+ setOnInsert: [...new Set([...summed.values()].flatMap((s) => Object.keys(s.setOnInsert)))]
85
+ });
86
+ if (result.failed > 0) throw createError(500, `[mongokit] applyIncrements: ${result.failed} grain(s) failed; the unit of work must abort`, {
87
+ code: READ_MODEL_ERROR_CODES.INCREMENT_FAILED,
88
+ meta: { failures: result.results.filter((r) => r.outcome === "failed") }
89
+ });
90
+ return result;
91
+ }
92
+ function contextOf(scope) {
93
+ return {
94
+ operation: "aggregatePipeline",
95
+ model: "read-model",
96
+ ...scope
97
+ };
98
+ }
99
+ /** The target's policy predicate for this scope (tenant + live docs), as equality fields. */
100
+ function targetScope(target, scope) {
101
+ const predicate = scopeForCollection(target.Model.collection.collectionName, {
102
+ connection: target.Model.db,
103
+ context: contextOf(scope),
104
+ operation: "aggregatePipeline"
105
+ });
106
+ if (!predicate) return {};
107
+ const parts = Array.isArray(predicate.$and) ? predicate.$and.filter(isRecord) : [predicate];
108
+ return Object.assign({}, ...parts);
109
+ }
110
+ /** Rebuild the scope's slice of `target` from `source` with one `$merge`, then drop stale grains. */
111
+ async function rebuildInto(source, target, options) {
112
+ const scope = options.scope ?? {};
113
+ if (inTransaction(options.session)) throw createError(500, "[mongokit] rebuildInto: $merge cannot run inside a transaction", { code: READ_MODEL_ERROR_CODES.MERGE_IN_TRANSACTION });
114
+ const on = [...options.on];
115
+ const missing = tenantFieldsOf(target.Model).filter((f) => !on.includes(f));
116
+ if (missing.length > 0) throw createError(400, `[mongokit] rebuildInto: 'on' must include the target's tenant field(s) ${missing.join(", ")}`, { code: READ_MODEL_ERROR_CODES.ON_MISSING_SCOPE });
117
+ if (!target.Model.schema.indexes().some(([fields, opts]) => opts.unique === true && sameSet(Object.keys(fields), on))) throw createError(400, `[mongokit] rebuildInto: $merge needs a unique index on exactly (${on.join(", ")})`, { code: READ_MODEL_ERROR_CODES.ON_NOT_UNIQUE });
118
+ const rebuildId = randomUUID();
119
+ const stamp = {
120
+ ...targetScope(target, scope),
121
+ _rebuildId: rebuildId
122
+ };
123
+ const stages = [
124
+ ...options.pipeline,
125
+ { $set: stamp },
126
+ { $merge: {
127
+ into: target.Model.collection.collectionName,
128
+ on,
129
+ whenMatched: options.whenMatched ?? "replace",
130
+ whenNotMatched: "insert"
131
+ } }
132
+ ];
133
+ await source.aggregatePipeline(stages, {
134
+ ...scope,
135
+ ...options.session ? { session: options.session } : {}
136
+ });
137
+ return {
138
+ rebuildId,
139
+ removed: (await target.deleteMany({ _rebuildId: { $ne: rebuildId } }, {
140
+ ...scope,
141
+ ...options.session ? { session: options.session } : {}
142
+ })).deletedCount
143
+ };
144
+ }
145
+ function sameSet(a, b) {
146
+ return a.length === b.length && a.every((x) => b.includes(x));
147
+ }
148
+ function canonical(value) {
149
+ if (value === null || value === void 0) return "null";
150
+ if (isRecord(value) && value._bsontype === "Decimal128") return String(Number(String(value)));
151
+ return JSON.stringify(value);
152
+ }
153
+ /** Recompute the scope's grains from `source` and report every one that differs in `target`. */
154
+ async function reconcile(source, target, options) {
155
+ const scope = options.scope ?? {};
156
+ const maxRows = options.maxRows ?? 1e5;
157
+ const stamp = targetScope(target, scope);
158
+ const keyFields = options.on.filter((f) => !(f in stamp));
159
+ const pick = (row, fields) => Object.fromEntries(fields.map((f) => [f, row[f] ?? null]));
160
+ const id = (row) => keyFields.map((f) => canonical(row[f])).join("|");
161
+ const expectedRows = await source.aggregatePipeline([...options.pipeline, { $limit: maxRows + 1 }], scope);
162
+ const actualRows = (await target.findAll({}, {
163
+ ...scope,
164
+ select: [...keyFields, ...options.measures].join(" "),
165
+ limit: maxRows + 1,
166
+ lean: true
167
+ })).map((d) => d).filter(isRecord);
168
+ if (expectedRows.length > maxRows || actualRows.length > maxRows) throw createError(500, `[mongokit] reconcile: more than ${maxRows} grains in scope; reconcile a narrower scope`, { code: READ_MODEL_ERROR_CODES.TOO_LARGE });
169
+ const actual = new Map(actualRows.map((r) => [id(r), r]));
170
+ const drift = [];
171
+ const seen = /* @__PURE__ */ new Set();
172
+ for (const e of expectedRows) {
173
+ const k = id(e);
174
+ seen.add(k);
175
+ const a = actual.get(k);
176
+ if (!a || options.measures.some((m) => canonical(e[m]) !== canonical(a[m]))) drift.push({
177
+ key: pick(e, keyFields),
178
+ expected: pick(e, options.measures),
179
+ actual: a ? pick(a, options.measures) : null
180
+ });
181
+ }
182
+ for (const [k, a] of actual) if (!seen.has(k)) drift.push({
183
+ key: pick(a, keyFields),
184
+ expected: null,
185
+ actual: pick(a, options.measures)
186
+ });
187
+ return {
188
+ drift,
189
+ compared: expectedRows.length
190
+ };
191
+ }
192
+ //#endregion
193
+ export { READ_MODEL_ERROR_CODES, applyIncrements, rebuildInto, reconcile };
@@ -1,4 +1,4 @@
1
- import { _ as PopulateSpec, g as ObjectId, h as FilterQuery, i as CountStrategy, p as AnyDocument, v as ReadPreferenceType, x as SortSpec, y as SelectSpec } from "./pagination-BpIv2bdS.mjs";
1
+ import { A as ObjectId, D as AnyDocument, F as SortSpec, M as ReadPreferenceType, N as SelectSpec, _ as QueryDefaults, b as WriteConcernSpec, i as CountStrategy, j as PopulateSpec, k as FilterQuery, p as AggregateDefaults, v as ReadConcernLevel } from "./pagination-D6psses0.mjs";
2
2
  import { r as DocField } from "./type-utils-CLzRFtIA.mjs";
3
3
  import { DistributionConfig, DistributionMissHandler, RepoCapabilities, RepositoryBase } from "@classytic/repo-core/repository";
4
4
  import { Model, PipelineStage } from "mongoose";
@@ -199,6 +199,13 @@ interface RepositoryOptions {
199
199
  * ```
200
200
  */
201
201
  requirePlugins?: readonly RepositoryPluginName[];
202
+ /**
203
+ * Time bound and concerns for every command this repository issues. A per-call value
204
+ * wins; unset fields fall back to `configureQueryDefaults` (see `repository/query-defaults.ts`).
205
+ */
206
+ queryDefaults?: QueryDefaults;
207
+ /** Aggregation-only defaults, consulted before `queryDefaults`. */
208
+ aggregateDefaults?: AggregateDefaults;
202
209
  /**
203
210
  * Declare the collection's shard key so every filter-taking verb
204
211
  * (`getAll`, `findAll`, `getOne`, `count`, `updateMany`, `aggregate`, …)
@@ -327,6 +334,13 @@ interface RepositoryContext {
327
334
  hint?: string | Record<string, 1 | -1>;
328
335
  /** Maximum execution time in milliseconds */
329
336
  maxTimeMS?: number;
337
+ /** Per-call read concern / comment / write concern / disk use (see `queryDefaults`). */
338
+ readConcern?: ReadConcernLevel;
339
+ comment?: string;
340
+ writeConcern?: WriteConcernSpec;
341
+ allowDiskUse?: boolean;
342
+ /** Collections the caller asserts are company-wide (joins only). */
343
+ unscopedJoins?: readonly string[];
330
344
  /** Count strategy for offset pagination. See {@link CountStrategy}. */
331
345
  countStrategy?: CountStrategy;
332
346
  /** Ceiling for `countStrategy: 'capped'` (default 10000). */
@@ -363,8 +377,23 @@ interface RepositoryContext {
363
377
  _cacheResolved?: unknown;
364
378
  /** Cache hit status (internal — `'fresh'` or `'stale'`) */
365
379
  _cacheStatus?: 'fresh' | 'stale' | 'miss' | 'disabled' | 'bypass';
366
- /** IDs to cascade delete (internal) */
367
- _cascadeIds?: unknown[];
380
+ /**
381
+ * Parent key values captured before a delete, by `parentKey` (internal —
382
+ * cascade plugin). Restrict, detach and cascade all read these.
383
+ */
384
+ _cascadeParentKeys?: Map<string, unknown[]>;
385
+ /**
386
+ * Memo shared by every hook of THIS operation: N plugins that need the same read pay for
387
+ * one. Scoped to the operation, so it can never serve a value across writes. A rejected
388
+ * load is forgotten, so the next caller retries.
389
+ */
390
+ memo?: <T>(key: string, load: () => Promise<T>) => Promise<T>;
391
+ /**
392
+ * The persisted document an id-addressed operation targets, as it is BEFORE the write,
393
+ * read once under the operation's session (not tenant-filtered — the write's own filter
394
+ * decides whether it applies). Use this instead of `Model.findById(context.id)` in a hook.
395
+ */
396
+ loadTarget?: () => Promise<Record<string, unknown> | null>;
368
397
  /** Custom context data from plugins */
369
398
  [key: string]: unknown;
370
399
  }
@@ -432,7 +461,7 @@ interface RepositoryInstance extends RepositoryBase {
432
461
  [key: string]: unknown;
433
462
  }
434
463
  /** Repository operation names */
435
- type RepositoryOperation = 'create' | 'createMany' | 'update' | 'updateMany' | 'findOneAndUpdate' | 'delete' | 'deleteMany' | 'restore' | 'getById' | 'getByQuery' | 'getOne' | 'getAll' | 'findAll' | 'getOrCreate' | 'count' | 'exists' | 'distinct' | 'aggregate' | 'aggregatePaginate' | 'aggregatePipeline' | 'aggregatePipelinePaginate' | 'lookupPopulate' | 'bulkWrite' | 'claim' | 'claimVersion' | 'cursor' | 'watch';
464
+ type RepositoryOperation = 'create' | 'createMany' | 'update' | 'updateMany' | 'findOneAndUpdate' | 'delete' | 'deleteMany' | 'restore' | 'getById' | 'getByQuery' | 'getOne' | 'getAll' | 'findAll' | 'getOrCreate' | 'count' | 'exists' | 'distinct' | 'aggregate' | 'aggregatePaginate' | 'aggregatePipeline' | 'aggregatePipelinePaginate' | 'lookupPopulate' | 'bulkWrite' | 'bulkUpsert' | 'claim' | 'claimVersion' | 'cursor' | 'iterate' | 'watch';
436
465
  /** Event lifecycle phases */
437
466
  type EventPhase = 'before' | 'after' | 'error';
438
467
  /** Repository event names (generated from template literals) */
@@ -528,6 +557,9 @@ type AllPluginMethods<TDoc> = {
528
557
  deleteSubdocument(parentId: string | ObjectId, arrayPath: DocField<TDoc>, subId: string | ObjectId, options?: Record<string, unknown>): Promise<TDoc>;
529
558
  restore(id: string | ObjectId, options?: {
530
559
  session?: unknown;
560
+ organizationId?: unknown;
561
+ bypassTenant?: boolean;
562
+ [key: string]: unknown;
531
563
  }): Promise<TDoc>;
532
564
  getDeleted(params?: {
533
565
  filters?: Record<string, unknown>;
@@ -0,0 +1,53 @@
1
+ import { ClientSession, Connection, Model } from "mongoose";
2
+ import { ChangeLogStore, CommandStreamStore, CommandStreamTx } from "@classytic/repo-core/sync";
3
+ //#region src/sync/index.d.ts
4
+ interface ChangeLogModelOptions {
5
+ /** Entries collection. Default `sync_changes`; its counter lives in `<collection>_counter`. */
6
+ collection?: string;
7
+ /** Mongoose model name prefix. Default `SyncChange`. Must be unique per connection. */
8
+ modelName?: string;
9
+ }
10
+ interface ChangeLogModels {
11
+ entries: Model<Record<string, unknown>>;
12
+ counter: Model<Record<string, unknown>>;
13
+ /** The counter document's id (one feed per entries collection). */
14
+ feed: string;
15
+ }
16
+ declare function createChangeLogModels(connection: Connection, options?: ChangeLogModelOptions): ChangeLogModels;
17
+ /** The mongo change feed: the `ChangeLogStore` contract plus superseded-entry compaction. */
18
+ interface MongoChangeLogStore extends ChangeLogStore {
19
+ /**
20
+ * Delete every entry a later COMMITTED entry of the same `(scope, docId)` replaced. Safe for any client
21
+ * cursor: entries carry whole documents, so a reader only ever needs the latest one; tombstones and
22
+ * each document's latest entry stay. Idempotent; run it on a schedule.
23
+ */
24
+ compactSuperseded(options?: {
25
+ batchSize?: number;
26
+ }): Promise<{
27
+ removed: number;
28
+ }>;
29
+ }
30
+ declare function createChangeLogStore(models: ChangeLogModels): MongoChangeLogStore;
31
+ interface CommandStreamModelOptions {
32
+ /** Collection prefix: `<prefix>_streams`, `<prefix>_verdicts`, `<prefix>_aliases`. Default `sync`. */
33
+ prefix?: string;
34
+ /** Mongoose model name prefix. Default `SyncStream`. Must be unique per connection. */
35
+ modelName?: string;
36
+ }
37
+ interface CommandStreamModels {
38
+ streams: Model<Record<string, unknown>>;
39
+ verdicts: Model<Record<string, unknown>>;
40
+ aliases: Model<Record<string, unknown>>;
41
+ }
42
+ declare function createCommandStreamModels(connection: Connection, options?: CommandStreamModelOptions): CommandStreamModels;
43
+ interface MongoCommandStreamTx extends CommandStreamTx {
44
+ readonly session: ClientSession;
45
+ }
46
+ /**
47
+ * The mongo {@link CommandStreamStore} (replica set). Proven by `runCommandStreamStoreConformance`.
48
+ * Concurrent transactions on one stream write-conflict on its document and `withTransaction`
49
+ * retries the loser, which then reads the winner's watermark.
50
+ */
51
+ declare function createCommandStreamStore(connection: Pick<Connection, 'startSession'>, models: CommandStreamModels, changes: ChangeLogStore): CommandStreamStore<MongoCommandStreamTx>;
52
+ //#endregion
53
+ export { ChangeLogModelOptions, ChangeLogModels, CommandStreamModelOptions, CommandStreamModels, MongoChangeLogStore, MongoCommandStreamTx, createChangeLogModels, createChangeLogStore, createCommandStreamModels, createCommandStreamStore };