@frogfish/k2db 1.0.14 → 2.0.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 (6) hide show
  1. package/README.md +297 -1
  2. package/data.d.ts +27 -24
  3. package/data.js +33 -5
  4. package/db.d.ts +104 -28
  5. package/db.js +435 -148
  6. package/package.json +18 -11
package/README.md CHANGED
@@ -1 +1,297 @@
1
- # k2db
1
+ # k2db
2
+
3
+ Lightweight MongoDB data layer that stays schemaless by default while enforcing consistent metadata and soft-delete.
4
+
5
+ Why this, not an ORM?
6
+ - Thin driver wrapper: Sits directly atop the official MongoDB driver. No models, decorators, or migrations to manage.
7
+ - Loose by default: Store arbitrary data while consistently enforcing `_uuid`, `_owner`, `_created`, `_updated`, `_deleted`.
8
+ - Opinionated guardrails: Soft‑delete and metadata are enforced everywhere, including aggregates, without changing your payloads.
9
+ - Opt‑in structure: Add Zod schemas per collection only if you want validation/coercion; skip entirely if you don’t.
10
+ - Predictable behavior: No hidden population, no query magic, and explicit return types. What you query is what runs.
11
+
12
+ What you get
13
+ - Concrete API for Mongo: Avoid re‑implementing the same metadata, soft‑delete, and versioning patterns across services. This wrapper centralizes those policies so teams don’t write boilerplate.
14
+ - Guardrails without heavy ORM: Prisma/Mongoose add ceremony (models, migrations, plugins) that can be overkill in microservices/serverless. k2db gives you just enough safety with minimal overhead.
15
+ - Soft deletes done properly: Automatically enforced everywhere (including aggregates and joins), so you don’t accidentally leak or operate on deleted data.
16
+ - Versioning baked in: `updateVersioned`, `listVersions`, and `revertToVersion` provide low‑friction “undo to N levels” that many DALs lack, increasing confidence in production changes.
17
+
18
+ Where it fits in the stack
19
+ - Below your API/service layer and above the MongoDB driver.
20
+ - Use it as a shared data access layer (DAL) across services that need flexible shapes but strict lifecycle rules.
21
+ - Keep ownership/authorization in your API; this library only guarantees metadata and deletion semantics.
22
+ - Designed for microservices and edge computing: tiny footprint, fast cold starts, and no heavy runtime dependencies.
23
+
24
+ Deployment tips (Nomad, Lambda, etc.)
25
+ - Environments: Targets Node.js runtimes (Node 18/20). Not suitable for non‑TCP “edge JS” (e.g., Cloudflare Workers) that cannot open Mongo sockets.
26
+ - Connection reuse: Create one `K2DB` instance per process and reuse it.
27
+ - Lambda: instantiate outside the handler and `await init()` lazily once.
28
+ - Nomad: init on service start; reuse for the lifetime of the task.
29
+ - Example (AWS Lambda):
30
+ ```ts
31
+ import { K2DB } from "@frogfish/k2db";
32
+ const db = new K2DB(K2DB.fromEnv());
33
+ let ready: Promise<void> | null = null;
34
+ export const handler = async (event) => {
35
+ ready = ready || db.init();
36
+ await ready; // reused across warm invocations
37
+ const res = await db.find("hello", { _owner: event.userId }, {}, 0, 10);
38
+ return { statusCode: 200, body: JSON.stringify(res) };
39
+ };
40
+ ```
41
+ - Pooling and timeouts: The driver manages a small pool by default.
42
+ - Serverless: keep `minPoolSize=0` (default), consider `maxIdleTimeMS` to drop idle sockets faster.
43
+ - Long‑lived services (Nomad): you can tune pool sizing if needed.
44
+ - You can adjust `connectTimeoutMS/serverSelectionTimeoutMS` in the code if your environment needs higher values.
45
+ - Networking:
46
+ - Atlas from Lambda: prefer VPC + PrivateLink or NAT egress; ensure security groups allow outbound to Atlas.
47
+ - Nomad: ensure egress to Atlas/DB, or run Mongo in the same network; set DNS to resolve SRV if using SRV.
48
+ - Secrets:
49
+ - Lambda: use AWS Secrets Manager/Parameter Store → env vars consumed by `K2DB.fromEnv()`.
50
+ - Nomad: pair with HashiCorp Vault templates/env inject; keep credentials out of images.
51
+ - Health/readiness:
52
+ - Use `db.isHealthy()` in readiness checks (Nomad) and `db.release()` on shutdown. For Lambda there’s no explicit shutdown.
53
+ - Bundling:
54
+ - ESM friendly; keep dependencies minimal. If you bundle, exclude native modules you don’t use.
55
+
56
+ When to pick this
57
+ - You want Mongo’s flexibility with light, reliable guardrails (soft delete, timestamps, owner, UUID).
58
+ - You’d rather not pull in a heavier ORM (Mongoose/Prisma) and prefer direct control of queries and indexes.
59
+ - Your payloads vary by tenant/feature and rigid schemas get in the way, but you still want optional validation.
60
+
61
+ When to consider something else
62
+ - Rich modeling, relations, and ecosystem plugins → Mongoose.
63
+ - Cross‑DB modeling, migrations, and schema‑first DX → Prisma.
64
+ - If you already standardized on an ORM and like its trade‑offs, this library aims to stay out of your way.
65
+
66
+ Core invariants
67
+ - _uuid: unique identifier generated on create (unique among non-deleted by default).
68
+ - _created/_updated: timestamps managed by the library.
69
+ - _owner: required on create; preserved across updates.
70
+ - _deleted: soft-delete flag. All reads exclude deleted by default; writes do not touch deleted docs. Purge hard-deletes only when `_deleted: true`.
71
+
72
+ Modernized behavior
73
+ - ESM build, TypeScript, NodeNext resolution.
74
+ - Soft-delete enforced across find, findOne, count, update, updateAll.
75
+ - Aggregate never returns deleted documents (also enforced across `$lookup`, `$unionWith`, `$graphLookup`, `$facet`).
76
+ - Reserved fields: user input cannot set keys starting with `_`; the library owns all underscore-prefixed metadata.
77
+ - Slow query logging: operations slower than `slowQueryMs` (default 200ms) are logged via `debug("k2:db")`.
78
+ - Hooks: optional `beforeQuery(op, details)` and `afterQuery(op, details, durationMs)` for observability.
79
+ - Index helper: `ensureIndexes(collection, { uuidPartialUnique: true, ownerIndex: true, deletedIndex: true })`.
80
+
81
+ Ownership (`_owner`)
82
+ - Purpose: `_owner` is not a tenant ID nor a technical auth scope. It’s a required, opinionated piece of metadata that records who a document belongs to (the data subject or system principal that created/owns it).
83
+ - Why it matters: Enables clear data lineage and supports privacy/jurisdiction workflows (GDPR/DSAR: “export all my data”, “delete my data”), audits, and stewardship.
84
+ - Typical values: a user’s UUID when a signed-in human creates the record; for automated/system operations use a stable identifier like `"system"`, `"service:mailer"`, or `"migration:2024-09-01"`.
85
+ - Not authorization: The library does not enforce access control based on `_owner`. Apply your authorization rules in the API/service layer using `_owner` as a helpful filter or join key.
86
+ - Multi-tenant setups: If you have tenants, keep a distinct `tenantId` (or similar) alongside `_owner`. `_owner` continues to model “who owns this record” rather than “which tenant it belongs to”.
87
+
88
+ Config
89
+ ```ts
90
+ import { K2DB } from "@frogfish/k2db";
91
+
92
+ const db = new K2DB({
93
+ name: "mydb",
94
+ hosts: [{ host: "cluster0.example.mongodb.net" }], // SRV if single host without port
95
+ user: process.env.DB_USER,
96
+ password: process.env.DB_PASS,
97
+ slowQueryMs: 300,
98
+ hooks: {
99
+ beforeQuery: (op, d) => {},
100
+ afterQuery: (op, d, ms) => {},
101
+ },
102
+ });
103
+
104
+ await db.init();
105
+ await db.ensureIndexes("myCollection");
106
+ ```
107
+
108
+ Environment loader
109
+ ```ts
110
+ const conf = K2DB.fromEnv(); // K2DB_NAME, K2DB_HOSTS, K2DB_USER, K2DB_PASSWORD, K2DB_REPLICASET, K2DB_SLOW_MS
111
+ ```
112
+
113
+ Tips
114
+ - Use `restore()` to clear `_deleted`.
115
+ - Use `purge()` to hard-delete; only works on soft-deleted docs.
116
+ - For aggregates with joins, the library automatically injects non-deleted filters in root and nested pipelines.
117
+
118
+ Versioning (optional)
119
+ - Per-document history is stored in a sibling collection named `<collection>__history`.
120
+ - Use `updateVersioned()` to snapshot the previous state before updating.
121
+ - Use `listVersions()` to see available versions and `revertToVersion()` to roll back (preserves metadata like `_uuid`, `_owner`, `_created`).
122
+
123
+ Example:
124
+ ```ts
125
+ // Save previous version and keep up to 20 versions
126
+ await db.ensureHistoryIndexes("hello");
127
+ await db.updateVersioned("hello", id, { message: "Hello v2" }, false, 20);
128
+
129
+ // List latest 5 versions
130
+ const versions = await db.listVersions("hello", id, 0, 5);
131
+
132
+ // Revert to a specific version
133
+ await db.revertToVersion("hello", id, versions[0]._v);
134
+ ```
135
+
136
+ Further examples:
137
+ ```ts
138
+ // Versioned replace
139
+ await db.updateVersioned("hello", id, { message: "replace payload" }, true);
140
+
141
+ // Keep only the most recent prior state (maxVersions = 1)
142
+ await db.updateVersioned("hello", id, { message: "v3" }, false, 1);
143
+ ```
144
+
145
+ **MongoDB Atlas**
146
+ - Create a Database User in Atlas and allow your IP under Network Access.
147
+ - Find your cluster address (looks like `cluster0.xxxxxx.mongodb.net`).
148
+ - Minimal config uses SRV (no port) when a single host is provided.
149
+
150
+ Example (direct config):
151
+ ```ts
152
+ import { K2DB } from "@frogfish/k2db";
153
+
154
+ const db = new K2DB({
155
+ name: "mydb", // your database name
156
+ hosts: [{ host: "cluster0.xxxxxx.mongodb.net" }], // Atlas SRV host
157
+ user: process.env.DB_USER, // Atlas DB user
158
+ password: process.env.DB_PASS, // Atlas DB password
159
+ slowQueryMs: 300,
160
+ });
161
+
162
+ await db.init();
163
+ ```
164
+
165
+ Example (env-based):
166
+ ```bash
167
+ export K2DB_NAME=mydb
168
+ export K2DB_HOSTS=cluster0.xxxxxx.mongodb.net
169
+ export K2DB_USER=your_user
170
+ export K2DB_PASSWORD=your_pass
171
+ node hello.mjs
172
+ ```
173
+ ```ts
174
+ // hello.mjs (Node 18+, ESM)
175
+ import { K2DB } from "@frogfish/k2db";
176
+
177
+ const conf = K2DB.fromEnv();
178
+ const db = new K2DB(conf);
179
+ await db.init();
180
+ ```
181
+
182
+ **Hello World**
183
+ - Connect to Atlas, insert into `hello` collection, then read it back.
184
+
185
+ ```ts
186
+ // hello-world.mjs
187
+ import { K2DB } from "@frogfish/k2db";
188
+
189
+ // Configure via env or inline config
190
+ const db = new K2DB({
191
+ name: "mydb",
192
+ hosts: [{ host: "cluster0.xxxxxx.mongodb.net" }],
193
+ user: process.env.DB_USER,
194
+ password: process.env.DB_PASS,
195
+ });
196
+
197
+ await db.init();
198
+ await db.ensureIndexes("hello"); // unique _uuid among non-deleted, plus helpful indexes
199
+
200
+ // Create a document (owner is required)
201
+ const { id } = await db.create("hello", "demo-owner", { message: "Hello, world!" });
202
+ console.log("Inserted id:", id);
203
+
204
+ // Read it back
205
+ const doc = await db.get("hello", id); // excludes soft-deleted by default
206
+ console.log("Retrieved:", doc);
207
+
208
+ // Soft delete it (optional)
209
+ await db.delete("hello", id);
210
+
211
+ // Restore it (optional)
212
+ await db.restore("hello", { _uuid: id });
213
+
214
+ await db.release();
215
+ ```
216
+
217
+ Notes
218
+ - Use Node 18+ (preferably Node 20+) for ESM + JSON imports.
219
+ - Atlas SRV requires only the cluster hostname (no port); the client handles TLS and topology.
220
+
221
+ Updates
222
+ - Patch vs Replace
223
+ - `update(collection, id, data)` patches by default using `$set` (fields you pass are updated, others remain).
224
+ - `update(collection, id, data, true)` replaces non‑metadata fields (PUT‑like). Metadata (`_uuid`, `_owner`, `_created`, `_updated`, `_deleted`) is preserved.
225
+ - Underscore‑prefixed fields in your input are ignored; `_updated` is refreshed automatically.
226
+
227
+ Examples:
228
+ ```ts
229
+ // Patch specific fields
230
+ await db.update("hello", id, { message: "patched value" });
231
+
232
+ // Replace (preserves metadata, overwrites non‑underscore fields)
233
+ await db.update("hello", id, { message: "entire new doc", count: 1 }, true);
234
+ ```
235
+
236
+ Schemas (optional, Zod)
237
+ - You can register a Zod schema per collection at runtime; it validates and (optionally) strips unknown fields on writes. Nothing is stored in DB.
238
+ - Modes: `strict` (reject unknown fields), `strip` (remove unknown; default), `passthrough` (allow unknown).
239
+
240
+ Example:
241
+ ```ts
242
+ import { z } from "zod";
243
+
244
+ // Define
245
+ const Hello = z
246
+ .object({
247
+ message: z.string(),
248
+ count: z.number().int().default(0),
249
+ })
250
+ .strip(); // default unknown-key behavior
251
+
252
+ // Register (in-memory for this instance)
253
+ db.setSchema("hello", Hello, { mode: "strip" });
254
+
255
+ // On create: full schema validation; on patch: partial validation
256
+ await db.create("hello", ownerId, { message: "hey", extra: "ignored" });
257
+ await db.update("hello", id, { count: 2 }); // partial OK
258
+
259
+ // To clear
260
+ db.clearSchema("hello");
261
+ ```
262
+
263
+ **Type Reference (Cheat Sheet)**
264
+ - `BaseDocument`: Core shape enforced by the library; apps may extend.
265
+ - `CreateResult`: `{ id: string }`
266
+ - `UpdateResult`: `{ updated: number }`
267
+ - `DeleteResult`: `{ deleted: number }`
268
+ - `RestoreResult`: `{ status: string; modified: number }`
269
+ - `CountResult`: `{ count: number }`
270
+ - `DropResult`: `{ status: string }`
271
+ - `PurgeResult`: `{ id: string }`
272
+ - `VersionedUpdateResult`: `{ updated: number; versionSaved: number }`
273
+ - `VersionInfo`: `{ _uuid: string; _v: number; _at: number }`
274
+
275
+ Returns by method
276
+ - `get(collection, id)`: `Promise<BaseDocument>`
277
+ - `find(collection, filter, params?, skip?, limit?)`: `Promise<BaseDocument[]>`
278
+ - `findOne(collection, criteria, fields?)`: `Promise<BaseDocument|null>`
279
+ - `aggregate(collection, pipeline, skip?, limit?)`: `Promise<BaseDocument[]>`
280
+ - `create(collection, owner, data)`: `Promise<CreateResult>`
281
+ - `update(collection, id, data, replace?)`: `Promise<UpdateResult>`
282
+ - `updateAll(collection, criteria, values)`: `Promise<UpdateResult>`
283
+ - `delete(collection, id)`: `Promise<DeleteResult>`
284
+ - `deleteAll(collection, criteria)`: `Promise<DeleteResult>`
285
+ - `purge(collection, id)`: `Promise<PurgeResult>`
286
+ - `restore(collection, criteria)`: `Promise<RestoreResult>`
287
+ - `count(collection, criteria)`: `Promise<CountResult>`
288
+ - `drop(collection)`: `Promise<DropResult>`
289
+ - `ensureIndexes(collection, opts?)`: `Promise<void>`
290
+ - `ensureHistoryIndexes(collection)`: `Promise<void>`
291
+ - `updateVersioned(collection, id, data, replace?, maxVersions?)`: `Promise<VersionedUpdateResult[]>`
292
+ - `listVersions(collection, id, skip?, limit?)`: `Promise<VersionInfo[]>`
293
+ - `revertToVersion(collection, id, version)`: `Promise<UpdateResult>`
294
+ - Zod registry:
295
+ - `setSchema(collection, zodSchema, { mode }?)`: `void`
296
+ - `clearSchema(collection)`: `void`
297
+ - `clearSchemas()`: `void`
package/data.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { K2DB, BaseDocument } from "./db";
1
+ import { K2DB, BaseDocument, CreateResult, UpdateResult, DeleteResult, RestoreResult, CountResult, DropResult, VersionedUpdateResult, VersionInfo } from "./db.js";
2
2
  export declare class K2Data {
3
3
  private db;
4
4
  private owner;
@@ -27,33 +27,43 @@ export declare class K2Data {
27
27
  /**
28
28
  * Creates a new document in the collection.
29
29
  */
30
- create(collectionName: string, data: Partial<BaseDocument>): Promise<{
31
- id: string;
32
- }>;
30
+ create(collectionName: string, data: Partial<BaseDocument>): Promise<CreateResult>;
33
31
  /**
34
32
  * Updates multiple documents based on criteria.
35
33
  */
36
- updateAll(collectionName: string, criteria: any, values: Partial<BaseDocument>): Promise<{
37
- updated: number;
38
- }>;
34
+ updateAll(collectionName: string, criteria: any, values: Partial<BaseDocument>): Promise<UpdateResult>;
39
35
  /**
40
36
  * Updates a single document by UUID.
41
37
  */
42
- update(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean): Promise<{
38
+ update(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean): Promise<UpdateResult>;
39
+ /**
40
+ * Updates a single document by UUID and saves the previous version to history.
41
+ */
42
+ updateVersioned(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean, maxVersions?: number): Promise<VersionedUpdateResult[]>;
43
+ /** List versions for a document (latest first). */
44
+ listVersions(collectionName: string, id: string, skip?: number, limit?: number): Promise<VersionInfo[]>;
45
+ /** Revert a document to a prior version. */
46
+ revertToVersion(collectionName: string, id: string, version: number): Promise<{
43
47
  updated: number;
44
48
  }>;
49
+ /** Ensure history collection indexes exist. */
50
+ ensureHistoryIndexes(collectionName: string): Promise<void>;
51
+ /** Register a Zod schema for a collection (in-memory). */
52
+ setSchema(collectionName: string, schema: any, options?: {
53
+ mode?: "strict" | "strip" | "passthrough";
54
+ }): void;
55
+ /** Clear a collection's schema. */
56
+ clearSchema(collectionName: string): void;
57
+ /** Clear all schemas. */
58
+ clearSchemas(): void;
45
59
  /**
46
60
  * Removes (soft deletes) multiple documents based on criteria.
47
61
  */
48
- deleteAll(collectionName: string, criteria: any): Promise<{
49
- deleted: number;
50
- }>;
62
+ deleteAll(collectionName: string, criteria: any): Promise<DeleteResult>;
51
63
  /**
52
64
  * Removes (soft deletes) a single document by UUID.
53
65
  */
54
- delete(collectionName: string, id: string): Promise<{
55
- deleted: number;
56
- }>;
66
+ delete(collectionName: string, id: string): Promise<DeleteResult>;
57
67
  /**
58
68
  * Permanently deletes a document that has been soft-deleted.
59
69
  */
@@ -63,22 +73,15 @@ export declare class K2Data {
63
73
  /**
64
74
  * Restores a soft-deleted document.
65
75
  */
66
- restore(collectionName: string, criteria: any): Promise<{
67
- status: string;
68
- modified: number;
69
- }>;
76
+ restore(collectionName: string, criteria: any): Promise<RestoreResult>;
70
77
  /**
71
78
  * Counts documents based on criteria.
72
79
  */
73
- count(collectionName: string, criteria: any): Promise<{
74
- count: number;
75
- }>;
80
+ count(collectionName: string, criteria: any): Promise<CountResult>;
76
81
  /**
77
82
  * Drops an entire collection.
78
83
  */
79
- drop(collectionName: string): Promise<{
80
- status: string;
81
- }>;
84
+ drop(collectionName: string): Promise<DropResult>;
82
85
  /**
83
86
  * Executes a transaction with the provided operations.
84
87
  */
package/data.js CHANGED
@@ -1,7 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.K2Data = void 0;
4
- class K2Data {
1
+ export class K2Data {
2
+ db;
3
+ owner;
5
4
  constructor(db, owner) {
6
5
  this.db = db;
7
6
  this.owner = owner;
@@ -55,6 +54,36 @@ class K2Data {
55
54
  // Ensure it returns { updated: number }
56
55
  return this.db.update(collectionName, id, data, replace);
57
56
  }
57
+ /**
58
+ * Updates a single document by UUID and saves the previous version to history.
59
+ */
60
+ async updateVersioned(collectionName, id, data, replace = false, maxVersions) {
61
+ return this.db.updateVersioned(collectionName, id, data, replace, maxVersions);
62
+ }
63
+ /** List versions for a document (latest first). */
64
+ async listVersions(collectionName, id, skip, limit) {
65
+ return this.db.listVersions(collectionName, id, skip, limit);
66
+ }
67
+ /** Revert a document to a prior version. */
68
+ async revertToVersion(collectionName, id, version) {
69
+ return this.db.revertToVersion(collectionName, id, version);
70
+ }
71
+ /** Ensure history collection indexes exist. */
72
+ async ensureHistoryIndexes(collectionName) {
73
+ return this.db.ensureHistoryIndexes(collectionName);
74
+ }
75
+ /** Register a Zod schema for a collection (in-memory). */
76
+ setSchema(collectionName, schema, options) {
77
+ return this.db.setSchema(collectionName, schema, options);
78
+ }
79
+ /** Clear a collection's schema. */
80
+ clearSchema(collectionName) {
81
+ return this.db.clearSchema(collectionName);
82
+ }
83
+ /** Clear all schemas. */
84
+ clearSchemas() {
85
+ return this.db.clearSchemas();
86
+ }
58
87
  /**
59
88
  * Removes (soft deletes) multiple documents based on criteria.
60
89
  */
@@ -117,4 +146,3 @@ class K2Data {
117
146
  return this.db.isHealthy();
118
147
  }
119
148
  }
120
- exports.K2Data = K2Data;
package/db.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ObjectId } from "mongodb";
2
+ import { ZodTypeAny } from "zod";
2
3
  export interface HostConfig {
3
4
  host: string;
4
5
  port?: number;
@@ -9,6 +10,11 @@ export interface DatabaseConfig {
9
10
  password?: string;
10
11
  hosts?: HostConfig[];
11
12
  replicaset?: string;
13
+ slowQueryMs?: number;
14
+ hooks?: {
15
+ beforeQuery?: (op: string, details: any) => void;
16
+ afterQuery?: (op: string, details: any, durationMs: number) => void;
17
+ };
12
18
  }
13
19
  export interface BaseDocument {
14
20
  _id?: ObjectId;
@@ -19,15 +25,53 @@ export interface BaseDocument {
19
25
  _deleted?: boolean;
20
26
  [key: string]: any;
21
27
  }
28
+ export interface CreateResult {
29
+ id: string;
30
+ }
31
+ export interface UpdateResult {
32
+ updated: number;
33
+ }
34
+ export interface DeleteResult {
35
+ deleted: number;
36
+ }
37
+ export interface RestoreResult {
38
+ status: string;
39
+ modified: number;
40
+ }
41
+ export interface CountResult {
42
+ count: number;
43
+ }
44
+ export interface DropResult {
45
+ status: string;
46
+ }
47
+ export interface PurgeResult {
48
+ id: string;
49
+ }
50
+ export interface VersionedUpdateResult {
51
+ updated: number;
52
+ versionSaved: number;
53
+ }
54
+ export interface VersionInfo {
55
+ _uuid: string;
56
+ _v: number;
57
+ _at: number;
58
+ }
22
59
  export declare class K2DB {
23
60
  private conf;
24
61
  private db;
25
62
  private connection;
63
+ private schemas;
26
64
  constructor(conf: DatabaseConfig);
27
65
  /**
28
66
  * Initializes the MongoDB connection.
29
67
  */
30
68
  init(): Promise<void>;
69
+ /**
70
+ * Build a robust MongoDB URI based on config (supports SRV and standard).
71
+ */
72
+ private buildMongoUri;
73
+ /** Load DatabaseConfig from environment variables. */
74
+ static fromEnv(prefix?: string): DatabaseConfig;
31
75
  /**
32
76
  * Retrieves a collection from the database.
33
77
  * @param collectionName - Name of the collection.
@@ -59,15 +103,18 @@ export declare class K2DB {
59
103
  * @param limit - Maximum number of documents to return.
60
104
  */
61
105
  aggregate(collectionName: string, criteria: any[], skip?: number, limit?: number): Promise<BaseDocument[]>;
106
+ /**
107
+ * Ensures an aggregation pipeline excludes soft-deleted documents for the root
108
+ * collection and any joined collections ($lookup, $unionWith, $graphLookup, $facet).
109
+ */
110
+ private static enforceNoDeletedInPipeline;
62
111
  /**
63
112
  * Creates a new document in the collection.
64
113
  * @param collectionName - Name of the collection.
65
114
  * @param owner - Owner of the document.
66
115
  * @param data - Data to insert.
67
116
  */
68
- create(collectionName: string, owner: string, data: Partial<BaseDocument>): Promise<{
69
- id: string;
70
- }>;
117
+ create(collectionName: string, owner: string, data: Partial<BaseDocument>): Promise<CreateResult>;
71
118
  /**
72
119
  * Updates multiple documents based on criteria.
73
120
  * Can either replace the documents or patch them.
@@ -75,9 +122,7 @@ export declare class K2DB {
75
122
  * @param criteria - Update criteria.
76
123
  * @param values - Values to update or replace with.
77
124
  */
78
- updateAll(collectionName: string, criteria: any, values: Partial<BaseDocument>): Promise<{
79
- updated: number;
80
- }>;
125
+ updateAll(collectionName: string, criteria: any, values: Partial<BaseDocument>): Promise<UpdateResult>;
81
126
  /**
82
127
  * Updates a single document by UUID.
83
128
  * Can either replace the document or patch it.
@@ -86,62 +131,62 @@ export declare class K2DB {
86
131
  * @param data - Data to update or replace with.
87
132
  * @param replace - If true, replaces the entire document (PUT), otherwise patches (PATCH).
88
133
  */
89
- update(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean): Promise<{
90
- updated: number;
91
- }>;
134
+ update(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean): Promise<UpdateResult>;
92
135
  /**
93
136
  * Removes (soft deletes) multiple documents based on criteria.
94
137
  * @param collectionName - Name of the collection.
95
138
  * @param criteria - Removal criteria.
96
139
  */
97
- deleteAll(collectionName: string, criteria: any): Promise<{
98
- deleted: number;
99
- }>;
140
+ deleteAll(collectionName: string, criteria: any): Promise<DeleteResult>;
100
141
  /**
101
142
  * Removes (soft deletes) a single document by UUID.
102
143
  * @param collectionName - Name of the collection.
103
144
  * @param id - UUID of the document.
104
145
  */
105
- delete(collectionName: string, id: string): Promise<{
106
- deleted: number;
107
- }>;
146
+ delete(collectionName: string, id: string): Promise<DeleteResult>;
108
147
  /**
109
148
  * Permanently deletes a document that has been soft-deleted.
110
149
  * @param collectionName - Name of the collection.
111
150
  * @param id - UUID of the document.
112
151
  */
113
- purge(collectionName: string, id: string): Promise<{
114
- id: string;
115
- }>;
152
+ purge(collectionName: string, id: string): Promise<PurgeResult>;
116
153
  /**
117
154
  * Restores a soft-deleted document.
118
155
  * @param collectionName - Name of the collection.
119
156
  * @param criteria - Criteria to identify the document.
120
157
  */
121
- restore(collectionName: string, criteria: any): Promise<{
122
- status: string;
123
- modified: number;
124
- }>;
158
+ restore(collectionName: string, criteria: any): Promise<RestoreResult>;
125
159
  /**
126
160
  * Counts documents based on criteria.
127
161
  * @param collectionName - Name of the collection.
128
162
  * @param criteria - Counting criteria.
129
163
  */
130
- count(collectionName: string, criteria: any): Promise<{
131
- count: number;
132
- }>;
164
+ count(collectionName: string, criteria: any): Promise<CountResult>;
133
165
  /**
134
166
  * Drops an entire collection.
135
167
  * @param collectionName - Name of the collection.
136
168
  */
137
- drop(collectionName: string): Promise<{
138
- status: string;
139
- }>;
169
+ drop(collectionName: string): Promise<DropResult>;
140
170
  /**
141
171
  * Sanitizes aggregation criteria.
142
172
  * @param criteria - Aggregation stage criteria.
143
173
  */
144
174
  private static sanitiseCriteria;
175
+ /** Strip any user-provided fields that start with '_' (reserved). */
176
+ private static stripReservedFields;
177
+ /**
178
+ * Run an async DB operation with timing, slow logging, and hooks.
179
+ */
180
+ private runTimed;
181
+ /**
182
+ * Ensure commonly needed indexes exist.
183
+ */
184
+ ensureIndexes(collectionName: string, opts?: {
185
+ uuidUnique?: boolean;
186
+ uuidPartialUnique?: boolean;
187
+ ownerIndex?: boolean;
188
+ deletedIndex?: boolean;
189
+ }): Promise<void>;
145
190
  /**
146
191
  * Optional: Executes a transaction with the provided operations.
147
192
  * @param operations - A function that performs operations within a transaction session.
@@ -182,4 +227,35 @@ export declare class K2DB {
182
227
  * @returns A normalized error of type `Error`.
183
228
  */
184
229
  private normalizeError;
230
+ /** Name of the history collection for a given collection. */
231
+ private historyName;
232
+ /** Register a Zod schema for a collection. */
233
+ setSchema(collectionName: string, schema: ZodTypeAny, options?: {
234
+ mode?: "strict" | "strip" | "passthrough";
235
+ }): void;
236
+ /** Clear a collection's schema. */
237
+ clearSchema(collectionName: string): void;
238
+ /** Clear all schemas. */
239
+ clearSchemas(): void;
240
+ /** Apply registered schema (if any) to data. For updates, partial=true allows partial input. */
241
+ private applySchema;
242
+ /** Get the history collection. */
243
+ private getHistoryCollection;
244
+ /** Ensure indexes for history tracking. */
245
+ ensureHistoryIndexes(collectionName: string): Promise<void>;
246
+ /** Compute the next version number for a document. */
247
+ private nextVersion;
248
+ /** Save a snapshot of the current document into the history collection. */
249
+ private snapshotCurrent;
250
+ /**
251
+ * Update a document and keep the previous version in a history collection.
252
+ * If maxVersions is provided, prunes oldest snapshots beyond that number.
253
+ */
254
+ updateVersioned(collectionName: string, id: string, data: Partial<BaseDocument>, replace?: boolean, maxVersions?: number): Promise<VersionedUpdateResult[]>;
255
+ /** List versions (latest first). */
256
+ listVersions(collectionName: string, id: string, skip?: number, limit?: number): Promise<VersionInfo[]>;
257
+ /** Revert the current document to a specific historical version (preserves metadata). */
258
+ revertToVersion(collectionName: string, id: string, version: number): Promise<{
259
+ updated: number;
260
+ }>;
185
261
  }