@puzzle-ui/storage-mongodb 0.0.0-stage → 0.5.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.
@@ -0,0 +1,159 @@
1
+ import "@puzzle-ui/storage";
2
+ import { randomUUID } from "node:crypto";
3
+ // src/mongodb.migrations.ts
4
+ const sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration));
5
+ const isDuplicate = (error) => typeof error === "object" && error !== null && "code" in error && error.code === 11e3;
6
+ var MongoStorageMigrationAdapter = class {
7
+ database;
8
+ prefix;
9
+ lockDurationMs;
10
+ constructor(options) {
11
+ this.database = options.database;
12
+ this.prefix = options.collectionPrefix ?? "puzzle_";
13
+ this.lockDurationMs = options.lockDurationMs ?? 6e5;
14
+ }
15
+ get migrations() {
16
+ return this.database.collection(`${this.prefix}migrations`);
17
+ }
18
+ get locks() {
19
+ return this.database.collection(`${this.prefix}migration_locks`);
20
+ }
21
+ async ensureCollections() {
22
+ for (const name of [`${this.prefix}migrations`, `${this.prefix}migration_locks`]) try {
23
+ await this.database.createCollection(name);
24
+ } catch (error) {
25
+ if (!(typeof error === "object" && error !== null && "code" in error && error.code === 48)) throw error;
26
+ }
27
+ await this.locks.createIndexes([{
28
+ key: { expiresAt: 1 },
29
+ name: "expiresAt_ttl",
30
+ expireAfterSeconds: 0
31
+ }]);
32
+ }
33
+ async getMigrationStatus() {
34
+ await this.ensureCollections();
35
+ const migrations = (await this.migrations.find({ _id: { $ne: "__lock__" } }).sort({ version: 1 }).toArray()).map((record) => ({
36
+ id: record._id,
37
+ version: record.version,
38
+ description: record.description,
39
+ status: record.status,
40
+ ...record.startedAt ? { startedAt: record.startedAt } : {},
41
+ ...record.completedAt ? { completedAt: record.completedAt } : {},
42
+ ...record.error ? { error: record.error } : {}
43
+ }));
44
+ return {
45
+ currentVersion: migrations.reduce((version, migration) => migration.status === "completed" ? Math.max(version, migration.version) : version, 0),
46
+ migrations
47
+ };
48
+ }
49
+ async acquireLock(owner) {
50
+ const deadline = Date.now() + this.lockDurationMs;
51
+ while (Date.now() < deadline) {
52
+ const now = /* @__PURE__ */ new Date();
53
+ const expiresAt = new Date(now.getTime() + this.lockDurationMs);
54
+ try {
55
+ await this.locks.updateOne({
56
+ _id: "global",
57
+ $or: [{ expiresAt: { $lt: now } }, { owner }]
58
+ }, { $set: {
59
+ owner,
60
+ expiresAt
61
+ } }, { upsert: true });
62
+ if ((await this.locks.findOne({ _id: "global" }))?.owner === owner) return;
63
+ } catch (error) {
64
+ if (!isDuplicate(error)) throw error;
65
+ }
66
+ await sleep(50);
67
+ }
68
+ throw new Error("Timed out while acquiring the migration lock.");
69
+ }
70
+ async execute(operation) {
71
+ switch (operation.type) {
72
+ case "createCollection":
73
+ try {
74
+ await this.database.createCollection(`${this.prefix}${operation.collection}`);
75
+ } catch (error) {
76
+ if (!(typeof error === "object" && error !== null && "code" in error && error.code === 48)) throw error;
77
+ }
78
+ return;
79
+ case "dropCollection":
80
+ await this.database.dropCollection(`${this.prefix}${operation.collection}`).catch(() => false);
81
+ return;
82
+ case "createIndex":
83
+ await this.database.collection(`${this.prefix}${operation.collection}`).createIndexes([{
84
+ key: Object.fromEntries(operation.fields.map((field) => [field, 1])),
85
+ name: operation.name,
86
+ unique: operation.unique ?? false,
87
+ ...operation.expireAfterSeconds !== void 0 ? { expireAfterSeconds: operation.expireAfterSeconds } : {}
88
+ }]);
89
+ return;
90
+ case "dropIndex":
91
+ await this.database.collection(`${this.prefix}${operation.collection}`).dropIndex(operation.name);
92
+ return;
93
+ }
94
+ }
95
+ async runMigrations(migrations, options = {}) {
96
+ await this.ensureCollections();
97
+ const owner = randomUUID();
98
+ await this.acquireLock(owner);
99
+ const applied = [];
100
+ const skipped = [];
101
+ try {
102
+ const sorted = [...migrations].sort((a, b) => a.version - b.version);
103
+ for (const migration of sorted) {
104
+ if (options.targetVersion !== void 0 && migration.version > options.targetVersion) {
105
+ skipped.push(migration.version);
106
+ continue;
107
+ }
108
+ if ((await this.migrations.findOne({ _id: migration.id }))?.status === "completed") {
109
+ skipped.push(migration.version);
110
+ continue;
111
+ }
112
+ if (options.dryRun) {
113
+ applied.push(migration.version);
114
+ continue;
115
+ }
116
+ const startedAt = /* @__PURE__ */ new Date();
117
+ await this.migrations.updateOne({ _id: migration.id }, {
118
+ $set: {
119
+ version: migration.version,
120
+ description: migration.description,
121
+ status: "running",
122
+ startedAt
123
+ },
124
+ $unset: {
125
+ completedAt: "",
126
+ error: ""
127
+ }
128
+ }, { upsert: true });
129
+ try {
130
+ await migration.up({ execute: (operation) => this.execute(operation) });
131
+ await this.migrations.updateOne({ _id: migration.id }, { $set: {
132
+ status: "completed",
133
+ completedAt: /* @__PURE__ */ new Date()
134
+ } });
135
+ applied.push(migration.version);
136
+ } catch (error) {
137
+ await this.migrations.updateOne({ _id: migration.id }, { $set: {
138
+ status: "failed",
139
+ error: error instanceof Error ? error.message : String(error)
140
+ } });
141
+ throw error;
142
+ }
143
+ }
144
+ } finally {
145
+ await this.locks.deleteOne({
146
+ _id: "global",
147
+ owner
148
+ });
149
+ }
150
+ return {
151
+ applied,
152
+ skipped,
153
+ currentVersion: (await this.getMigrationStatus()).currentVersion
154
+ };
155
+ }
156
+ };
157
+ export { MongoStorageMigrationAdapter };
158
+
159
+ //# sourceMappingURL=mongodb.migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongodb.migrations.js","names":[],"sources":["../src/mongodb.migrations.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Db, Document } from \"mongodb\";\n\nimport {\n type StorageMigration,\n type StorageMigrationAdapter,\n type StorageMigrationOperation,\n type StorageMigrationOptions,\n type StorageMigrationRecord,\n type StorageMigrationResult,\n} from \"@puzzle-ui/storage\";\n\nimport type { MongoStorageMigrationOptions } from \"./mongodb.types\";\n\ntype MigrationDocument = Document & {\n _id: string;\n version: number;\n description: string;\n status: \"running\" | \"completed\" | \"failed\";\n startedAt?: Date;\n completedAt?: Date;\n error?: string;\n};\n\ntype MigrationLockDocument = Document & {\n _id: string;\n owner: string;\n expiresAt: Date;\n};\n\nconst sleep = (duration: number) =>\n new Promise((resolve) => setTimeout(resolve, duration));\n\nconst isDuplicate = (error: unknown) =>\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n (error as { code: number }).code === 11000;\n\nexport class MongoStorageMigrationAdapter implements StorageMigrationAdapter {\n private readonly database: Db;\n private readonly prefix: string;\n private readonly lockDurationMs: number;\n\n constructor(options: MongoStorageMigrationOptions) {\n this.database = options.database;\n this.prefix = options.collectionPrefix ?? \"puzzle_\";\n this.lockDurationMs = options.lockDurationMs ?? 600_000;\n }\n\n private get migrations() {\n return this.database.collection<MigrationDocument>(\n `${this.prefix}migrations`,\n );\n }\n\n private get locks() {\n return this.database.collection<MigrationLockDocument>(\n `${this.prefix}migration_locks`,\n );\n }\n\n private async ensureCollections() {\n for (const name of [\n `${this.prefix}migrations`,\n `${this.prefix}migration_locks`,\n ]) {\n try {\n await this.database.createCollection(name);\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\n await this.locks.createIndexes([\n {\n key: { expiresAt: 1 },\n name: \"expiresAt_ttl\",\n expireAfterSeconds: 0,\n },\n ]);\n }\n\n async getMigrationStatus(): Promise<{\n currentVersion: number;\n migrations: readonly StorageMigrationRecord[];\n }> {\n await this.ensureCollections();\n\n const records = await this.migrations\n .find({ _id: { $ne: \"__lock__\" } })\n .sort({ version: 1 })\n .toArray();\n\n const migrations = records.map((record) => ({\n id: record._id,\n version: record.version,\n description: record.description,\n status: record.status,\n ...(record.startedAt ? { startedAt: record.startedAt } : {}),\n ...(record.completedAt ? { completedAt: record.completedAt } : {}),\n ...(record.error ? { error: record.error } : {}),\n })) satisfies StorageMigrationRecord[];\n\n return {\n currentVersion: migrations.reduce(\n (version, migration) =>\n migration.status === \"completed\"\n ? Math.max(version, migration.version)\n : version,\n 0,\n ),\n migrations,\n };\n }\n\n private async acquireLock(owner: string): Promise<void> {\n const deadline = Date.now() + this.lockDurationMs;\n\n while (Date.now() < deadline) {\n const now = new Date();\n const expiresAt = new Date(now.getTime() + this.lockDurationMs);\n\n try {\n await this.locks.updateOne(\n {\n _id: \"global\",\n $or: [{ expiresAt: { $lt: now } }, { owner }],\n },\n {\n $set: { owner, expiresAt },\n },\n { upsert: true },\n );\n\n const lock = await this.locks.findOne({ _id: \"global\" });\n\n if (lock?.owner === owner) return;\n } catch (error) {\n if (!isDuplicate(error)) throw error;\n }\n\n await sleep(50);\n }\n\n throw new Error(\"Timed out while acquiring the migration lock.\");\n }\n\n private async execute(operation: StorageMigrationOperation): Promise<void> {\n switch (operation.type) {\n case \"createCollection\": {\n try {\n await this.database.createCollection(\n `${this.prefix}${operation.collection}`,\n );\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 return;\n }\n\n case \"dropCollection\":\n await this.database\n .dropCollection(`${this.prefix}${operation.collection}`)\n .catch(() => false);\n return;\n\n case \"createIndex\": {\n const collection = this.database.collection(\n `${this.prefix}${operation.collection}`,\n );\n\n await collection.createIndexes([\n {\n key: Object.fromEntries(\n operation.fields.map((field) => [field, 1]),\n ),\n name: operation.name,\n unique: operation.unique ?? false,\n ...(operation.expireAfterSeconds !== undefined\n ? { expireAfterSeconds: operation.expireAfterSeconds }\n : {}),\n },\n ]);\n\n return;\n }\n\n case \"dropIndex\":\n await this.database\n .collection(`${this.prefix}${operation.collection}`)\n .dropIndex(operation.name);\n return;\n }\n }\n\n async runMigrations(\n migrations: readonly StorageMigration[],\n options: StorageMigrationOptions = {},\n ): Promise<StorageMigrationResult> {\n await this.ensureCollections();\n\n const owner = randomUUID();\n await this.acquireLock(owner);\n\n const applied: number[] = [];\n const skipped: number[] = [];\n\n try {\n const sorted = [...migrations].sort((a, b) => a.version - b.version);\n\n for (const migration of sorted) {\n if (\n options.targetVersion !== undefined &&\n migration.version > options.targetVersion\n ) {\n skipped.push(migration.version);\n continue;\n }\n\n const existing = await this.migrations.findOne({\n _id: migration.id,\n });\n\n if (existing?.status === \"completed\") {\n skipped.push(migration.version);\n continue;\n }\n\n if (options.dryRun) {\n applied.push(migration.version);\n continue;\n }\n\n const startedAt = new Date();\n\n await this.migrations.updateOne(\n { _id: migration.id },\n {\n $set: {\n version: migration.version,\n description: migration.description,\n status: \"running\",\n startedAt,\n },\n $unset: {\n completedAt: \"\",\n error: \"\",\n },\n },\n { upsert: true },\n );\n\n try {\n await migration.up({\n execute: (operation) => this.execute(operation),\n });\n\n await this.migrations.updateOne(\n { _id: migration.id },\n {\n $set: {\n status: \"completed\",\n completedAt: new Date(),\n },\n },\n );\n\n applied.push(migration.version);\n } catch (error) {\n await this.migrations.updateOne(\n { _id: migration.id },\n {\n $set: {\n status: \"failed\",\n error: error instanceof Error ? error.message : String(error),\n },\n },\n );\n\n throw error;\n }\n }\n } finally {\n await this.locks.deleteOne({\n _id: \"global\",\n owner,\n });\n }\n\n const status = await this.getMigrationStatus();\n\n return {\n applied,\n skipped,\n currentVersion: status.currentVersion,\n };\n }\n}\n"],"mappings":";;;AA8BA,MAAM,SAAS,aACb,IAAI,SAAS,YAAY,WAAW,SAAS,QAAQ,CAAC;AAExD,MAAM,eAAe,UACnB,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAA2B,SAAS;AAEvC,IAAa,+BAAb,MAA6E;CAC3E;CACA;CACA;CAEA,YAAY,SAAuC;EACjD,KAAK,WAAW,QAAQ;EACxB,KAAK,SAAS,QAAQ,oBAAoB;EAC1C,KAAK,iBAAiB,QAAQ,kBAAkB;CAClD;CAEA,IAAY,aAAa;EACvB,OAAO,KAAK,SAAS,WACnB,GAAG,KAAK,OAAO,WACjB;CACF;CAEA,IAAY,QAAQ;EAClB,OAAO,KAAK,SAAS,WACnB,GAAG,KAAK,OAAO,gBACjB;CACF;CAEA,MAAc,oBAAoB;EAChC,KAAK,MAAM,QAAQ,CACjB,GAAG,KAAK,OAAO,aACf,GAAG,KAAK,OAAO,gBACjB,GACE,IAAI;GACF,MAAM,KAAK,SAAS,iBAAiB,IAAI;EAC3C,SAAS,OAAO;GACd,IACE,EACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAA2B,SAAS,KAGvC,MAAM;EAEV;EAGF,MAAM,KAAK,MAAM,cAAc,CAC7B;GACE,KAAK,EAAE,WAAW,EAAE;GACpB,MAAM;GACN,oBAAoB;EACtB,CACF,CAAC;CACH;CAEA,MAAM,qBAGH;EACD,MAAM,KAAK,kBAAkB;EAO7B,MAAM,cAAa,MALG,KAAK,WACxB,KAAK,EAAE,KAAK,EAAE,KAAK,WAAW,EAAE,CAAC,CAAC,CAClC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CACpB,QAAQ,EAAA,CAEgB,KAAK,YAAY;GAC1C,IAAI,OAAO;GACX,SAAS,OAAO;GAChB,aAAa,OAAO;GACpB,QAAQ,OAAO;GACf,GAAI,OAAO,YAAY,EAAE,WAAW,OAAO,UAAU,IAAI,CAAC;GAC1D,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;GAChE,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EAChD,EAAE;EAEF,OAAO;GACL,gBAAgB,WAAW,QACxB,SAAS,cACR,UAAU,WAAW,cACjB,KAAK,IAAI,SAAS,UAAU,OAAO,IACnC,SACN,CACF;GACA;EACF;CACF;CAEA,MAAc,YAAY,OAA8B;EACtD,MAAM,WAAW,KAAK,IAAI,IAAI,KAAK;EAEnC,OAAO,KAAK,IAAI,IAAI,UAAU;GAC5B,MAAM,sBAAM,IAAI,KAAK;GACrB,MAAM,YAAY,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,cAAc;GAE9D,IAAI;IACF,MAAM,KAAK,MAAM,UACf;KACE,KAAK;KACL,KAAK,CAAC,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC;IAC9C,GACA,EACE,MAAM;KAAE;KAAO;IAAU,EAC3B,GACA,EAAE,QAAQ,KAAK,CACjB;IAIA,KAAI,MAFe,KAAK,MAAM,QAAQ,EAAE,KAAK,SAAS,CAAC,EAAA,EAE7C,UAAU,OAAO;GAC7B,SAAS,OAAO;IACd,IAAI,CAAC,YAAY,KAAK,GAAG,MAAM;GACjC;GAEA,MAAM,MAAM,EAAE;EAChB;EAEA,MAAM,IAAI,MAAM,+CAA+C;CACjE;CAEA,MAAc,QAAQ,WAAqD;EACzE,QAAQ,UAAU,MAAlB;GACE,KAAK;IACH,IAAI;KACF,MAAM,KAAK,SAAS,iBAClB,GAAG,KAAK,SAAS,UAAU,YAC7B;IACF,SAAS,OAAO;KACd,IACE,EACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACT,MAA2B,SAAS,KAGvC,MAAM;IAEV;IAEA;GAGF,KAAK;IACH,MAAM,KAAK,SACR,eAAe,GAAG,KAAK,SAAS,UAAU,YAAY,CAAC,CACvD,YAAY,KAAK;IACpB;GAEF,KAAK;IAKH,MAJmB,KAAK,SAAS,WAC/B,GAAG,KAAK,SAAS,UAAU,YAGd,CAAC,CAAC,cAAc,CAC7B;KACE,KAAK,OAAO,YACV,UAAU,OAAO,KAAK,UAAU,CAAC,OAAO,CAAC,CAAC,CAC5C;KACA,MAAM,UAAU;KAChB,QAAQ,UAAU,UAAU;KAC5B,GAAI,UAAU,uBAAuB,KAAA,IACjC,EAAE,oBAAoB,UAAU,mBAAmB,IACnD,CAAC;IACP,CACF,CAAC;IAED;GAGF,KAAK;IACH,MAAM,KAAK,SACR,WAAW,GAAG,KAAK,SAAS,UAAU,YAAY,CAAC,CACnD,UAAU,UAAU,IAAI;IAC3B;EACJ;CACF;CAEA,MAAM,cACJ,YACA,UAAmC,CAAC,GACH;EACjC,MAAM,KAAK,kBAAkB;EAE7B,MAAM,QAAQ,WAAW;EACzB,MAAM,KAAK,YAAY,KAAK;EAE5B,MAAM,UAAoB,CAAC;EAC3B,MAAM,UAAoB,CAAC;EAE3B,IAAI;GACF,MAAM,SAAS,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO;GAEnE,KAAK,MAAM,aAAa,QAAQ;IAC9B,IACE,QAAQ,kBAAkB,KAAA,KAC1B,UAAU,UAAU,QAAQ,eAC5B;KACA,QAAQ,KAAK,UAAU,OAAO;KAC9B;IACF;IAMA,KAAI,MAJmB,KAAK,WAAW,QAAQ,EAC7C,KAAK,UAAU,GACjB,CAAC,EAAA,EAEa,WAAW,aAAa;KACpC,QAAQ,KAAK,UAAU,OAAO;KAC9B;IACF;IAEA,IAAI,QAAQ,QAAQ;KAClB,QAAQ,KAAK,UAAU,OAAO;KAC9B;IACF;IAEA,MAAM,4BAAY,IAAI,KAAK;IAE3B,MAAM,KAAK,WAAW,UACpB,EAAE,KAAK,UAAU,GAAG,GACpB;KACE,MAAM;MACJ,SAAS,UAAU;MACnB,aAAa,UAAU;MACvB,QAAQ;MACR;KACF;KACA,QAAQ;MACN,aAAa;MACb,OAAO;KACT;IACF,GACA,EAAE,QAAQ,KAAK,CACjB;IAEA,IAAI;KACF,MAAM,UAAU,GAAG,EACjB,UAAU,cAAc,KAAK,QAAQ,SAAS,EAChD,CAAC;KAED,MAAM,KAAK,WAAW,UACpB,EAAE,KAAK,UAAU,GAAG,GACpB,EACE,MAAM;MACJ,QAAQ;MACR,6BAAa,IAAI,KAAK;KACxB,EACF,CACF;KAEA,QAAQ,KAAK,UAAU,OAAO;IAChC,SAAS,OAAO;KACd,MAAM,KAAK,WAAW,UACpB,EAAE,KAAK,UAAU,GAAG,GACpB,EACE,MAAM;MACJ,QAAQ;MACR,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KAC9D,EACF,CACF;KAEA,MAAM;IACR;GACF;EACF,UAAU;GACR,MAAM,KAAK,MAAM,UAAU;IACzB,KAAK;IACL;GACF,CAAC;EACH;EAIA,OAAO;GACL;GACA;GACA,iBAAgB,MALG,KAAK,mBAAmB,EAAA,CAKpB;EACzB;CACF;AACF"}
@@ -0,0 +1,12 @@
1
+ import { Db, MongoClient } from "mongodb";
2
+ export type MongoStorageAdapterOptions = {
3
+ readonly database: Db;
4
+ readonly client?: MongoClient;
5
+ readonly collectionPrefix?: string;
6
+ readonly transactionsEnabled?: boolean;
7
+ };
8
+ export type MongoStorageMigrationOptions = {
9
+ readonly database: Db;
10
+ readonly collectionPrefix?: string;
11
+ readonly lockDurationMs?: number;
12
+ };
@@ -0,0 +1,12 @@
1
+ import { Db, MongoClient } from "mongodb";
2
+ export type MongoStorageAdapterOptions = {
3
+ readonly database: Db;
4
+ readonly client?: MongoClient;
5
+ readonly collectionPrefix?: string;
6
+ readonly transactionsEnabled?: boolean;
7
+ };
8
+ export type MongoStorageMigrationOptions = {
9
+ readonly database: Db;
10
+ readonly collectionPrefix?: string;
11
+ readonly lockDurationMs?: number;
12
+ };
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@puzzle-ui/storage-mongodb",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.5.0",
4
+ "description": "MongoDB storage adapter foundation for Puzzle Auth.",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public",
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "import": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "require": {
21
+ "types": "./dist/index.d.cts",
22
+ "default": "./dist/index.cjs"
23
+ }
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "sideEffects": false,
30
+ "scripts": {
31
+ "build": "tsdown",
32
+ "typecheck": "tsc --noEmit -p .",
33
+ "test": "bash scripts/test-with-mongodb.sh",
34
+ "vitest": "bash scripts/test-with-mongodb.sh",
35
+ "clean": "rm -rf dist",
36
+ "prepack": "tsdown"
37
+ },
38
+ "devDependencies": {
39
+ "@types/bun": "^1.4.4",
40
+ "@types/node": "^26.6.6",
41
+ "bun": "^1.4.4",
42
+ "tsdown": "^0.23.1",
43
+ "typescript": "^7.0.2"
44
+ },
45
+ "dependencies": {
46
+ "@puzzle-ui/storage": "^0.5.0",
47
+ "mongodb": "^7.8.0"
48
+ }
49
+ }