@frogfish/k2db 2.0.7 → 3.0.2

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.
@@ -1,5 +1,12 @@
1
1
  import { ObjectId } from "mongodb";
2
2
  import { ZodTypeAny } from "zod";
3
+ /**
4
+ * Test helper: fully reset the shared MongoClient pool.
5
+ *
6
+ * Not for production usage; intended for test runners to clean up
7
+ * between suites without restarting the process.
8
+ */
9
+ export declare function resetSharedMongoClientsForTests(): Promise<void>;
3
10
  export interface HostConfig {
4
11
  host: string;
5
12
  port?: number;
@@ -8,6 +15,7 @@ export interface DatabaseConfig {
8
15
  name: string;
9
16
  user?: string;
10
17
  password?: string;
18
+ authSource?: string;
11
19
  hosts?: HostConfig[];
12
20
  replicaset?: string;
13
21
  slowQueryMs?: number;
@@ -15,6 +23,11 @@ export interface DatabaseConfig {
15
23
  beforeQuery?: (op: string, details: any) => void;
16
24
  afterQuery?: (op: string, details: any, durationMs: number) => void;
17
25
  };
26
+ ownershipMode?: "lax" | "strict";
27
+ aggregationMode?: "loose" | "guarded" | "strict";
28
+ secureFieldPrefixes?: string[];
29
+ secureFieldEncryptionKey?: string;
30
+ secureFieldEncryptionKeyId?: string;
18
31
  }
19
32
  export interface BaseDocument {
20
33
  _id?: ObjectId;
@@ -59,12 +72,29 @@ export interface VersionInfo {
59
72
  _v: number;
60
73
  _at: number;
61
74
  }
75
+ export type Scope = string | "*";
62
76
  export declare class K2DB {
63
77
  private conf;
64
78
  private db;
65
79
  private connection;
80
+ private clientKey?;
81
+ private initialized;
82
+ private initPromise?;
66
83
  private schemas;
84
+ private readonly ownershipMode;
85
+ private readonly aggregationMode;
86
+ private readonly secureFieldPrefixes;
87
+ private readonly secureFieldEncryptionKey?;
88
+ private readonly secureFieldEncryptionKeyId?;
67
89
  constructor(conf: DatabaseConfig);
90
+ /**
91
+ * Normalize a scope value for ownership enforcement.
92
+ */
93
+ private normalizeScope;
94
+ /**
95
+ * Apply a scope constraint to criteria for ownership enforcement.
96
+ */
97
+ private applyScopeToCriteria;
68
98
  /**
69
99
  * Initializes the MongoDB connection.
70
100
  */
@@ -80,15 +110,21 @@ export declare class K2DB {
80
110
  * @param collectionName - Name of the collection.
81
111
  */
82
112
  private getCollection;
83
- get(collectionName: string, uuid: string): Promise<BaseDocument>;
84
113
  /**
85
114
  * Retrieves a single document by UUID.
86
115
  * @param collectionName - Name of the collection.
87
116
  * @param uuid - UUID of the document.
88
- * @param objectTypeName - Optional object type name.
117
+ * @param scope - (optional) Owner selector; "*" means all owners.
118
+ */
119
+ get(collectionName: string, uuid: string, scope?: Scope | string): Promise<BaseDocument>;
120
+ /**
121
+ * Retrieves a single document by criteria.
122
+ * @param collectionName - Name of the collection.
123
+ * @param criteria - Criteria to find the document.
89
124
  * @param fields - Optional array of fields to include.
125
+ * @param scope - (optional) Owner selector; "*" means all owners.
90
126
  */
91
- findOne(collectionName: string, criteria: any, fields?: Array<string>): Promise<BaseDocument | null>;
127
+ findOne(collectionName: string, criteria: any, fields?: Array<string>, scope?: Scope | string): Promise<BaseDocument | null>;
92
128
  /**
93
129
  * Finds documents based on parameters with pagination support.
94
130
  * @param collectionName - Name of the collection.
@@ -96,16 +132,72 @@ export declare class K2DB {
96
132
  * @param params - Optional search parameters (for sorting, including/excluding fields).
97
133
  * @param skip - Number of documents to skip (for pagination).
98
134
  * @param limit - Maximum number of documents to return.
135
+ * @param scope - (optional) Owner selector; "*" means all owners.
99
136
  */
100
- find(collectionName: string, filter: any, params?: any, skip?: number, limit?: number): Promise<BaseDocument[]>;
137
+ find(collectionName: string, filter: any, params?: any, skip?: number, limit?: number, scope?: Scope | string): Promise<BaseDocument[]>;
101
138
  /**
102
- * Aggregates documents based on criteria with pagination support.
139
+ * Aggregates documents based on criteria with pagination support (may validate/limit stages in guarded/strict aggregationMode). Secure-prefixed fields may be stripped from results when configured.
103
140
  * @param collectionName - Name of the collection.
104
141
  * @param criteria - Aggregation pipeline criteria.
105
142
  * @param skip - Number of documents to skip (for pagination).
106
143
  * @param limit - Maximum number of documents to return.
144
+ * @param scope - (optional) Owner selector; "*" means all owners.
145
+ */
146
+ aggregate(collectionName: string, criteria: any[], skip?: number, limit?: number, scope?: Scope | string): Promise<BaseDocument[]>;
147
+ /**
148
+ * Validate an aggregation pipeline for safety based on aggregationMode.
149
+ * - loose: no validation
150
+ * - guarded: deny obvious footguns (writes, server-side code) and enforce basic caps
151
+ * - strict: allow only a small safe subset of stages and enforce basic caps
152
+ */
153
+ private validateAggregationPipeline;
154
+ /** Collect top-level stage operators for a pipeline (e.g. "$match", "$lookup"). */
155
+ private collectStageOps;
156
+ /** True if a field key is considered secure and must not be returned. */
157
+ private isSecureFieldKey;
158
+ /**
159
+ * Recursively strips secure-prefixed fields from objects/arrays (e.g. "#passport_number").
160
+ * This is applied on read results (e.g. aggregate) so pipelines cannot exfiltrate secure fields.
161
+ */
162
+ private stripSecureFieldsDeep;
163
+ /** True if secure-field encryption is enabled (requires both key and keyId). */
164
+ private hasSecureEncryption;
165
+ /** Encrypt a JS value using AES-256-GCM and return "<kid>:<ivB64>.<tagB64>.<ctB64>". */
166
+ private encryptSecureValueToString;
167
+ /** Decrypt a "<kid>:<ivB64>.<tagB64>.<ctB64>" string back into a JS value. */
168
+ private decryptSecureStringToValue;
169
+ /**
170
+ * Encrypt secure-prefixed fields in an object/array tree.
171
+ * Only keys with secure prefixes are encrypted; other keys are recursed to allow nested secure keys.
172
+ */
173
+ private encryptSecureFieldsDeep;
174
+ /**
175
+ * Decrypt secure-prefixed fields in an object/array tree.
176
+ * If a secure field value is not an encrypted string, it is returned as-is.
177
+ */
178
+ private decryptSecureFieldsDeep;
179
+ /**
180
+ * Throws if an aggregation pipeline references any secure-prefixed field paths.
181
+ * This prevents deriving output from secure fields (e.g. {$group: {x: {$sum: "$#passport_number"}}}).
182
+ */
183
+ private assertNoSecureFieldRefsInPipeline;
184
+ /** Recursively detects secure field references in an aggregation pipeline AST. */
185
+ private containsSecureFieldRefDeep;
186
+ /**
187
+ * Returns true if a string appears to reference a secure field path.
188
+ * We consider:
189
+ * - "$path.to.field"
190
+ * - "$$var.path.to.field"
191
+ * - plain "path.to.field" (e.g. $getField: { field: "#passport_number" })
107
192
  */
108
- aggregate(collectionName: string, criteria: any[], skip?: number, limit?: number): Promise<BaseDocument[]>;
193
+ private stringHasSecureFieldPath;
194
+ /** True if any segment in a dotted path starts with a secure prefix. */
195
+ private pathHasSecureSegment;
196
+ /**
197
+ * Ensures an aggregation pipeline respects ownership scope for the root
198
+ * collection and any joined collections ($lookup, $unionWith, $graphLookup, $facet).
199
+ */
200
+ private enforceScopeInPipeline;
109
201
  /**
110
202
  * Ensures an aggregation pipeline excludes soft-deleted documents for the root
111
203
  * collection and any joined collections ($lookup, $unionWith, $graphLookup, $facet).
@@ -124,8 +216,9 @@ export declare class K2DB {
124
216
  * @param collectionName - Name of the collection.
125
217
  * @param criteria - Update criteria.
126
218
  * @param values - Values to update or replace with.
219
+ * @param scope - (optional) Owner selector; "*" means all owners.
127
220
  */
128
- updateAll(collectionName: string, criteria: any, values: Partial<BaseDocument>): Promise<UpdateResult>;
221
+ updateAll(collectionName: string, criteria: any, values: Partial<BaseDocument>, scope?: Scope | string): Promise<UpdateResult>;
129
222
  /**
130
223
  * Updates a single document by UUID.
131
224
  * Can either replace the document or patch it.
@@ -133,60 +226,70 @@ export declare class K2DB {
133
226
  * @param id - UUID string to identify the document.
134
227
  * @param data - Data to update or replace with.
135
228
  * @param replace - If true, replaces the entire document (PUT), otherwise patches (PATCH).
229
+ * @param scope - (optional) Owner selector; "*" means all owners.
136
230
  */
137
- update(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean): Promise<UpdateResult>;
231
+ update(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean, scope?: Scope | string): Promise<UpdateResult>;
138
232
  /**
139
233
  * Removes (soft deletes) multiple documents based on criteria.
140
234
  * @param collectionName - Name of the collection.
141
235
  * @param criteria - Removal criteria.
236
+ * @param scope - (optional) Owner selector; "*" means all owners.
142
237
  */
143
- deleteAll(collectionName: string, criteria: any): Promise<DeleteResult>;
238
+ deleteAll(collectionName: string, criteria: any, scope?: Scope | string): Promise<DeleteResult>;
144
239
  /**
145
240
  * Removes (soft deletes) a single document by UUID.
146
241
  * @param collectionName - Name of the collection.
147
242
  * @param id - UUID of the document.
243
+ * @param scope - (optional) Owner selector; "*" means all owners.
148
244
  */
149
- delete(collectionName: string, id: string): Promise<DeleteResult>;
245
+ delete(collectionName: string, id: string, scope?: Scope | string): Promise<DeleteResult>;
150
246
  /**
151
247
  * Permanently deletes a document that has been soft-deleted.
152
248
  * @param collectionName - Name of the collection.
153
249
  * @param id - UUID of the document.
250
+ * @param scope - (optional) Owner selector; "*" means all owners.
154
251
  */
155
- purge(collectionName: string, id: string): Promise<PurgeResult>;
252
+ purge(collectionName: string, id: string, scope?: Scope | string): Promise<PurgeResult>;
156
253
  /**
157
254
  * Permanently deletes all documents that are soft-deleted and whose _updated
158
255
  * timestamp is older than the provided threshold (in milliseconds ago).
159
256
  * @param collectionName - Name of the collection.
160
257
  * @param olderThanMs - Age threshold in milliseconds; documents with
161
258
  * `_updated <= (Date.now() - olderThanMs)` will be purged.
259
+ * @param scope - (optional) Owner selector; "*" means all owners.
162
260
  */
163
- purgeDeletedOlderThan(collectionName: string, olderThanMs: number): Promise<PurgeManyResult>;
261
+ purgeDeletedOlderThan(collectionName: string, olderThanMs: number, scope?: Scope | string): Promise<PurgeManyResult>;
164
262
  /**
165
263
  * Restores a soft-deleted document.
166
264
  * @param collectionName - Name of the collection.
167
265
  * @param criteria - Criteria to identify the document.
266
+ * @param scope - (optional) Owner selector; "*" means all owners.
168
267
  */
169
- restore(collectionName: string, criteria: any): Promise<RestoreResult>;
268
+ restore(collectionName: string, criteria: any, scope?: Scope | string): Promise<RestoreResult>;
170
269
  /**
171
270
  * Counts documents based on criteria.
172
271
  * @param collectionName - Name of the collection.
173
272
  * @param criteria - Counting criteria.
273
+ * @param scope - (optional) Owner selector; "*" means all owners.
174
274
  */
175
- count(collectionName: string, criteria: any): Promise<CountResult>;
275
+ count(collectionName: string, criteria: any, scope?: Scope | string): Promise<CountResult>;
176
276
  /**
177
- * Drops an entire collection.
277
+ * Drops an entire collection (global destructive operation).
178
278
  * @param collectionName - Name of the collection.
279
+ * @param scope - (optional) Must be "*" in strict ownership mode.
179
280
  */
180
- drop(collectionName: string): Promise<DropResult>;
281
+ drop(collectionName: string, scope?: Scope | string): Promise<DropResult>;
181
282
  /**
182
283
  * Sanitizes aggregation criteria.
183
284
  * @param criteria - Aggregation stage criteria.
184
285
  */
185
286
  private static sanitiseCriteria;
186
- /** Recursively uppercases any values for fields named `_uuid` within a query object. */
287
+ /** Recursively normalizes query fields: `_uuid` uppercased, `_owner` lowercased. */
187
288
  private static normalizeCriteriaIds;
188
289
  /** Uppercase helper for `_uuid` field supporting operators like $in/$nin/$eq/$ne and arrays. */
189
290
  private static normalizeUuidField;
291
+ /** Lowercase helper for `_owner` field supporting operators like $in/$nin/$eq/$ne and arrays. */
292
+ private static normalizeOwnerField;
190
293
  /** Strip any user-provided fields that start with '_' (reserved). */
191
294
  private static stripReservedFields;
192
295
  /** True if string matches K2 ID format (Crockford Base32, 8-4-4-4-6, uppercase). */
@@ -240,12 +343,6 @@ export declare class K2DB {
240
343
  * Optional: Checks the health of the database connection.
241
344
  */
242
345
  isHealthy(): Promise<boolean>;
243
- /**
244
- * Utility to normalize the error type.
245
- * @param err - The caught error of type `unknown`.
246
- * @returns A normalized error of type `Error`.
247
- */
248
- private normalizeError;
249
346
  /** Name of the history collection for a given collection. */
250
347
  private historyName;
251
348
  /** Register a Zod schema for a collection. */
@@ -269,12 +366,31 @@ export declare class K2DB {
269
366
  /**
270
367
  * Update a document and keep the previous version in a history collection.
271
368
  * If maxVersions is provided, prunes oldest snapshots beyond that number.
369
+ * @param collectionName - Name of the collection.
370
+ * @param id - UUID string to identify the document.
371
+ * @param data - Data to update or replace with.
372
+ * @param replace - If true, replaces the entire document (PUT), otherwise patches (PATCH).
373
+ * @param maxVersions - Maximum number of versions to keep (optional).
374
+ * @param scope - (optional) Owner selector; "*" means all owners.
375
+ */
376
+ updateVersioned(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean, maxVersions?: number, scope?: Scope | string): Promise<VersionedUpdateResult[]>;
377
+ /**
378
+ * List versions (latest first).
379
+ * @param collectionName - Name of the collection.
380
+ * @param id - UUID string to identify the document.
381
+ * @param skip - Number of versions to skip (for pagination).
382
+ * @param limit - Maximum number of versions to return.
383
+ * @param scope - (optional) Owner selector; "*" means all owners.
384
+ */
385
+ listVersions(collectionName: string, id: string, skip?: number, limit?: number, scope?: Scope | string): Promise<VersionInfo[]>;
386
+ /**
387
+ * Revert the current document to a specific historical version (preserves metadata).
388
+ * @param collectionName - Name of the collection.
389
+ * @param id - UUID string to identify the document.
390
+ * @param version - Version number to revert to.
391
+ * @param scope - (optional) Owner selector; "*" means all owners.
272
392
  */
273
- updateVersioned(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean, maxVersions?: number): Promise<VersionedUpdateResult[]>;
274
- /** List versions (latest first). */
275
- listVersions(collectionName: string, id: string, skip?: number, limit?: number): Promise<VersionInfo[]>;
276
- /** Revert the current document to a specific historical version (preserves metadata). */
277
- revertToVersion(collectionName: string, id: string, version: number): Promise<{
393
+ revertToVersion(collectionName: string, id: string, version: number, scope?: Scope | string): Promise<{
278
394
  updated: number;
279
395
  }>;
280
396
  }