@puzzle-ui/storage-mongodb 0.0.0-stage → 0.5.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.
package/README.md CHANGED
@@ -1,3 +1,46 @@
1
- # Temporary Holding Version
1
+ # @puzzle-ui/storage-mongodb
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A MongoDB implementation of the vendor-neutral contracts from `@puzzle-ui/storage`. It provides CRUD, shared query operators, conditional writes, cursor pagination, unique and TTL indexes, and versioned migrations. MongoDB's multi-document transactions and atomic batches are opt-in.
4
+
5
+ **Documentation: [puzzle-ui.com/docs/storage](https://puzzle-ui.com/docs/storage#mongodb-adapter)**
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install @puzzle-ui/storage @puzzle-ui/storage-mongodb
11
+ # or: bun add @puzzle-ui/storage @puzzle-ui/storage-mongodb
12
+ ```
13
+
14
+ ## Use
15
+
16
+ ```ts
17
+ import { MongoClient } from "mongodb";
18
+ import { MongoStorageAdapter } from "@puzzle-ui/storage-mongodb";
19
+
20
+ const client = new MongoClient(process.env.MONGODB_URI!);
21
+ await client.connect();
22
+ const storage = new MongoStorageAdapter({
23
+ database: client.db("app"),
24
+ client,
25
+ transactionsEnabled: true,
26
+ });
27
+
28
+ await storage.registerCollection({
29
+ name: "sessions",
30
+ indexes: [{ name: "by-user", fields: ["userId"] }],
31
+ });
32
+ ```
33
+
34
+ Use a replica set or sharded deployment for multi-document transactions. Pass the client and set `transactionsEnabled: true` to opt in; otherwise `atomicBatch` is not available. The default collection prefix is `puzzle_`; set `collectionPrefix` to change it. A `StorageIndex` with `expireAfterSeconds` creates a MongoDB TTL index; TTL deletion is asynchronous, so enforce security-sensitive expiry in application logic as well. Close the client during application shutdown.
35
+
36
+ `MongoStorageMigrationAdapter` stores versioned migration status and a shared migration lock in prefixed collections. See the [storage guide](https://puzzle-ui.com/docs/storage) for adapter guarantees, index behavior, migration options, and custom adapter testing.
37
+
38
+ ## Development
39
+
40
+ ```sh
41
+ bun run build
42
+ bun run typecheck
43
+ bun run test
44
+ ```
45
+
46
+ The test script requires Docker and Docker Compose; it starts a MongoDB replica set for adapter tests.
package/dist/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sjoerd van Den Berg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_mongodb_adapter = require("./mongodb.adapter.cjs");
3
+ const require_mongodb_migrations = require("./mongodb.migrations.cjs");
4
+ exports.MongoStorageAdapter = require_mongodb_adapter.MongoStorageAdapter;
5
+ exports.MongoStorageMigrationAdapter = require_mongodb_migrations.MongoStorageMigrationAdapter;
@@ -0,0 +1,4 @@
1
+ import { MongoStorageAdapter } from "./mongodb.adapter.cjs";
2
+ import { MongoStorageMigrationAdapter } from "./mongodb.migrations.cjs";
3
+ import { MongoStorageAdapterOptions, MongoStorageMigrationOptions } from "./mongodb.types.cjs";
4
+ export { MongoStorageAdapter, type MongoStorageAdapterOptions, MongoStorageMigrationAdapter, type MongoStorageMigrationOptions };
@@ -0,0 +1,4 @@
1
+ import { MongoStorageAdapter } from "./mongodb.adapter.js";
2
+ import { MongoStorageMigrationAdapter } from "./mongodb.migrations.js";
3
+ import { MongoStorageAdapterOptions, MongoStorageMigrationOptions } from "./mongodb.types.js";
4
+ export { MongoStorageAdapter, type MongoStorageAdapterOptions, MongoStorageMigrationAdapter, type MongoStorageMigrationOptions };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { MongoStorageAdapter } from "./mongodb.adapter.js";
2
+ import { MongoStorageMigrationAdapter } from "./mongodb.migrations.js";
3
+ export { MongoStorageAdapter, MongoStorageMigrationAdapter };
@@ -0,0 +1,320 @@
1
+ require("mongodb");
2
+ let _puzzle_ui_storage = require("@puzzle-ui/storage");
3
+ // src/mongodb.adapter.ts
4
+ const isDuplicateError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === 11e3;
5
+ const mapMongoError = (error) => {
6
+ if (isDuplicateError(error)) throw new _puzzle_ui_storage.StorageDuplicateError("mongodb_unique_constraint");
7
+ throw error;
8
+ };
9
+ const toMongoField = (field) => field === "id" ? "_id" : field;
10
+ const encodeCursor = (payload) => ({ value: Buffer.from(JSON.stringify(payload)).toString("base64url") });
11
+ const decodeCursor = (cursor) => {
12
+ try {
13
+ return JSON.parse(Buffer.from(cursor.value, "base64url").toString("utf8"));
14
+ } catch {
15
+ throw new _puzzle_ui_storage.StorageValidationError("Invalid MongoDB cursor.");
16
+ }
17
+ };
18
+ const toRecord = (document) => {
19
+ if (!document) return null;
20
+ const { _id, ...data } = document;
21
+ return {
22
+ ...data,
23
+ id: String(_id)
24
+ };
25
+ };
26
+ const toDocument = (record) => {
27
+ const { id, createdAt, updatedAt, revision, ...data } = record;
28
+ const now = /* @__PURE__ */ new Date();
29
+ return {
30
+ ...data,
31
+ _id: id,
32
+ createdAt: createdAt ?? now,
33
+ updatedAt: updatedAt ?? now,
34
+ revision: revision ?? 1
35
+ };
36
+ };
37
+ const toMongoWhere = (where) => {
38
+ if (!where) return {};
39
+ const result = [];
40
+ for (const [field, comparison] of Object.entries(where)) {
41
+ if (field === "and" || field === "or") {
42
+ const nested = comparison.map((item) => toMongoWhere(item));
43
+ result.push({ [field === "and" ? "$and" : "$or"]: nested });
44
+ continue;
45
+ }
46
+ const value = comparison;
47
+ if (typeof value !== "object" || value === null || !("operator" in value) || typeof value.operator !== "string" || !("value" in value)) throw new _puzzle_ui_storage.StorageValidationError(`Invalid query comparison for field: ${field}`);
48
+ const mongoField = toMongoField(field);
49
+ const operator = {
50
+ eq: "$eq",
51
+ ne: "$ne",
52
+ in: "$in",
53
+ notIn: "$nin",
54
+ gt: "$gt",
55
+ gte: "$gte",
56
+ lt: "$lt",
57
+ lte: "$lte",
58
+ exists: "$exists"
59
+ }[value.operator];
60
+ if (!operator) throw new _puzzle_ui_storage.StorageValidationError(`Unsupported query operator: ${value.operator}`);
61
+ result.push({ [mongoField]: { [operator]: value.value } });
62
+ }
63
+ if (result.length === 0) return {};
64
+ if (result.length === 1) return result[0];
65
+ return { $and: result };
66
+ };
67
+ const toMongoSort = (orderBy) => {
68
+ const sort = {};
69
+ for (const item of orderBy ?? []) sort[toMongoField(String(item.field))] = item.direction === "asc" ? 1 : -1;
70
+ if (!("_id" in sort)) sort._id = 1;
71
+ return sort;
72
+ };
73
+ const toCursorFilter = (orderBy, values) => {
74
+ const fields = [...orderBy.map((item) => ({
75
+ field: toMongoField(String(item.field)),
76
+ direction: item.direction
77
+ }))];
78
+ if (!fields.some((item) => item.field === "_id")) fields.push({
79
+ field: "_id",
80
+ direction: "asc"
81
+ });
82
+ const branches = [];
83
+ for (let index = 0; index < fields.length; index++) {
84
+ const prefix = [];
85
+ for (let previous = 0; previous < index; previous++) prefix.push({ [fields[previous].field]: values[previous] });
86
+ const field = fields[index];
87
+ const operator = field.direction === "asc" ? "$gt" : "$lt";
88
+ branches.push({ $and: [...prefix, { [field.field]: { [operator]: values[index] } }] });
89
+ }
90
+ return { $or: branches };
91
+ };
92
+ const buildCondition = (condition) => {
93
+ if (!condition) return {};
94
+ const result = {};
95
+ if (condition.expectedRevision !== void 0) result.revision = condition.expectedRevision;
96
+ if (condition.expectedUpdatedAt !== void 0) result.updatedAt = condition.expectedUpdatedAt;
97
+ return result;
98
+ };
99
+ const cleanChanges = (changes) => {
100
+ const result = { ...changes };
101
+ delete result.id;
102
+ delete result.createdAt;
103
+ delete result.updatedAt;
104
+ delete result.revision;
105
+ return result;
106
+ };
107
+ var MongoStorageAdapter = class {
108
+ capabilities;
109
+ database;
110
+ client;
111
+ prefix;
112
+ constructor(options) {
113
+ this.database = options.database;
114
+ this.client = options.client;
115
+ this.prefix = options.collectionPrefix ?? "puzzle_";
116
+ const transactionsEnabled = options.transactionsEnabled === true && !!options.client;
117
+ this.capabilities = {
118
+ transactions: transactionsEnabled,
119
+ atomicBatch: transactionsEnabled,
120
+ conditionalWrites: true,
121
+ uniqueConstraints: true,
122
+ upsert: true,
123
+ cursorPagination: true,
124
+ migrations: true,
125
+ ttlIndexes: true
126
+ };
127
+ }
128
+ getCapabilities() {
129
+ const limitations = this.capabilities.transactions ? [] : ["Multi-document transactions are disabled. Configure a replica set or sharded deployment and set transactionsEnabled to true."];
130
+ return {
131
+ capabilities: this.capabilities,
132
+ limitations
133
+ };
134
+ }
135
+ collection(name) {
136
+ return this.database.collection(`${this.prefix}${name}`);
137
+ }
138
+ async registerCollection(definition) {
139
+ const collectionName = `${this.prefix}${definition.name}`;
140
+ try {
141
+ await this.database.createCollection(collectionName);
142
+ } catch (error) {
143
+ if (!(typeof error === "object" && error !== null && "code" in error && error.code === 48)) throw error;
144
+ }
145
+ const collection = this.database.collection(collectionName);
146
+ const indexes = (definition.indexes ?? []).map((index) => ({
147
+ key: Object.fromEntries(index.fields.map((field) => [toMongoField(String(field)), 1])),
148
+ name: index.name,
149
+ unique: index.unique ?? false,
150
+ ...index.expireAfterSeconds !== void 0 ? { expireAfterSeconds: index.expireAfterSeconds } : {}
151
+ }));
152
+ if (indexes.length > 0) await collection.createIndexes(indexes);
153
+ }
154
+ async findById(collection, id) {
155
+ const document = await this.collection(collection).findOne({ _id: id });
156
+ return toRecord(document);
157
+ }
158
+ async findOne(collection, query) {
159
+ (0, _puzzle_ui_storage.validateQuery)(query);
160
+ const document = await this.collection(collection).findOne(toMongoWhere(query.where), {
161
+ sort: toMongoSort(query.orderBy),
162
+ ...query.offset !== void 0 ? { skip: query.offset } : {}
163
+ });
164
+ return toRecord(document);
165
+ }
166
+ async findMany(collection, query = {}) {
167
+ (0, _puzzle_ui_storage.validateQuery)(query);
168
+ return (await this.collection(collection).find(toMongoWhere(query.where)).sort(toMongoSort(query.orderBy)).skip(query.offset ?? 0).limit(query.limit ?? 0).toArray()).map((document) => toRecord(document));
169
+ }
170
+ async findPage(collection, query) {
171
+ if (!Number.isInteger(query.first) || query.first < 1) throw new _puzzle_ui_storage.StorageValidationError("Cursor page size must be a positive integer.");
172
+ const orderBy = query.orderBy ?? [{
173
+ field: "id",
174
+ direction: "asc"
175
+ }];
176
+ let filter = toMongoWhere(query.where);
177
+ if (query.after) {
178
+ const payload = decodeCursor(query.after);
179
+ const cursorFilter = toCursorFilter(orderBy, payload.values);
180
+ filter = Object.keys(filter).length ? { $and: [filter, cursorFilter] } : cursorFilter;
181
+ }
182
+ const sort = toMongoSort(orderBy);
183
+ const documents = await this.collection(collection).find(filter).sort(sort).limit(query.first + 1).toArray();
184
+ const hasNextPage = documents.length > query.first;
185
+ const items = documents.slice(0, query.first);
186
+ const last = items.at(-1);
187
+ const cursorFields = [...orderBy.map((item) => toMongoField(String(item.field)))];
188
+ if (!cursorFields.includes("_id")) cursorFields.push("_id");
189
+ const nextCursor = hasNextPage && last ? encodeCursor({ values: cursorFields.map((field) => field === "_id" ? last._id : last[field]) }) : null;
190
+ return {
191
+ items: items.map((item) => toRecord(item)),
192
+ hasNextPage,
193
+ nextCursor
194
+ };
195
+ }
196
+ async insert(collection, record) {
197
+ const document = toDocument(record);
198
+ try {
199
+ await this.collection(collection).insertOne(document);
200
+ return toRecord(document);
201
+ } catch (error) {
202
+ return mapMongoError(error);
203
+ }
204
+ }
205
+ async upsert(collection, query, record) {
206
+ (0, _puzzle_ui_storage.validateQuery)(query);
207
+ const existing = await this.findOne(collection, query);
208
+ if (existing) return await this.update(collection, existing.id, record) ?? existing;
209
+ return this.insert(collection, record);
210
+ }
211
+ async update(collection, id, changes, condition) {
212
+ const target = this.collection(collection);
213
+ const filter = {
214
+ _id: id,
215
+ ...buildCondition(condition)
216
+ };
217
+ const update = {
218
+ $set: {
219
+ ...cleanChanges(changes),
220
+ updatedAt: /* @__PURE__ */ new Date()
221
+ },
222
+ $inc: { revision: 1 }
223
+ };
224
+ if ((await target.updateOne(filter, update)).matchedCount === 0) {
225
+ if (condition && await target.findOne({ _id: id })) throw new _puzzle_ui_storage.StorageConflictError();
226
+ return null;
227
+ }
228
+ return toRecord(await target.findOne({ _id: id }));
229
+ }
230
+ async updateMany(collection, query, changes) {
231
+ (0, _puzzle_ui_storage.validateQuery)(query);
232
+ return (await this.collection(collection).updateMany(toMongoWhere(query.where), {
233
+ $set: {
234
+ ...cleanChanges(changes),
235
+ updatedAt: /* @__PURE__ */ new Date()
236
+ },
237
+ $inc: { revision: 1 }
238
+ })).modifiedCount;
239
+ }
240
+ async delete(collection, id, condition) {
241
+ const target = this.collection(collection);
242
+ if ((await target.deleteOne({
243
+ _id: id,
244
+ ...buildCondition(condition)
245
+ })).deletedCount > 0) return true;
246
+ if (condition && await target.findOne({ _id: id })) throw new _puzzle_ui_storage.StorageConflictError();
247
+ return false;
248
+ }
249
+ async deleteMany(collection, query) {
250
+ (0, _puzzle_ui_storage.validateQuery)(query);
251
+ return (await this.collection(collection).deleteMany(toMongoWhere(query.where))).deletedCount;
252
+ }
253
+ async exists(collection, query) {
254
+ (0, _puzzle_ui_storage.validateQuery)(query);
255
+ return await this.collection(collection).findOne(toMongoWhere(query.where), { projection: { _id: 1 } }) !== null;
256
+ }
257
+ async count(collection, query = {}) {
258
+ (0, _puzzle_ui_storage.validateQuery)(query);
259
+ return this.collection(collection).countDocuments(toMongoWhere(query.where));
260
+ }
261
+ async atomicBatch(operations) {
262
+ if (!this.capabilities.atomicBatch || !this.client) throw new _puzzle_ui_storage.StorageAtomicityError("MongoDB atomicBatch requires an explicitly enabled transaction-capable deployment.");
263
+ const session = this.client.startSession();
264
+ const results = [];
265
+ try {
266
+ await session.withTransaction(async () => {
267
+ results.length = 0;
268
+ for (const operation of operations) {
269
+ const target = this.collection(operation.collection);
270
+ if (operation.type === "insert") {
271
+ const document = toDocument(operation.record);
272
+ await target.insertOne(document, { session });
273
+ results.push({
274
+ type: "insert",
275
+ record: toRecord(document)
276
+ });
277
+ continue;
278
+ }
279
+ if (operation.type === "update") {
280
+ if ((await target.updateOne({
281
+ _id: operation.id,
282
+ ...buildCondition(operation.condition)
283
+ }, {
284
+ $set: {
285
+ ...cleanChanges(operation.changes),
286
+ updatedAt: /* @__PURE__ */ new Date()
287
+ },
288
+ $inc: { revision: 1 }
289
+ }, { session })).matchedCount === 0 && operation.condition) throw new _puzzle_ui_storage.StorageConflictError();
290
+ const updated = await target.findOne({ _id: operation.id }, { session });
291
+ results.push({
292
+ type: "update",
293
+ record: toRecord(updated)
294
+ });
295
+ continue;
296
+ }
297
+ const result = await target.deleteOne({
298
+ _id: operation.id,
299
+ ...buildCondition(operation.condition)
300
+ }, { session });
301
+ if (result.deletedCount === 0 && operation.condition && await target.findOne({ _id: operation.id }, { session })) throw new _puzzle_ui_storage.StorageConflictError();
302
+ results.push({
303
+ type: "delete",
304
+ deleted: result.deletedCount > 0
305
+ });
306
+ }
307
+ });
308
+ return { results };
309
+ } catch (error) {
310
+ if (error instanceof _puzzle_ui_storage.StorageConflictError || error instanceof _puzzle_ui_storage.StorageAtomicityError) throw error;
311
+ if (isDuplicateError(error)) throw new _puzzle_ui_storage.StorageDuplicateError("mongodb_unique_constraint");
312
+ throw error;
313
+ } finally {
314
+ await session.endSession();
315
+ }
316
+ }
317
+ };
318
+ exports.MongoStorageAdapter = MongoStorageAdapter;
319
+
320
+ //# sourceMappingURL=mongodb.adapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb.adapter.cjs","names":["StorageDuplicateError","StorageValidationError","StorageConflictError","StorageAtomicityError"],"sources":["../src/mongodb.adapter.ts"],"sourcesContent":["import {\n type Document,\n type Filter,\n type MongoServerError,\n type Sort,\n type UpdateFilter,\n type WithId,\n} from \"mongodb\";\n\nimport {\n StorageAtomicityError,\n StorageConflictError,\n StorageDuplicateError,\n StorageValidationError,\n type StorageAdapter,\n type StorageBatchOperation,\n type StorageBatchResult,\n type StorageCollection,\n type StorageCollectionName,\n type StorageCondition,\n type StorageCursor,\n type StorageCursorQuery,\n type StoragePage,\n type StorageQuery,\n type StorageRecord,\n type StorageUpdate,\n type StorageWhere,\n type StorageCapabilities,\n type StorageCapabilityReport,\n validateQuery,\n type StorageInsert,\n} from \"@puzzle-ui/storage\";\n\nimport type { MongoStorageAdapterOptions } from \"./mongodb.types\";\n\ntype MongoRecord = Document & {\n _id: string;\n createdAt: Date;\n updatedAt: Date;\n revision: number;\n};\n\ntype CursorPayload = {\n readonly values: readonly unknown[];\n};\n\nconst isDuplicateError = (error: unknown): error is MongoServerError =>\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as MongoServerError).code === 11000;\n\nconst mapMongoError = (error: unknown): never => {\n if (isDuplicateError(error)) {\n throw new StorageDuplicateError(\"mongodb_unique_constraint\");\n }\n\n throw error;\n};\n\nconst toMongoField = (field: string) => (field === \"id\" ? \"_id\" : field);\n\nconst toMongoValue = (value: unknown) =>\n value instanceof Date ? value : value;\n\nconst encodeCursor = (payload: CursorPayload): StorageCursor => ({\n value: Buffer.from(JSON.stringify(payload)).toString(\"base64url\"),\n});\n\nconst decodeCursor = (cursor: StorageCursor): CursorPayload => {\n try {\n return JSON.parse(\n Buffer.from(cursor.value, \"base64url\").toString(\"utf8\"),\n ) as CursorPayload;\n } catch {\n throw new StorageValidationError(\"Invalid MongoDB cursor.\");\n }\n};\n\nconst toRecord = <T extends StorageRecord>(\n document: WithId<MongoRecord> | null,\n): T | null => {\n if (!document) return null;\n\n const { _id, ...data } = document;\n\n return {\n ...data,\n id: String(_id),\n } as T;\n};\n\nconst toDocument = <T extends StorageRecord>(\n record: Partial<T> & { id: string },\n): MongoRecord => {\n const { id, createdAt, updatedAt, revision, ...data } =\n record as StorageRecord & Document;\n\n const now = new Date();\n\n return {\n ...data,\n _id: id,\n createdAt: createdAt ?? now,\n updatedAt: updatedAt ?? now,\n revision: revision ?? 1,\n };\n};\n\nconst toMongoWhere = <T extends object>(\n where?: StorageWhere<T>,\n): Filter<MongoRecord> => {\n if (!where) return {};\n\n const result: Filter<MongoRecord>[] = [];\n\n for (const [field, comparison] of Object.entries(where)) {\n if (field === \"and\" || field === \"or\") {\n const nested = (comparison as readonly StorageWhere<T>[]).map((item) =>\n toMongoWhere(item),\n );\n\n result.push({\n [field === \"and\" ? \"$and\" : \"$or\"]: nested,\n });\n\n continue;\n }\n\n const value: unknown = comparison;\n\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"operator\" in value) ||\n typeof value.operator !== \"string\" ||\n !(\"value\" in value)\n ) {\n throw new StorageValidationError(\n `Invalid query comparison for field: ${field}`,\n );\n }\n\n const mongoField = toMongoField(field);\n\n const operators: Record<string, string> = {\n eq: \"$eq\",\n ne: \"$ne\",\n in: \"$in\",\n notIn: \"$nin\",\n gt: \"$gt\",\n gte: \"$gte\",\n lt: \"$lt\",\n lte: \"$lte\",\n exists: \"$exists\",\n };\n\n const operator = operators[value.operator];\n\n if (!operator) {\n throw new StorageValidationError(\n `Unsupported query operator: ${value.operator}`,\n );\n }\n\n result.push({\n [mongoField]: {\n [operator]: value.value,\n },\n });\n }\n\n if (result.length === 0) return {};\n if (result.length === 1) return result[0]!;\n\n return { $and: result };\n};\n\nconst toMongoSort = <T extends StorageRecord>(\n orderBy?: StorageQuery<T>[\"orderBy\"],\n): Sort => {\n const sort: Record<string, 1 | -1> = {};\n\n for (const item of orderBy ?? []) {\n sort[toMongoField(String(item.field))] = item.direction === \"asc\" ? 1 : -1;\n }\n\n if (!(\"_id\" in sort)) {\n sort._id = 1;\n }\n\n return sort;\n};\n\nconst toCursorFilter = <T extends StorageRecord>(\n orderBy: NonNullable<StorageQuery<T>[\"orderBy\"]>,\n values: readonly unknown[],\n): Filter<MongoRecord> => {\n const fields = [\n ...orderBy.map((item) => ({\n field: toMongoField(String(item.field)),\n direction: item.direction,\n })),\n ];\n\n if (!fields.some((item) => item.field === \"_id\")) {\n fields.push({ field: \"_id\", direction: \"asc\" });\n }\n\n const branches: Filter<MongoRecord>[] = [];\n\n for (let index = 0; index < fields.length; index++) {\n const prefix: Filter<MongoRecord>[] = [];\n\n for (let previous = 0; previous < index; previous++) {\n prefix.push({\n [fields[previous]!.field]: values[previous],\n });\n }\n\n const field = fields[index]!;\n const operator = field.direction === \"asc\" ? \"$gt\" : \"$lt\";\n\n branches.push({\n $and: [\n ...prefix,\n {\n [field.field]: {\n [operator]: values[index],\n },\n },\n ],\n });\n }\n\n return { $or: branches };\n};\n\nconst buildCondition = (condition?: StorageCondition): Document => {\n if (!condition) return {};\n\n const result: Document = {};\n\n if (condition.expectedRevision !== undefined) {\n result.revision = condition.expectedRevision;\n }\n\n if (condition.expectedUpdatedAt !== undefined) {\n result.updatedAt = condition.expectedUpdatedAt;\n }\n\n return result;\n};\n\nconst cleanChanges = <T extends StorageRecord>(\n changes: StorageUpdate<T>,\n): Document => {\n const result = { ...changes } as Document;\n\n delete result.id;\n delete result.createdAt;\n delete result.updatedAt;\n delete result.revision;\n\n return result;\n};\n\nexport class MongoStorageAdapter implements StorageAdapter {\n readonly capabilities: StorageCapabilities;\n\n private readonly database: MongoStorageAdapterOptions[\"database\"];\n private readonly client: MongoStorageAdapterOptions[\"client\"];\n private readonly prefix: string;\n\n constructor(options: MongoStorageAdapterOptions) {\n this.database = options.database;\n this.client = options.client;\n this.prefix = options.collectionPrefix ?? \"puzzle_\";\n\n const transactionsEnabled =\n options.transactionsEnabled === true && !!options.client;\n\n this.capabilities = {\n transactions: transactionsEnabled,\n atomicBatch: transactionsEnabled,\n conditionalWrites: true,\n uniqueConstraints: true,\n upsert: true,\n cursorPagination: true,\n migrations: true,\n ttlIndexes: true,\n };\n }\n\n getCapabilities(): StorageCapabilityReport {\n const limitations = this.capabilities.transactions\n ? []\n : [\n \"Multi-document transactions are disabled. Configure a replica set or sharded deployment and set transactionsEnabled to true.\",\n ];\n\n return {\n capabilities: this.capabilities,\n limitations,\n };\n }\n\n private collection<T extends StorageRecord>(name: StorageCollectionName) {\n return this.database.collection<MongoRecord>(`${this.prefix}${name}`);\n }\n\n async registerCollection<T extends StorageRecord>(\n definition: StorageCollection<T>,\n ): Promise<void> {\n const collectionName = `${this.prefix}${definition.name}`;\n\n try {\n await this.database.createCollection(collectionName);\n } catch (error) {\n if (\n !(\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code: number }).code === 48\n )\n ) {\n throw error;\n }\n }\n\n const collection = this.database.collection(collectionName);\n\n const indexes = (definition.indexes ?? []).map((index) => ({\n key: Object.fromEntries(\n index.fields.map((field) => [toMongoField(String(field)), 1]),\n ),\n name: index.name,\n unique: index.unique ?? false,\n ...(index.expireAfterSeconds !== undefined\n ? { expireAfterSeconds: index.expireAfterSeconds }\n : {}),\n }));\n\n if (indexes.length > 0) {\n await collection.createIndexes(indexes);\n }\n }\n\n async findById<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n ): Promise<T | null> {\n const document = await this.collection(collection).findOne({\n _id: id,\n });\n\n return toRecord<T>(document);\n }\n\n async findOne<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<T | null> {\n validateQuery(query);\n\n const document = await this.collection(collection).findOne(\n toMongoWhere(query.where),\n {\n sort: toMongoSort(query.orderBy),\n ...(query.offset !== undefined ? { skip: query.offset } : {}),\n },\n );\n\n return toRecord<T>(document);\n }\n\n async findMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T> = {},\n ): Promise<readonly T[]> {\n validateQuery(query);\n\n const documents = await this.collection(collection)\n .find(toMongoWhere(query.where))\n .sort(toMongoSort(query.orderBy))\n .skip(query.offset ?? 0)\n .limit(query.limit ?? 0)\n .toArray();\n\n return documents.map((document) => toRecord<T>(document) as T);\n }\n\n async findPage<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageCursorQuery<T>,\n ): Promise<StoragePage<T>> {\n if (!Number.isInteger(query.first) || query.first < 1) {\n throw new StorageValidationError(\n \"Cursor page size must be a positive integer.\",\n );\n }\n\n const orderBy = query.orderBy ?? [\n { field: \"id\" as keyof T & string, direction: \"asc\" as const },\n ];\n\n let filter = toMongoWhere(query.where);\n\n if (query.after) {\n const payload = decodeCursor(query.after);\n\n const cursorFilter = toCursorFilter(orderBy, payload.values);\n\n filter = Object.keys(filter).length\n ? { $and: [filter, cursorFilter] }\n : cursorFilter;\n }\n\n const sort = toMongoSort(orderBy);\n\n const documents = await this.collection(collection)\n .find(filter)\n .sort(sort)\n .limit(query.first + 1)\n .toArray();\n\n const hasNextPage = documents.length > query.first;\n const items = documents.slice(0, query.first);\n const last = items.at(-1);\n\n const cursorFields = [\n ...orderBy.map((item) => toMongoField(String(item.field))),\n ];\n\n if (!cursorFields.includes(\"_id\")) cursorFields.push(\"_id\");\n\n const nextCursor =\n hasNextPage && last\n ? encodeCursor({\n values: cursorFields.map((field) =>\n field === \"_id\" ? last._id : last[field],\n ),\n })\n : null;\n\n return {\n items: items.map((item) => toRecord<T>(item) as T),\n hasNextPage,\n nextCursor,\n };\n }\n\n async insert<T extends StorageRecord>(\n collection: StorageCollectionName,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): Promise<T> {\n const document = toDocument(record as StorageRecord & { id: string });\n\n try {\n await this.collection(collection).insertOne(document);\n return toRecord<T>(document as WithId<MongoRecord>) as T;\n } catch (error) {\n return mapMongoError(error);\n }\n }\n\n async upsert<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n record: StorageInsert<T>,\n ): Promise<T> {\n validateQuery(query);\n\n const existing = await this.findOne<T>(collection, query);\n\n if (existing) {\n const updated = await this.update<T>(\n collection,\n existing.id,\n record as StorageUpdate<T>,\n );\n\n return updated ?? existing;\n }\n\n return this.insert<T>(collection, record);\n }\n\n async update<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n changes: StorageUpdate<T>,\n condition?: StorageCondition,\n ): Promise<T | null> {\n const target = this.collection(collection);\n\n const filter = {\n _id: id,\n ...buildCondition(condition),\n };\n\n const update: UpdateFilter<MongoRecord> = {\n $set: {\n ...cleanChanges(changes),\n updatedAt: new Date(),\n },\n $inc: { revision: 1 },\n };\n\n const result = await target.updateOne(filter, update);\n\n if (result.matchedCount === 0) {\n if (condition && (await target.findOne({ _id: id }))) {\n throw new StorageConflictError();\n }\n\n return null;\n }\n\n return toRecord<T>(await target.findOne({ _id: id }));\n }\n\n async updateMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n changes: StorageUpdate<T>,\n ): Promise<number> {\n validateQuery(query);\n\n const result = await this.collection(collection).updateMany(\n toMongoWhere(query.where),\n {\n $set: {\n ...cleanChanges(changes),\n updatedAt: new Date(),\n },\n $inc: { revision: 1 },\n },\n );\n\n return result.modifiedCount;\n }\n\n async delete<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n condition?: StorageCondition,\n ): Promise<boolean> {\n const target = this.collection(collection);\n\n const result = await target.deleteOne({\n _id: id,\n ...buildCondition(condition),\n });\n\n if (result.deletedCount > 0) return true;\n\n if (condition && (await target.findOne({ _id: id }))) {\n throw new StorageConflictError();\n }\n\n return false;\n }\n\n async deleteMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<number> {\n validateQuery(query);\n\n const result = await this.collection(collection).deleteMany(\n toMongoWhere(query.where),\n );\n\n return result.deletedCount;\n }\n\n async exists<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<boolean> {\n validateQuery(query);\n\n return (\n (await this.collection(collection).findOne(toMongoWhere(query.where), {\n projection: { _id: 1 },\n })) !== null\n );\n }\n\n async count<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T> = {},\n ): Promise<number> {\n validateQuery(query);\n\n return this.collection(collection).countDocuments(\n toMongoWhere(query.where),\n );\n }\n\n async atomicBatch<T extends StorageRecord>(\n operations: readonly StorageBatchOperation<T>[],\n ): Promise<StorageBatchResult<T>> {\n if (!this.capabilities.atomicBatch || !this.client) {\n throw new StorageAtomicityError(\n \"MongoDB atomicBatch requires an explicitly enabled transaction-capable deployment.\",\n );\n }\n\n const session = this.client.startSession();\n const results: StorageBatchResult<T>[\"results\"][number][] = [];\n\n try {\n await session.withTransaction(async () => {\n results.length = 0;\n\n for (const operation of operations) {\n const target = this.collection(operation.collection);\n\n if (operation.type === \"insert\") {\n const document = toDocument(\n operation.record as StorageRecord & { id: string },\n );\n\n await target.insertOne(document, { session });\n\n results.push({\n type: \"insert\",\n record: toRecord<T>(document as WithId<MongoRecord>) as T,\n });\n\n continue;\n }\n\n if (operation.type === \"update\") {\n const result = await target.updateOne(\n {\n _id: operation.id,\n ...buildCondition(operation.condition),\n },\n {\n $set: {\n ...cleanChanges(operation.changes),\n updatedAt: new Date(),\n },\n $inc: { revision: 1 },\n },\n { session },\n );\n\n if (result.matchedCount === 0 && operation.condition) {\n throw new StorageConflictError();\n }\n\n const updated = await target.findOne(\n { _id: operation.id },\n { session },\n );\n\n results.push({\n type: \"update\",\n record: toRecord<T>(updated),\n });\n\n continue;\n }\n\n const result = await target.deleteOne(\n {\n _id: operation.id,\n ...buildCondition(operation.condition),\n },\n { session },\n );\n\n if (\n result.deletedCount === 0 &&\n operation.condition &&\n (await target.findOne({ _id: operation.id }, { session }))\n ) {\n throw new StorageConflictError();\n }\n\n results.push({\n type: \"delete\",\n deleted: result.deletedCount > 0,\n });\n }\n });\n\n return { results };\n } catch (error) {\n if (\n error instanceof StorageConflictError ||\n error instanceof StorageAtomicityError\n ) {\n throw error;\n }\n\n if (isDuplicateError(error)) {\n throw new StorageDuplicateError(\"mongodb_unique_constraint\");\n }\n\n throw error;\n } finally {\n await session.endSession();\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { MongoStorageAdapterOptions } from "./mongodb.types.cjs";
2
+ import { StorageAdapter, StorageBatchOperation, StorageBatchResult, StorageCapabilities, StorageCapabilityReport, StorageCollection, StorageCollectionName, StorageCondition, StorageCursorQuery, StorageInsert, StoragePage, StorageQuery, StorageRecord, StorageUpdate } from "@puzzle-ui/storage";
3
+ export declare class MongoStorageAdapter implements StorageAdapter {
4
+ readonly capabilities: StorageCapabilities;
5
+ private readonly database;
6
+ private readonly client;
7
+ private readonly prefix;
8
+ constructor(options: MongoStorageAdapterOptions);
9
+ getCapabilities(): StorageCapabilityReport;
10
+ private collection;
11
+ registerCollection<T extends StorageRecord>(definition: StorageCollection<T>): Promise<void>;
12
+ findById<T extends StorageRecord>(collection: StorageCollectionName, id: string): Promise<T | null>;
13
+ findOne<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<T | null>;
14
+ findMany<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<readonly T[]>;
15
+ findPage<T extends StorageRecord>(collection: StorageCollectionName, query: StorageCursorQuery<T>): Promise<StoragePage<T>>;
16
+ insert<T extends StorageRecord>(collection: StorageCollectionName, record: Parameters<StorageAdapter["insert"]>[1]): Promise<T>;
17
+ upsert<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, record: StorageInsert<T>): Promise<T>;
18
+ update<T extends StorageRecord>(collection: StorageCollectionName, id: string, changes: StorageUpdate<T>, condition?: StorageCondition): Promise<T | null>;
19
+ updateMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, changes: StorageUpdate<T>): Promise<number>;
20
+ delete<T extends StorageRecord>(collection: StorageCollectionName, id: string, condition?: StorageCondition): Promise<boolean>;
21
+ deleteMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<number>;
22
+ exists<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<boolean>;
23
+ count<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<number>;
24
+ atomicBatch<T extends StorageRecord>(operations: readonly StorageBatchOperation<T>[]): Promise<StorageBatchResult<T>>;
25
+ }
@@ -0,0 +1,25 @@
1
+ import { MongoStorageAdapterOptions } from "./mongodb.types.js";
2
+ import { StorageAdapter, StorageBatchOperation, StorageBatchResult, StorageCapabilities, StorageCapabilityReport, StorageCollection, StorageCollectionName, StorageCondition, StorageCursorQuery, StorageInsert, StoragePage, StorageQuery, StorageRecord, StorageUpdate } from "@puzzle-ui/storage";
3
+ export declare class MongoStorageAdapter implements StorageAdapter {
4
+ readonly capabilities: StorageCapabilities;
5
+ private readonly database;
6
+ private readonly client;
7
+ private readonly prefix;
8
+ constructor(options: MongoStorageAdapterOptions);
9
+ getCapabilities(): StorageCapabilityReport;
10
+ private collection;
11
+ registerCollection<T extends StorageRecord>(definition: StorageCollection<T>): Promise<void>;
12
+ findById<T extends StorageRecord>(collection: StorageCollectionName, id: string): Promise<T | null>;
13
+ findOne<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<T | null>;
14
+ findMany<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<readonly T[]>;
15
+ findPage<T extends StorageRecord>(collection: StorageCollectionName, query: StorageCursorQuery<T>): Promise<StoragePage<T>>;
16
+ insert<T extends StorageRecord>(collection: StorageCollectionName, record: Parameters<StorageAdapter["insert"]>[1]): Promise<T>;
17
+ upsert<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, record: StorageInsert<T>): Promise<T>;
18
+ update<T extends StorageRecord>(collection: StorageCollectionName, id: string, changes: StorageUpdate<T>, condition?: StorageCondition): Promise<T | null>;
19
+ updateMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>, changes: StorageUpdate<T>): Promise<number>;
20
+ delete<T extends StorageRecord>(collection: StorageCollectionName, id: string, condition?: StorageCondition): Promise<boolean>;
21
+ deleteMany<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<number>;
22
+ exists<T extends StorageRecord>(collection: StorageCollectionName, query: StorageQuery<T>): Promise<boolean>;
23
+ count<T extends StorageRecord>(collection: StorageCollectionName, query?: StorageQuery<T>): Promise<number>;
24
+ atomicBatch<T extends StorageRecord>(operations: readonly StorageBatchOperation<T>[]): Promise<StorageBatchResult<T>>;
25
+ }