@classytic/mongokit 3.24.0 → 3.25.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/README.md +11 -2
- package/dist/{PaginationEngine-BnZ24ehC.d.mts → PaginationEngine-CBS1XGzp.d.mts} +2 -1
- package/dist/{Repository-ClOraFjT.mjs → Repository-KKR8jxfD.mjs} +70 -41
- package/dist/actions/index.d.mts +1 -1
- package/dist/adapter/index.d.mts +1 -1
- package/dist/adapter/index.mjs +1 -1
- package/dist/{adapter-BWgJ_Jdc.mjs → adapter-CLvUHiTW.mjs} +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.mjs +2 -2
- package/dist/core-62oo3N1C.d.mts +50 -0
- package/dist/{create-repository-CVupgeRG.mjs → create-repository-DA5VJh-3.mjs} +2 -2
- package/dist/{create-repository-Cn1LkvL7.d.mts → create-repository-HjMD9h9_.d.mts} +18 -5
- package/dist/{id-resolution-P2OzcyRl.mjs → id-resolution-BqDrk0S2.mjs} +12 -1
- package/dist/{index-DLQ6pjal.d.mts → index-DceInCTX.d.mts} +3 -1
- package/dist/index.d.mts +53 -213
- package/dist/index.mjs +1071 -922
- package/dist/{mongooseToJsonSchema-CVnrKFDq.mjs → mongooseToJsonSchema-CByt1VhQ.mjs} +1 -1
- package/dist/{multi-tenant.plugin-BjXXuc_g.d.mts → multi-tenant.plugin-DVyVGREb.d.mts} +117 -18
- package/dist/operations-C8IcCHF9.d.mts +463 -0
- package/dist/pagination/PaginationEngine.d.mts +1 -1
- package/dist/pagination-jSUdrACK.d.mts +158 -0
- package/dist/plugin-options-vFufQZMZ.d.mts +125 -0
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +3 -3
- package/dist/query/primitives/coercion.mjs +1 -1
- package/dist/query/primitives/geo.d.mts +1 -1
- package/dist/repository-DCHRWRXw.d.mts +511 -0
- package/dist/testkit/index.d.mts +1 -1
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-BhSX0Lu_.mjs → timestamp.plugin-B8252GuQ.mjs} +192 -46
- package/dist/{to-plain-g8-gsVxZ.d.mts → to-plain-DkF5px0R.d.mts} +12 -16
- package/dist/type-utils-CLzRFtIA.d.mts +163 -0
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +3 -3
- package/dist/{validation-chain.plugin-Dtt4tO7R.mjs → validation-chain.plugin-C3Vwp-Rk.mjs} +1 -1
- package/dist/{validation-chain.plugin-CFJbr964.d.mts → validation-chain.plugin-DxD15h-2.d.mts} +3 -1
- package/package.json +5 -3
- package/dist/types-BtMuhoX5.d.mts +0 -1830
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as isObjectIdInstance } from "./id-resolution-
|
|
1
|
+
import { r as isObjectIdInstance } from "./id-resolution-BqDrk0S2.mjs";
|
|
2
2
|
import mongoose from "mongoose";
|
|
3
3
|
import { applyFieldRules, collectFieldsToOmit } from "@classytic/repo-core/schema";
|
|
4
4
|
//#region src/utils/mongooseToJsonSchema.ts
|
|
@@ -1,16 +1,62 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { i as ObjectId } from "./core-62oo3N1C.mjs";
|
|
2
|
+
import { l as Plugin, p as RepositoryContext } from "./repository-DCHRWRXw.mjs";
|
|
3
|
+
import mongoose, { Connection } from "mongoose";
|
|
3
4
|
import { CacheAdapter as CacheAdapter$1, CacheOptions, RepositoryCacheHandle, RepositoryCachePluginOptions, cachePlugin } from "@classytic/repo-core/cache";
|
|
4
5
|
import { TenantConfig } from "@classytic/repo-core/tenant";
|
|
5
6
|
//#region src/plugins/audit-trail.plugin.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Audit write delivery mode.
|
|
9
|
+
*
|
|
10
|
+
* - `'best-effort'`: async fire-and-forget; never blocks or fails the
|
|
11
|
+
* operation. Observability-grade.
|
|
12
|
+
* - `'transactional'`: awaited insert sharing the operation's session; a
|
|
13
|
+
* failed audit write fails the operation.
|
|
14
|
+
*
|
|
15
|
+
* PRECISION: `'transactional'` is **atomic only when the operation actually
|
|
16
|
+
* runs inside a MongoDB transaction** (`withTransaction` / an explicit
|
|
17
|
+
* `session`). Without one, the mode still guarantees "the call does not
|
|
18
|
+
* return success unless the audit entry landed" — but the business write has
|
|
19
|
+
* already committed by the time the audit insert runs, so an audit failure
|
|
20
|
+
* rejects the call while leaving the business write in place. For
|
|
21
|
+
* compliance-grade "both or neither", wrap the operation in a transaction.
|
|
22
|
+
* Call {@link ensureAuditTrailReady} at boot so the collection and indexes
|
|
23
|
+
* are not created lazily inside the first transaction.
|
|
24
|
+
*/
|
|
25
|
+
type AuditTrailMode = 'best-effort' | 'transactional';
|
|
6
26
|
interface AuditTrailOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Delivery guarantee for audit writes (default: 'best-effort').
|
|
29
|
+
* See {@link AuditTrailMode}. `'transactional'` requires the repository's
|
|
30
|
+
* default `hooks: 'async'` mode — plugin registration throws on a
|
|
31
|
+
* `hooks: 'sync'` repository because awaited delivery cannot be honored.
|
|
32
|
+
*/
|
|
33
|
+
mode?: AuditTrailMode;
|
|
34
|
+
/**
|
|
35
|
+
* Mongoose connection to register the audit model on. Defaults to the
|
|
36
|
+
* audited model's OWN connection (`repo.Model.db`), so
|
|
37
|
+
* `mongoose.createConnection()` apps get audit entries in the same database
|
|
38
|
+
* as their data automatically. Pass this explicitly only to centralize
|
|
39
|
+
* audit storage on a different connection than the model lives on. Mirrors
|
|
40
|
+
* the `connection` option on the lock and usage stores.
|
|
41
|
+
*
|
|
42
|
+
* Note: `AuditTrailQuery` / `ensureAuditTrailReady` have no repository to
|
|
43
|
+
* infer from and default to the global `mongoose.connection` — pass the
|
|
44
|
+
* same `connection` there for multi-DB apps.
|
|
45
|
+
*/
|
|
46
|
+
connection?: Connection;
|
|
7
47
|
/** Operations to track (default: ['create', 'update', 'delete']) */
|
|
8
48
|
operations?: AuditOperation[];
|
|
9
49
|
/** Store field-level before/after diff on updates (default: true) */
|
|
10
50
|
trackChanges?: boolean;
|
|
11
51
|
/** Store full document snapshot on create (default: false — can be heavy) */
|
|
12
52
|
trackDocument?: boolean;
|
|
13
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Auto-purge after N days via MongoDB TTL index (default: undefined — keep
|
|
55
|
+
* forever). Retention is collection-level configuration: every plugin (and
|
|
56
|
+
* `AuditTrailQuery`) touching the same collection on the same connection
|
|
57
|
+
* must agree on `ttlDays`. A conflicting value throws at registration time
|
|
58
|
+
* instead of silently keeping whichever caller ran first.
|
|
59
|
+
*/
|
|
14
60
|
ttlDays?: number;
|
|
15
61
|
/** MongoDB collection name (default: 'audit_trails') */
|
|
16
62
|
collectionName?: string;
|
|
@@ -25,19 +71,19 @@ interface AuditTrailOptions {
|
|
|
25
71
|
*/
|
|
26
72
|
excludeFields?: string[];
|
|
27
73
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
74
|
+
* Callback fired when an audit write fails. Receives the error and the
|
|
75
|
+
* entry that failed to land.
|
|
30
76
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
77
|
+
* - In `'best-effort'` mode this is the ONLY failure signal — the
|
|
78
|
+
* operation has already succeeded. Forward to a dead-letter queue or
|
|
79
|
+
* error reporter; the default is a `console.warn`-level log. Note that
|
|
80
|
+
* a callback cannot make fire-and-forget writes atomic — if you need
|
|
81
|
+
* "no business write without an audit entry", use `mode:
|
|
82
|
+
* 'transactional'` inside a transaction.
|
|
83
|
+
* - In `'transactional'` mode the callback observes the failure before
|
|
84
|
+
* the operation itself rejects (and, inside a transaction, before the
|
|
85
|
+
* transaction aborts). Throwing from the callback is safe — the
|
|
86
|
+
* original write error still propagates.
|
|
41
87
|
*/
|
|
42
88
|
onWriteError?: (error: Error, entry: Omit<AuditEntry, 'timestamp'>) => void;
|
|
43
89
|
}
|
|
@@ -57,6 +103,38 @@ interface AuditEntry {
|
|
|
57
103
|
timestamp: Date;
|
|
58
104
|
}
|
|
59
105
|
declare function auditTrailPlugin(options?: AuditTrailOptions): Plugin;
|
|
106
|
+
/** Options for {@link ensureAuditTrailReady}. */
|
|
107
|
+
interface EnsureAuditTrailReadyOptions {
|
|
108
|
+
/** Connection the audit model lives on (default: `mongoose.connection`). */
|
|
109
|
+
connection?: Connection;
|
|
110
|
+
/** MongoDB collection name (default: 'audit_trails'). */
|
|
111
|
+
collectionName?: string;
|
|
112
|
+
/**
|
|
113
|
+
* TTL in days. Omit to reuse whatever the plugin already registered;
|
|
114
|
+
* pass explicitly (before the plugin, e.g. in a migration script) to
|
|
115
|
+
* declare retention up front — conflicts with an existing registration
|
|
116
|
+
* throw.
|
|
117
|
+
*/
|
|
118
|
+
ttlDays?: number;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create the audit collection and build its indexes BEFORE traffic begins.
|
|
122
|
+
*
|
|
123
|
+
* Call once at boot (after the plugin is registered, before serving
|
|
124
|
+
* requests) — especially with `mode: 'transactional'`: creating the
|
|
125
|
+
* collection or building indexes lazily inside the first transaction
|
|
126
|
+
* forces MongoDB catalog locks and transient `TransientTransactionError`
|
|
127
|
+
* retries. Idempotent; safe to call on every boot.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* const repo = new Repository(Model, [
|
|
132
|
+
* auditTrailPlugin({ mode: 'transactional', ttlDays: 90 }),
|
|
133
|
+
* ]);
|
|
134
|
+
* await ensureAuditTrailReady(); // collection + indexes exist before traffic
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
declare function ensureAuditTrailReady(options?: EnsureAuditTrailReadyOptions): Promise<void>;
|
|
60
138
|
interface AuditQueryOptions {
|
|
61
139
|
model?: string;
|
|
62
140
|
documentId?: string | ObjectId;
|
|
@@ -77,6 +155,24 @@ interface AuditQueryResult {
|
|
|
77
155
|
hasNext: boolean;
|
|
78
156
|
hasPrev: boolean;
|
|
79
157
|
}
|
|
158
|
+
/** Constructor options for {@link AuditTrailQuery}. */
|
|
159
|
+
interface AuditTrailQueryOptions {
|
|
160
|
+
/** MongoDB collection name (default: 'audit_trails') */
|
|
161
|
+
collectionName?: string;
|
|
162
|
+
/**
|
|
163
|
+
* TTL in days. Omit (recommended) to reuse whatever retention the writing
|
|
164
|
+
* plugin configured — readers don't need to know it. When passed
|
|
165
|
+
* explicitly it is conflict-checked against the existing registration and
|
|
166
|
+
* a mismatch throws.
|
|
167
|
+
*/
|
|
168
|
+
ttlDays?: number;
|
|
169
|
+
/**
|
|
170
|
+
* Mongoose connection the audit model lives on. Defaults to the global
|
|
171
|
+
* `mongoose.connection`. Pass the same connection you gave
|
|
172
|
+
* `auditTrailPlugin` for multi-DB apps.
|
|
173
|
+
*/
|
|
174
|
+
connection?: Connection;
|
|
175
|
+
}
|
|
80
176
|
/**
|
|
81
177
|
* Standalone audit trail query utility.
|
|
82
178
|
* Use this to query audits across all models — e.g., admin dashboards, audit APIs.
|
|
@@ -85,7 +181,10 @@ interface AuditQueryResult {
|
|
|
85
181
|
* ```typescript
|
|
86
182
|
* import { AuditTrailQuery } from '@classytic/mongokit';
|
|
87
183
|
*
|
|
88
|
-
* const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails'
|
|
184
|
+
* const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails' on mongoose.connection
|
|
185
|
+
*
|
|
186
|
+
* // Named connection (multi-DB apps)
|
|
187
|
+
* const tenantAudits = new AuditTrailQuery({ connection: tenantConnection });
|
|
89
188
|
*
|
|
90
189
|
* // All audits for an org
|
|
91
190
|
* const orgAudits = await auditQuery.query({ orgId: '...' });
|
|
@@ -117,7 +216,7 @@ interface AuditQueryResult {
|
|
|
117
216
|
*/
|
|
118
217
|
declare class AuditTrailQuery {
|
|
119
218
|
private model;
|
|
120
|
-
constructor(
|
|
219
|
+
constructor(options?: AuditTrailQueryOptions);
|
|
121
220
|
/**
|
|
122
221
|
* Get the underlying Mongoose model for custom queries
|
|
123
222
|
*/
|
|
@@ -483,4 +582,4 @@ declare function adminBypass(options: {
|
|
|
483
582
|
adminRoles: readonly string[];
|
|
484
583
|
}): (context: RepositoryContext, operation: string) => boolean;
|
|
485
584
|
//#endregion
|
|
486
|
-
export { AuditTrailMethods as C,
|
|
585
|
+
export { ensureAuditTrailReady as A, AuditTrailMethods as C, AuditTrailQueryOptions as D, AuditTrailQuery as E, EnsureAuditTrailReadyOptions as O, AuditQueryResult as S, AuditTrailOptions as T, RepositoryCachePluginOptions as _, DateSequentialIdOptions as a, AuditOperation as b, SequentialIdOptions as c, getNextSequence as d, prefixedId as f, RepositoryCacheHandle as g, CacheOptions as h, CustomIdOptions as i, auditTrailPlugin as k, customIdPlugin as l, CacheAdapter$1 as m, adminBypass as n, IdGenerator as o, sequentialId as p, multiTenantPlugin as r, PrefixedIdOptions as s, MultiTenantOptions as t, dateSequentialId as u, cachePlugin as v, AuditTrailMode as w, AuditQueryOptions as x, AuditEntry as y };
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { a as PopulateSpec, i as ObjectId, l as SortSpec, o as ReadPreferenceType, s as SelectSpec } from "./core-62oo3N1C.mjs";
|
|
2
|
+
import { r as CollationOptions } from "./pagination-jSUdrACK.mjs";
|
|
3
|
+
import { DeleteManyResult, DeleteResult, LookupPopulateResult, UpdateManyResult } from "@classytic/repo-core/repository";
|
|
4
|
+
import { PipelineStage, PopulateOptions } from "mongoose";
|
|
5
|
+
import { Filter } from "@classytic/repo-core/filter";
|
|
6
|
+
//#region src/query/LookupBuilder.d.ts
|
|
7
|
+
interface LookupOptions {
|
|
8
|
+
/** Collection to join with */
|
|
9
|
+
from: string;
|
|
10
|
+
/** Field from the input documents */
|
|
11
|
+
localField: string;
|
|
12
|
+
/** Field from the documents of the "from" collection */
|
|
13
|
+
foreignField: string;
|
|
14
|
+
/** Name of the new array field to add to the input documents */
|
|
15
|
+
as?: string;
|
|
16
|
+
/** Whether to unwrap array to single object */
|
|
17
|
+
single?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Field selection on the joined collection (shorthand for `$project`).
|
|
20
|
+
* Three accepted shapes — all map to a single `$project` stage:
|
|
21
|
+
* - `string` — CSV form (`'name,price'`); leading `-` marks an exclusion.
|
|
22
|
+
* - `readonly string[]` — array form (`['name', 'price']` / `['-status']`);
|
|
23
|
+
* mirrors `repo-core/lookup` `LookupSpec.select` for cross-kit parity.
|
|
24
|
+
* - `Record<string, 0 | 1>` — explicit Mongo projection map.
|
|
25
|
+
*/
|
|
26
|
+
select?: string | readonly string[] | Record<string, 0 | 1>;
|
|
27
|
+
/** Additional pipeline to run on the joined collection */
|
|
28
|
+
pipeline?: PipelineStage[];
|
|
29
|
+
/** Optional let variables for pipeline */
|
|
30
|
+
let?: Record<string, string>;
|
|
31
|
+
/** Query filter to apply before join (legacy, for aggregate.ts compatibility) */
|
|
32
|
+
query?: Record<string, unknown>;
|
|
33
|
+
/** Query options (legacy, for aggregate.ts compatibility) */
|
|
34
|
+
options?: {
|
|
35
|
+
session?: unknown;
|
|
36
|
+
};
|
|
37
|
+
/** Sanitize pipeline stages (default: true). Set false only for trusted server-side pipelines */
|
|
38
|
+
sanitize?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Joined-side filter, compiled into a `$match` stage inside the lookup
|
|
41
|
+
* pipeline AFTER the auto-generated join correlation. Mirrors
|
|
42
|
+
* `repo-core/lookup/types.ts#LookupSpec.where` and sqlitekit's
|
|
43
|
+
* predicate-on-the-JOIN behavior, so cross-kit code that narrows the
|
|
44
|
+
* foreign side (`where: eq('status', 'active')`) gets identical rows
|
|
45
|
+
* from both backends.
|
|
46
|
+
*
|
|
47
|
+
* Accepts Filter IR or a plain mongo query — `compileFilterToMongo`
|
|
48
|
+
* routes each correctly.
|
|
49
|
+
*/
|
|
50
|
+
where?: Filter | Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Fluent builder for MongoDB $lookup aggregation stage
|
|
54
|
+
* Optimized for custom field joins at scale
|
|
55
|
+
*/
|
|
56
|
+
declare class LookupBuilder {
|
|
57
|
+
private options;
|
|
58
|
+
constructor(from?: string);
|
|
59
|
+
/**
|
|
60
|
+
* Set the collection to join with
|
|
61
|
+
*/
|
|
62
|
+
from(collection: string): this;
|
|
63
|
+
/**
|
|
64
|
+
* Set the local field (source collection)
|
|
65
|
+
* IMPORTANT: This field should be indexed for optimal performance
|
|
66
|
+
*/
|
|
67
|
+
localField(field: string): this;
|
|
68
|
+
/**
|
|
69
|
+
* Set the foreign field (target collection)
|
|
70
|
+
* IMPORTANT: This field should be indexed (preferably unique) for optimal performance
|
|
71
|
+
*/
|
|
72
|
+
foreignField(field: string): this;
|
|
73
|
+
/**
|
|
74
|
+
* Set the output field name
|
|
75
|
+
* Defaults to the collection name if not specified
|
|
76
|
+
*/
|
|
77
|
+
as(fieldName: string): this;
|
|
78
|
+
/**
|
|
79
|
+
* Mark this lookup as returning a single document
|
|
80
|
+
* Automatically unwraps the array result to a single object or null
|
|
81
|
+
*/
|
|
82
|
+
single(isSingle?: boolean): this;
|
|
83
|
+
/**
|
|
84
|
+
* Add a pipeline to filter/transform joined documents
|
|
85
|
+
* Useful for filtering, sorting, or limiting joined results
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* lookup.pipeline([
|
|
90
|
+
* { $match: { status: 'active' } },
|
|
91
|
+
* { $sort: { priority: -1 } },
|
|
92
|
+
* { $limit: 5 }
|
|
93
|
+
* ]);
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
pipeline(stages: PipelineStage[]): this;
|
|
97
|
+
/**
|
|
98
|
+
* Set let variables for use in pipeline
|
|
99
|
+
* Allows referencing local document fields in the pipeline
|
|
100
|
+
*/
|
|
101
|
+
let(variables: Record<string, string>): this;
|
|
102
|
+
/**
|
|
103
|
+
* Control pipeline sanitization (default: true)
|
|
104
|
+
* Set to false for auto-generated pipelines that are known safe
|
|
105
|
+
*/
|
|
106
|
+
sanitize(enabled: boolean): this;
|
|
107
|
+
/**
|
|
108
|
+
* Build the $lookup aggregation stage(s)
|
|
109
|
+
* Returns an array of pipeline stages including $lookup and optional $unwind
|
|
110
|
+
*
|
|
111
|
+
* IMPORTANT: MongoDB $lookup has two mutually exclusive forms:
|
|
112
|
+
* 1. Simple form: { from, localField, foreignField, as }
|
|
113
|
+
* 2. Pipeline form: { from, let, pipeline, as }
|
|
114
|
+
*
|
|
115
|
+
* When pipeline or let is specified, we use the pipeline form.
|
|
116
|
+
* Otherwise, we use the simpler localField/foreignField form.
|
|
117
|
+
*/
|
|
118
|
+
build(): PipelineStage[];
|
|
119
|
+
/**
|
|
120
|
+
* Build and return only the $lookup stage (without $unwind)
|
|
121
|
+
* Useful when you want to handle unwrapping yourself
|
|
122
|
+
*/
|
|
123
|
+
buildLookupOnly(): PipelineStage.Lookup;
|
|
124
|
+
/**
|
|
125
|
+
* Static helper: Create a simple lookup in one line
|
|
126
|
+
*/
|
|
127
|
+
static simple(from: string, localField: string, foreignField: string, options?: {
|
|
128
|
+
as?: string;
|
|
129
|
+
single?: boolean;
|
|
130
|
+
}): PipelineStage[];
|
|
131
|
+
/**
|
|
132
|
+
* Static helper: Create multiple lookups at once
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const pipeline = LookupBuilder.multiple([
|
|
137
|
+
* { from: 'departments', localField: 'deptSlug', foreignField: 'slug', single: true },
|
|
138
|
+
* { from: 'managers', localField: 'managerId', foreignField: '_id', single: true }
|
|
139
|
+
* ]);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
static multiple(lookups: LookupOptions[]): PipelineStage[];
|
|
143
|
+
/**
|
|
144
|
+
* Static helper: Create a nested lookup (lookup within lookup)
|
|
145
|
+
* Useful for multi-level joins like Order -> Product -> Category
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* // Join orders with products, then products with categories
|
|
150
|
+
* const pipeline = LookupBuilder.nested([
|
|
151
|
+
* { from: 'products', localField: 'productSku', foreignField: 'sku', as: 'product', single: true },
|
|
152
|
+
* { from: 'categories', localField: 'product.categorySlug', foreignField: 'slug', as: 'product.category', single: true }
|
|
153
|
+
* ]);
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
static nested(lookups: LookupOptions[]): PipelineStage[];
|
|
157
|
+
/**
|
|
158
|
+
* Sanitize pipeline stages by blocking dangerous stages and operators.
|
|
159
|
+
* Used internally by build() and available for external use (e.g., aggregate.ts).
|
|
160
|
+
*/
|
|
161
|
+
static sanitizePipeline(stages: PipelineStage[]): PipelineStage[];
|
|
162
|
+
/**
|
|
163
|
+
* Recursively remove dangerous operators from an expression object.
|
|
164
|
+
*/
|
|
165
|
+
private static _sanitizeDeep;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Performance Guidelines for $lookup at Scale:
|
|
169
|
+
*
|
|
170
|
+
* 1. **Index Requirements** (Critical for millions of records):
|
|
171
|
+
* - localField should be indexed on source collection
|
|
172
|
+
* - foreignField should be indexed on target collection (unique index preferred)
|
|
173
|
+
*
|
|
174
|
+
* Example:
|
|
175
|
+
* ```typescript
|
|
176
|
+
* // Employee collection
|
|
177
|
+
* employeeSchema.index({ departmentSlug: 1 });
|
|
178
|
+
*
|
|
179
|
+
* // Department collection
|
|
180
|
+
* departmentSchema.index({ slug: 1 }, { unique: true });
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* 2. **Query Performance**:
|
|
184
|
+
* - With proper indexes: O(log n) per lookup
|
|
185
|
+
* - Without indexes: O(n * m) - AVOID THIS!
|
|
186
|
+
* - Use explain() to verify index usage: IXSCAN (good) vs COLLSCAN (bad)
|
|
187
|
+
*
|
|
188
|
+
* 3. **Pipeline Optimization**:
|
|
189
|
+
* - Place $match stages as early as possible
|
|
190
|
+
* - Use $project to reduce field size before lookups
|
|
191
|
+
* - Limit joined results with pipeline: [{ $match: {...} }, { $limit: n }]
|
|
192
|
+
*
|
|
193
|
+
* 4. **Memory Considerations**:
|
|
194
|
+
* - Each lookup creates a new field in memory
|
|
195
|
+
* - Use $project after lookup to remove unnecessary fields
|
|
196
|
+
* - Consider allowDiskUse: true for very large datasets
|
|
197
|
+
*
|
|
198
|
+
* 5. **Alternative Approaches**:
|
|
199
|
+
* - For 1:1 relationships with high read frequency: Consider storing ObjectId + slug
|
|
200
|
+
* - For read-heavy workloads: Consider caching or materialized views
|
|
201
|
+
* - For real-time dashboards: Consider separate aggregation collections
|
|
202
|
+
*/
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/types/operations.d.ts
|
|
205
|
+
/** Session-only options — shared base for lightweight operations */
|
|
206
|
+
interface SessionOptions {
|
|
207
|
+
/** MongoDB session for transactions */
|
|
208
|
+
session?: unknown;
|
|
209
|
+
/** Organization/tenant ID for multi-tenant plugin scoping */
|
|
210
|
+
organizationId?: string | ObjectId;
|
|
211
|
+
/**
|
|
212
|
+
* Per-call escape hatch — plugins listed here that opt into the
|
|
213
|
+
* mechanism (`auditLog`, `auditTrail`, `observability`) read this
|
|
214
|
+
* from `RepositoryContext` and skip their work for this call. Use
|
|
215
|
+
* on hot paths (e.g. heartbeats / counters) where the audit overhead
|
|
216
|
+
* dominates.
|
|
217
|
+
*
|
|
218
|
+
* Plugins that enforce correctness or security (multi-tenant scope,
|
|
219
|
+
* cache invalidation, soft-delete filter) deliberately ignore this
|
|
220
|
+
* flag — security/correctness must not be skippable per-call.
|
|
221
|
+
*
|
|
222
|
+
* **Match exactly by `plugin.name`** (camelCase, NOT kebab-case):
|
|
223
|
+
* - `'auditLog'` — see `auditLogPlugin()` in
|
|
224
|
+
* `src/plugins/audit-log.plugin.ts` (`name: 'auditLog'`)
|
|
225
|
+
* - `'auditTrail'` — see `auditTrailPlugin()` in
|
|
226
|
+
* `src/plugins/audit-trail.plugin.ts` (`name: 'auditTrail'`)
|
|
227
|
+
* - `'observability'` — see `observabilityPlugin()` in
|
|
228
|
+
* `src/plugins/observability.plugin.ts` (`name: 'observability'`)
|
|
229
|
+
*/
|
|
230
|
+
skipPlugins?: readonly string[];
|
|
231
|
+
/**
|
|
232
|
+
* Per-call escape hatch for `multiTenantPlugin`. When `true`, the
|
|
233
|
+
* tenant scope hook returns without injecting `organizationId` (or
|
|
234
|
+
* the configured `tenantField`) into the operation's policy slot
|
|
235
|
+
* for THIS call only. The plugin emits `after:tenant-bypass` with
|
|
236
|
+
* `{ context, operation, reason: 'option' }` so audit / observability
|
|
237
|
+
* plugins can distinguish bypassed queries from tenant-scoped ones.
|
|
238
|
+
*
|
|
239
|
+
* Reach for this when:
|
|
240
|
+
* - A super-admin / platform-admin user needs cross-tenant read
|
|
241
|
+
* for support tooling.
|
|
242
|
+
* - A migration script writes / backfills across tenants
|
|
243
|
+
* deliberately.
|
|
244
|
+
* - A scheduled job (TTL cleanup, cross-tenant aggregate, billing
|
|
245
|
+
* rollup) needs the unscoped view.
|
|
246
|
+
*
|
|
247
|
+
* **`bypassTenant` is the most-specific decision** — runs before
|
|
248
|
+
* `skipWhen` (plugin-level callback) and `resolveContext` (CLS
|
|
249
|
+
* fallback). Distinct from `skipWhen` which fires for every call
|
|
250
|
+
* and reads context to decide; `bypassTenant: true` is a deliberate
|
|
251
|
+
* per-call opt-out with no role check, so the calling code carries
|
|
252
|
+
* the responsibility.
|
|
253
|
+
*
|
|
254
|
+
* **NOT a substitute for proper access control.** This flag bypasses
|
|
255
|
+
* tenant scoping; it does NOT bypass authentication, RBAC, or any
|
|
256
|
+
* other host-level access check. Wrap it with your auth boundary at
|
|
257
|
+
* the controller / service layer.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* // Support engineer pulling a customer's order across tenants:
|
|
262
|
+
* await orderRepo.findAll({}, { bypassTenant: true });
|
|
263
|
+
*
|
|
264
|
+
* // Migration backfill across the whole estate:
|
|
265
|
+
* for await (const doc of repo.cursor({}, { bypassTenant: true })) { ... }
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
bypassTenant?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Per-call escape hatch for `appendOnlyPlugin` — deliberate
|
|
271
|
+
* maintenance on an immutable-facts collection (backfill repair,
|
|
272
|
+
* migration). Audited via `after:append-only-bypass`; grep for this
|
|
273
|
+
* flag the same way you grep `bypassTenant` / `mode: 'hard'`. NOT a
|
|
274
|
+
* substitute for access control — gate it at the host layer.
|
|
275
|
+
*/
|
|
276
|
+
bypassAppendOnly?: boolean;
|
|
277
|
+
/** Extensible — plugins can read custom fields from options */
|
|
278
|
+
[key: string]: unknown;
|
|
279
|
+
}
|
|
280
|
+
/** Read options — session + readPreference for read-only operations */
|
|
281
|
+
interface ReadOptions extends SessionOptions {
|
|
282
|
+
/** Read preference for replica sets (e.g. 'secondaryPreferred') */
|
|
283
|
+
readPreference?: ReadPreferenceType;
|
|
284
|
+
}
|
|
285
|
+
/** Repository operation options */
|
|
286
|
+
interface OperationOptions extends ReadOptions {
|
|
287
|
+
/** Fields to select */
|
|
288
|
+
select?: SelectSpec;
|
|
289
|
+
/** Fields to populate */
|
|
290
|
+
populate?: PopulateSpec;
|
|
291
|
+
/** Advanced populate options (from QueryParser or Arc's BaseController) */
|
|
292
|
+
populateOptions?: PopulateOptions[];
|
|
293
|
+
/** Return plain JavaScript objects */
|
|
294
|
+
lean?: boolean;
|
|
295
|
+
/** Throw error if document not found (default: true) */
|
|
296
|
+
throwOnNotFound?: boolean;
|
|
297
|
+
/** Override the ID field for this call (default: repo.idField or '_id').
|
|
298
|
+
* Use when the same repo sometimes queries by _id and sometimes by slug/code. */
|
|
299
|
+
idField?: string;
|
|
300
|
+
/** Additional query filters (e.g., for soft delete) */
|
|
301
|
+
query?: Record<string, unknown>;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Cache-aware operation options — extends OperationOptions with the
|
|
305
|
+
* unified cache plugin's per-call shape. Pass `cache: { staleTime, swr,
|
|
306
|
+
* tags, bypass, ... }` to override freshness for this call.
|
|
307
|
+
*/
|
|
308
|
+
interface CacheableOptions extends OperationOptions {
|
|
309
|
+
/** Per-call cache override forwarded to the unified cache plugin. */
|
|
310
|
+
cache?: import('@classytic/repo-core/cache').CacheOptions;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Per-call cache options — TanStack-shaped (`staleTime`, `gcTime`, `swr`,
|
|
314
|
+
* `tags`, `bypass`, `enabled`, `key`). Threaded through read methods via
|
|
315
|
+
* `options.cache` (CRUD) or `aggRequest.cache` (aggregate).
|
|
316
|
+
*
|
|
317
|
+
* Mongokit 3.13+ delegates cache wiring to `@classytic/repo-core/cache` —
|
|
318
|
+
* the unified plugin owns the canonical `CacheAdapter` / `CacheOptions` /
|
|
319
|
+
* `RepositoryCacheHandle` types; kits import them directly.
|
|
320
|
+
*/
|
|
321
|
+
interface CacheOperationOptions {
|
|
322
|
+
/** Per-call cache override forwarded to the unified cache plugin. */
|
|
323
|
+
cache?: import('@classytic/repo-core/cache').CacheOptions;
|
|
324
|
+
}
|
|
325
|
+
/** withTransaction options */
|
|
326
|
+
interface WithTransactionOptions {
|
|
327
|
+
/** Allow non-transactional fallback when transactions are unsupported */
|
|
328
|
+
allowFallback?: boolean;
|
|
329
|
+
/** Optional hook to observe fallback triggers */
|
|
330
|
+
onFallback?: (error: Error) => void;
|
|
331
|
+
/** MongoDB transaction options (readConcern, writeConcern, readPreference, maxCommitTimeMS) */
|
|
332
|
+
transactionOptions?: import('mongoose').mongo.TransactionOptions;
|
|
333
|
+
}
|
|
334
|
+
/** Create operation options */
|
|
335
|
+
interface CreateOptions extends SessionOptions {
|
|
336
|
+
/** Keep insertion order on error (default: false).
|
|
337
|
+
* When false, all valid documents insert even if some fail (e.g. duplicates).
|
|
338
|
+
* Set to true to abort remaining inserts on first error. */
|
|
339
|
+
ordered?: boolean;
|
|
340
|
+
}
|
|
341
|
+
/** Update operation options */
|
|
342
|
+
interface UpdateOptions extends OperationOptions {
|
|
343
|
+
/** Enable update pipeline syntax */
|
|
344
|
+
updatePipeline?: boolean;
|
|
345
|
+
/** Array filters for positional operator $[<identifier>] updates */
|
|
346
|
+
arrayFilters?: Record<string, unknown>[];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Options for atomic findOneAndUpdate (compare-and-set primitive).
|
|
350
|
+
*
|
|
351
|
+
* Designed for outbox/lock/semaphore patterns that need a single round-trip
|
|
352
|
+
* match-and-mutate. Goes through the full hook pipeline (multi-tenant scope,
|
|
353
|
+
* soft-delete, audit) the same way `update()` does.
|
|
354
|
+
*/
|
|
355
|
+
interface FindOneAndUpdateOptions extends OperationOptions {
|
|
356
|
+
/** Sort to disambiguate when filter matches multiple docs (e.g. FIFO claim). */
|
|
357
|
+
sort?: SortSpec;
|
|
358
|
+
/** Return doc state before or after the update. Default: 'after'. */
|
|
359
|
+
returnDocument?: 'before' | 'after';
|
|
360
|
+
/** Insert if no doc matches. Default: false. */
|
|
361
|
+
upsert?: boolean;
|
|
362
|
+
/** Array filters for positional `$[<identifier>]` updates. */
|
|
363
|
+
arrayFilters?: Record<string, unknown>[];
|
|
364
|
+
/** Allow aggregation-pipeline updates (array form). Default: false. */
|
|
365
|
+
updatePipeline?: boolean;
|
|
366
|
+
/** Run mongoose schema validators on the update. Default: true. */
|
|
367
|
+
runValidators?: boolean;
|
|
368
|
+
/** Collation for locale-aware string comparison. */
|
|
369
|
+
collation?: CollationOptions;
|
|
370
|
+
/** Maximum execution time in milliseconds. */
|
|
371
|
+
maxTimeMS?: number;
|
|
372
|
+
}
|
|
373
|
+
/** Aggregate operation options */
|
|
374
|
+
interface AggregateOptions extends ReadOptions {
|
|
375
|
+
/** Allow aggregation to use disk for large sorts/groups */
|
|
376
|
+
allowDiskUse?: boolean;
|
|
377
|
+
/** Comment for profiler/logs */
|
|
378
|
+
comment?: string;
|
|
379
|
+
/** Maximum execution time in milliseconds */
|
|
380
|
+
maxTimeMS?: number;
|
|
381
|
+
/** Read concern level */
|
|
382
|
+
readConcern?: {
|
|
383
|
+
level: string;
|
|
384
|
+
};
|
|
385
|
+
/** Collation for locale-aware string comparison */
|
|
386
|
+
collation?: CollationOptions;
|
|
387
|
+
/** Maximum allowed pipeline stages (governance) */
|
|
388
|
+
maxPipelineStages?: number;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Mongokit's `lookupPopulate` options — kit-native superset of the
|
|
392
|
+
* portable `LookupPopulateOptions<TBase>` from repo-core. Widens three
|
|
393
|
+
* slots so arc / mongoose consumers compose cleanly:
|
|
394
|
+
*
|
|
395
|
+
* - `filters`: accepts Filter IR, a `Partial<TBase> & Record<string,
|
|
396
|
+
* unknown>` literal (same shape repo-core's contract uses), or a
|
|
397
|
+
* plain `Record<string, unknown>` for legacy callers.
|
|
398
|
+
* - `lookups`: accepts either the portable `LookupSpec[]` (works on
|
|
399
|
+
* every kit) or mongokit's `LookupOptions[]` (adds `pipeline` /
|
|
400
|
+
* `let` / `sanitize` for mongo-correlated joins).
|
|
401
|
+
* - `select`: accepts mongoose's space-separated string form
|
|
402
|
+
* (`'name email'`) in addition to the portable array / inclusion
|
|
403
|
+
* map. Kept for arc + existing controller convenience.
|
|
404
|
+
*
|
|
405
|
+
* `TBase` defaults to `unknown` so legacy non-generic usage still
|
|
406
|
+
* compiles; pass the doc type for arc `RepositoryLike<TDoc>` structural
|
|
407
|
+
* assignment — `LookupPopulateOptions<TDoc>` is bit-compatible with
|
|
408
|
+
* `import('@classytic/repo-core/repository').LookupPopulateOptions<TDoc>`.
|
|
409
|
+
*/
|
|
410
|
+
interface LookupPopulateOptions<TBase = unknown> {
|
|
411
|
+
/**
|
|
412
|
+
* Base-table filter. Accepts Filter IR from repo-core, a typed
|
|
413
|
+
* `Partial<TBase> & Record<string, unknown>` literal (what arc
|
|
414
|
+
* controllers produce when typed against `StandardRepo<TDoc>`), or
|
|
415
|
+
* a plain `Record<string, unknown>` for ad-hoc callers.
|
|
416
|
+
*/
|
|
417
|
+
filters?: import('@classytic/repo-core/filter').Filter | (Partial<TBase> & Record<string, unknown>) | Record<string, unknown>;
|
|
418
|
+
/**
|
|
419
|
+
* Portable `LookupSpec[]` (cross-kit) or mongokit-native
|
|
420
|
+
* `LookupOptions[]` (with `pipeline` / `let` / `sanitize`). The
|
|
421
|
+
* portable shape is a structural subset, so callers can mix.
|
|
422
|
+
*/
|
|
423
|
+
lookups: readonly import('@classytic/repo-core/repository').LookupSpec[] | LookupOptions[];
|
|
424
|
+
/** Sort specification */
|
|
425
|
+
sort?: SortSpec | string;
|
|
426
|
+
/** Page number (offset mode, 1-indexed) */
|
|
427
|
+
page?: number;
|
|
428
|
+
/** Cursor token for next page (keyset mode) */
|
|
429
|
+
after?: string;
|
|
430
|
+
/** Number of documents per page */
|
|
431
|
+
limit?: number;
|
|
432
|
+
/** Fields to select */
|
|
433
|
+
select?: SelectSpec;
|
|
434
|
+
/** Collation for locale-aware string comparison */
|
|
435
|
+
collation?: CollationOptions;
|
|
436
|
+
/** Count strategy for offset pagination */
|
|
437
|
+
countStrategy?: 'exact' | 'estimated' | 'none';
|
|
438
|
+
/**
|
|
439
|
+
* Transaction session. Typed `unknown` to match repo-core's
|
|
440
|
+
* `RepositorySession` contract — mongokit narrows to `ClientSession`
|
|
441
|
+
* internally. Not inherited from `ReadOptions` because the repo-core
|
|
442
|
+
* contract type has no `[key: string]: unknown` escape hatch, and
|
|
443
|
+
* arc's `Partial<StandardRepo<TDoc>>` boundary rejects index-signature
|
|
444
|
+
* drift under strict function-type variance.
|
|
445
|
+
*/
|
|
446
|
+
session?: unknown;
|
|
447
|
+
/** Read preference for replica sets (e.g. 'secondaryPreferred') */
|
|
448
|
+
readPreference?: ReadPreferenceType;
|
|
449
|
+
/** Multi-tenant plugin scoping (applied via before:lookupPopulate hook). */
|
|
450
|
+
organizationId?: string | ObjectId;
|
|
451
|
+
}
|
|
452
|
+
/** Group result */
|
|
453
|
+
interface GroupResult {
|
|
454
|
+
_id: unknown;
|
|
455
|
+
count: number;
|
|
456
|
+
}
|
|
457
|
+
/** Min/Max result */
|
|
458
|
+
interface MinMaxResult {
|
|
459
|
+
min: unknown;
|
|
460
|
+
max: unknown;
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
export { WithTransactionOptions as _, DeleteManyResult as a, GroupResult as c, MinMaxResult as d, OperationOptions as f, UpdateOptions as g, UpdateManyResult as h, CreateOptions as i, LookupPopulateOptions as l, SessionOptions as m, CacheOperationOptions as n, DeleteResult as o, ReadOptions as p, CacheableOptions as r, FindOneAndUpdateOptions as s, AggregateOptions as t, LookupPopulateResult as u, LookupBuilder as v, LookupOptions as y };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as PaginationEngine } from "../PaginationEngine-
|
|
1
|
+
import { t as PaginationEngine } from "../PaginationEngine-CBS1XGzp.mjs";
|
|
2
2
|
export { PaginationEngine };
|