awaitly-mongo 9.0.0 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var y=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var L=Object.prototype.hasOwnProperty;var P=(t,o)=>{for(var e in o)y(t,e,{get:o[e],enumerable:!0})},S=(t,o,e,l)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of C(o))!L.call(t,r)&&r!==e&&y(t,r,{get:()=>o[r],enumerable:!(l=h(o,r))||l.enumerable});return t};var D=t=>S(y({},"__esModule",{value:!0}),t);var v={};P(v,{mongo:()=>O});module.exports=D(v);var x=require("mongodb");var A=require("crypto");function M(t,o={}){let e=o.lockCollectionName??"workflow_lock",l=t.collection(e);async function r(){(await t.listCollections({name:e}).toArray()).length===0&&await t.createCollection(e)}async function p(c,s){let w=s?.ttlMs??6e4,a=(0,A.randomUUID)(),d=new Date(Date.now()+w);await r();try{let i=await l.findOneAndUpdate({_id:c,$or:[{expiresAt:{$lt:new Date}},{expiresAt:{$exists:!1}}]},{$set:{ownerToken:a,expiresAt:d}},{upsert:!0,returnDocument:"after"});return i&&i.ownerToken===a?{ownerToken:a}:null}catch(i){if(i&&typeof i=="object"&&"code"in i&&i.code===11e3)return null;throw i}}async function k(c,s){await l.deleteOne({_id:c,ownerToken:s})}return{tryAcquire:p,release:k,ensureLockCollection:r}}function O(t){let o=typeof t=="string"?{url:t}:t,e=o.prefix??"",l=o.database,r=o.url.match(/mongodb(?:\+srv)?:\/\/[^/]+\/([^?]+)/);!l&&r&&r[1]&&(l=r[1]),l=l??"awaitly";let p=o.collection??"awaitly_snapshots",k=!o.client,c=o.client,s,w=!1,a=null,d=async()=>(s&&w||(c||(c=new x.MongoClient(o.url,{directConnection:!o.url.includes("mongodb+srv://"),...o.clientOptions})),await c.connect(),w=!0,s=c.db(l),await s.collection(p).createIndex({updatedAt:-1},{background:!0}).catch(()=>{}),o.lock&&!a&&(a=M(s,o.lock))),s),i={async save(n,u){let f=(await d()).collection(p),g=e+n;await f.updateOne({_id:g},{$set:{snapshot:u,updatedAt:new Date}},{upsert:!0})},async load(n){let m=(await d()).collection(p),f=e+n,g=await m.findOne({_id:f});return g?g.snapshot:null},async delete(n){let m=(await d()).collection(p),f=e+n;await m.deleteOne({_id:f})},async list(n){let m=(await d()).collection(p),f=e+(n?.prefix??""),g=n?.limit??100;return(await m.find({_id:{$regex:`^${f.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}`}}).sort({updatedAt:-1}).limit(g).toArray()).map(b=>({id:String(b._id).slice(e.length),updatedAt:b.updatedAt.toISOString()}))},async close(){k&&c&&(await c.close(),w=!1,s=void 0)},async tryAcquire(n,u){return await d(),a?a.tryAcquire(n,u):null},async release(n,u){if(await d(),!!a)return a.release(n,u)}};return o.lock||(delete i.tryAcquire,delete i.release),i}0&&(module.exports={mongo});
1
+ "use strict";var y=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var L=(t,o)=>{for(var e in o)y(t,e,{get:o[e],enumerable:!0})},P=(t,o,e,c)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of x(o))!C.call(t,r)&&r!==e&&y(t,r,{get:()=>o[r],enumerable:!(c=M(o,r))||c.enumerable});return t};var O=t=>P(y({},"__esModule",{value:!0}),t);var v={};L(v,{mongo:()=>_});module.exports=O(v);var S=require("mongodb");var h=require("crypto");function A(t,o={}){let e=o.lockCollectionName??"workflow_lock",c=t.collection(e);async function r(){(await t.listCollections({name:e}).toArray()).length===0&&await t.createCollection(e)}async function u(s,l){let w=l?.ttlMs??6e4,a=(0,h.randomUUID)(),d=new Date(Date.now()+w);await r();try{let i=await c.findOneAndUpdate({_id:s,$or:[{expiresAt:{$lt:new Date}},{expiresAt:{$exists:!1}}]},{$set:{ownerToken:a,expiresAt:d}},{upsert:!0,returnDocument:"after"});return i&&i.ownerToken===a?{ownerToken:a}:null}catch(i){if(i&&typeof i=="object"&&"code"in i&&i.code===11e3)return null;throw i}}async function k(s,l){await c.deleteOne({_id:s,ownerToken:l})}return{tryAcquire:u,release:k,ensureLockCollection:r}}function _(t){let o=typeof t=="string"?{url:t}:t,e=o.prefix??"",c=o.database,r=o.url.match(/mongodb(?:\+srv)?:\/\/[^/]+\/([^?]+)/);!c&&r&&r[1]&&(c=r[1]),c=c??"awaitly";let u=o.collection??"awaitly_snapshots",k=!o.client,s=o.client,l,w=!1,a=null,d=async()=>(l&&w||(s||(s=new S.MongoClient(o.url,{directConnection:!o.url.includes("mongodb+srv://"),...o.clientOptions})),await s.connect(),w=!0,l=s.db(c),await l.collection(u).createIndex({updatedAt:-1},{background:!0}).catch(()=>{}),o.lock&&!a&&(a=A(l,o.lock))),l),i={async save(n,p){let f=(await d()).collection(u),g=e+n;await f.updateOne({_id:g},{$set:{snapshot:p,updatedAt:new Date}},{upsert:!0})},async load(n){let m=(await d()).collection(u),f=e+n,g=await m.findOne({_id:f});return g?g.snapshot:null},async delete(n){let m=(await d()).collection(u),f=e+n;await m.deleteOne({_id:f})},async list(n){let m=(await d()).collection(u),f=e+(n?.prefix??""),g=n?.limit??100,D=f.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return(await m.find({_id:{$regex:`^${D}`}}).sort({updatedAt:-1}).limit(g).toArray()).map(b=>({id:String(b._id).slice(e.length),updatedAt:b.updatedAt.toISOString()}))},async close(){k&&s&&(await s.close(),w=!1,l=void 0)},async tryAcquire(n,p){return await d(),a?a.tryAcquire(n,p):null},async release(n,p){if(await d(),!!a)return a.release(n,p)}};return o.lock||(delete i.tryAcquire,delete i.release),i}0&&(module.exports={mongo});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/mongo-lock.ts"],"sourcesContent":["/**\n * awaitly-mongo\n *\n * MongoDB persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by MongoDB.\n */\n\nimport type { Db, MongoClientOptions } from \"mongodb\";\nimport { MongoClient as MongoClientImpl } from \"mongodb\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\nimport { createMongoLock, type MongoLockOptions } from \"./mongo-lock\";\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/durable\";\nexport type { MongoLockOptions } from \"./mongo-lock\";\n\n// =============================================================================\n// MongoOptions\n// =============================================================================\n\n/**\n * Options for the mongo() shorthand function.\n */\nexport interface MongoOptions {\n /** MongoDB connection URL. */\n url: string;\n /** Database name. @default 'awaitly' */\n database?: string;\n /** Collection name for snapshots. @default 'awaitly_snapshots' */\n collection?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own client. */\n client?: MongoClientImpl;\n /** MongoDB client options. */\n clientOptions?: MongoClientOptions;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: MongoLockOptions;\n}\n\n// =============================================================================\n// mongo() - One-liner Snapshot Store Setup\n// =============================================================================\n\n/**\n * Create a snapshot store backed by MongoDB.\n * This is the simplified one-liner API for workflow persistence.\n *\n * @example\n * ```typescript\n * import { mongo } from 'awaitly-mongo';\n *\n * // One-liner setup\n * const store = mongo('mongodb://localhost:27017/mydb');\n *\n * // Execute + persist\n * const wf = createWorkflow(deps);\n * await wf(myWorkflowFn);\n * await store.save('wf-123', wf.getSnapshot());\n *\n * // Restore\n * const snapshot = await store.load('wf-123');\n * const wf2 = createWorkflow(deps, { snapshot });\n * await wf2(myWorkflowFn);\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = mongo({\n * url: 'mongodb://localhost:27017',\n * database: 'myapp',\n * collection: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockCollectionName: 'my_workflow_locks' },\n * });\n * ```\n */\nexport function mongo(urlOrOptions: string | MongoOptions): SnapshotStore & Partial<WorkflowLock> {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const prefix = opts.prefix ?? \"\";\n\n // Parse database from URL if provided\n let databaseName = opts.database;\n const urlMatch = opts.url.match(/mongodb(?:\\+srv)?:\\/\\/[^/]+\\/([^?]+)/);\n if (!databaseName && urlMatch && urlMatch[1]) {\n databaseName = urlMatch[1];\n }\n databaseName = databaseName ?? \"awaitly\";\n\n const collectionName = opts.collection ?? \"awaitly_snapshots\";\n\n // Create or use existing client\n const ownClient = !opts.client;\n let client: MongoClientImpl | undefined = opts.client;\n let db: Db | undefined;\n let connected = false;\n let lock: { tryAcquire: WorkflowLock[\"tryAcquire\"]; release: WorkflowLock[\"release\"] } | null = null;\n\n const ensureConnected = async (): Promise<Db> => {\n if (db && connected) return db;\n\n if (!client) {\n client = new MongoClientImpl(opts.url, {\n directConnection: !opts.url.includes(\"mongodb+srv://\"),\n ...opts.clientOptions,\n });\n }\n\n await client.connect();\n connected = true;\n db = client.db(databaseName);\n\n // Create index on updatedAt for list queries\n const collection = db.collection(collectionName);\n await collection.createIndex({ updatedAt: -1 }, { background: true }).catch(() => {\n // Index may already exist, ignore error\n });\n\n // Create lock if requested\n if (opts.lock && !lock) {\n lock = createMongoLock(db, opts.lock);\n }\n\n return db;\n };\n\n const store: SnapshotStore & Partial<WorkflowLock> = {\n async save(id: string, snapshot: WorkflowSnapshot): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const fullId = prefix + id;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await collection.updateOne(\n { _id: fullId } as any,\n {\n $set: {\n snapshot,\n updatedAt: new Date(),\n },\n },\n { upsert: true }\n );\n },\n\n async load(id: string): Promise<WorkflowSnapshot | null> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const fullId = prefix + id;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const doc = await collection.findOne({ _id: fullId } as any);\n if (!doc) return null;\n return doc.snapshot as WorkflowSnapshot;\n },\n\n async delete(id: string): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const fullId = prefix + id;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await collection.deleteOne({ _id: fullId } as any);\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cursor = collection\n .find({ _id: { $regex: `^${filterPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}` } } as any)\n .sort({ updatedAt: -1 })\n .limit(limit);\n\n const docs = await cursor.toArray();\n return docs.map(doc => ({\n id: String(doc._id).slice(prefix.length),\n updatedAt: (doc.updatedAt as Date).toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only close client if we created it\n if (ownClient && client) {\n await client.close();\n connected = false;\n db = undefined;\n }\n },\n\n // Lock methods are added dynamically below after first connection\n async tryAcquire(id: string, options?: { ttlMs?: number }): Promise<{ ownerToken: string } | null> {\n await ensureConnected();\n if (!lock) return null;\n return lock.tryAcquire(id, options);\n },\n\n async release(id: string, ownerToken: string): Promise<void> {\n await ensureConnected();\n if (!lock) return;\n return lock.release(id, ownerToken);\n },\n };\n\n // Only include lock methods if lock is configured\n if (!opts.lock) {\n delete store.tryAcquire;\n delete store.release;\n }\n\n return store;\n}\n","/**\n * MongoDB workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Db, Collection } from \"mongodb\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface MongoLockOptions {\n /**\n * Collection name for workflow locks.\n * @default 'workflow_lock'\n */\n lockCollectionName?: string;\n}\n\ninterface LockDocument {\n _id: string;\n ownerToken: string;\n expiresAt: Date;\n}\n\n/**\n * Create tryAcquire and release functions that use a MongoDB lock collection.\n * Caller must pass the same Db used for state (so one connection).\n */\nexport function createMongoLock(\n db: Db,\n options: MongoLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n ensureLockCollection(): Promise<void>;\n} {\n const lockCollectionName = options.lockCollectionName ?? \"workflow_lock\";\n const collection = db.collection<LockDocument>(lockCollectionName);\n\n async function ensureLockCollection(): Promise<void> {\n const collections = await db.listCollections({ name: lockCollectionName }).toArray();\n if (collections.length === 0) {\n await db.createCollection(lockCollectionName);\n }\n }\n\n async function tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const ownerToken = randomUUID();\n const expiresAt = new Date(Date.now() + ttlMs);\n\n await ensureLockCollection();\n\n // Atomic: insert or update only when no doc or doc is expired.\n // When lock exists and is unexpired, filter won't match and upsert\n // throws duplicate key error (E11000) - catch it and return null.\n try {\n const result = await collection.findOneAndUpdate(\n {\n _id: id,\n $or: [\n { expiresAt: { $lt: new Date() } },\n { expiresAt: { $exists: false } },\n ],\n },\n { $set: { ownerToken, expiresAt } },\n { upsert: true, returnDocument: \"after\" }\n );\n\n if (result && result.ownerToken === ownerToken) {\n return { ownerToken };\n }\n return null;\n } catch (error: unknown) {\n // Duplicate key error means lock exists and is not expired\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === 11000\n ) {\n return null;\n }\n throw error;\n }\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await collection.deleteOne({ _id: id, ownerToken });\n }\n\n return { tryAcquire, release, ensureLockCollection };\n}\n"],"mappings":"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","names":["index_exports","__export","mongo","__toCommonJS","import_mongodb","import_node_crypto","createMongoLock","db","options","lockCollectionName","collection","ensureLockCollection","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","error","release","mongo","urlOrOptions","opts","prefix","databaseName","urlMatch","collectionName","ownClient","client","db","connected","lock","ensureConnected","MongoClientImpl","createMongoLock","store","id","snapshot","collection","fullId","doc","options","filterPrefix","limit","ownerToken"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/mongo-lock.ts"],"sourcesContent":["/**\n * awaitly-mongo\n *\n * MongoDB persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by MongoDB.\n */\n\nimport type { Db, MongoClientOptions } from \"mongodb\";\nimport { MongoClient as MongoClientImpl } from \"mongodb\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\nimport { createMongoLock, type MongoLockOptions } from \"./mongo-lock\";\n\n/** Document shape for the snapshots collection (string _id). */\ninterface SnapshotDoc {\n _id: string;\n snapshot: WorkflowSnapshot;\n updatedAt: Date;\n}\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/durable\";\nexport type { MongoLockOptions } from \"./mongo-lock\";\n\n// =============================================================================\n// MongoOptions\n// =============================================================================\n\n/**\n * Options for the mongo() shorthand function.\n */\nexport interface MongoOptions {\n /** MongoDB connection URL. */\n url: string;\n /** Database name. @default 'awaitly' */\n database?: string;\n /** Collection name for snapshots. @default 'awaitly_snapshots' */\n collection?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own client. */\n client?: MongoClientImpl;\n /** MongoDB client options. */\n clientOptions?: MongoClientOptions;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: MongoLockOptions;\n}\n\n// =============================================================================\n// mongo() - One-liner Snapshot Store Setup\n// =============================================================================\n\n/**\n * Create a snapshot store backed by MongoDB.\n * This is the simplified one-liner API for workflow persistence.\n *\n * @example\n * ```typescript\n * import { mongo } from 'awaitly-mongo';\n *\n * // One-liner setup\n * const store = mongo('mongodb://localhost:27017/mydb');\n *\n * // Execute + persist\n * const wf = createWorkflow(deps);\n * await wf(myWorkflowFn);\n * await store.save('wf-123', wf.getSnapshot());\n *\n * // Restore\n * const snapshot = await store.load('wf-123');\n * const wf2 = createWorkflow(deps, { snapshot });\n * await wf2(myWorkflowFn);\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = mongo({\n * url: 'mongodb://localhost:27017',\n * database: 'myapp',\n * collection: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockCollectionName: 'my_workflow_locks' },\n * });\n * ```\n */\nexport function mongo(urlOrOptions: string | MongoOptions): SnapshotStore & Partial<WorkflowLock> {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const prefix = opts.prefix ?? \"\";\n\n // Parse database from URL if provided\n let databaseName = opts.database;\n const urlMatch = opts.url.match(/mongodb(?:\\+srv)?:\\/\\/[^/]+\\/([^?]+)/);\n if (!databaseName && urlMatch && urlMatch[1]) {\n databaseName = urlMatch[1];\n }\n databaseName = databaseName ?? \"awaitly\";\n\n const collectionName = opts.collection ?? \"awaitly_snapshots\";\n\n // Create or use existing client\n const ownClient = !opts.client;\n let client: MongoClientImpl | undefined = opts.client;\n let db: Db | undefined;\n let connected = false;\n let lock: { tryAcquire: WorkflowLock[\"tryAcquire\"]; release: WorkflowLock[\"release\"] } | null = null;\n\n const ensureConnected = async (): Promise<Db> => {\n if (db && connected) return db;\n\n if (!client) {\n client = new MongoClientImpl(opts.url, {\n directConnection: !opts.url.includes(\"mongodb+srv://\"),\n ...opts.clientOptions,\n });\n }\n\n await client.connect();\n connected = true;\n db = client.db(databaseName);\n\n // Create index on updatedAt for list queries\n const collection = db.collection<SnapshotDoc>(collectionName);\n await collection.createIndex({ updatedAt: -1 }, { background: true }).catch(() => {\n // Index may already exist, ignore error\n });\n\n // Create lock if requested\n if (opts.lock && !lock) {\n lock = createMongoLock(db, opts.lock);\n }\n\n return db;\n };\n\n const store: SnapshotStore & Partial<WorkflowLock> = {\n async save(id: string, snapshot: WorkflowSnapshot): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const fullId = prefix + id;\n await collection.updateOne(\n { _id: fullId },\n {\n $set: {\n snapshot,\n updatedAt: new Date(),\n },\n },\n { upsert: true }\n );\n },\n\n async load(id: string): Promise<WorkflowSnapshot | null> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const fullId = prefix + id;\n const doc = await collection.findOne({ _id: fullId });\n if (!doc) return null;\n return doc.snapshot;\n },\n\n async delete(id: string): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const fullId = prefix + id;\n await collection.deleteOne({ _id: fullId });\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n const escaped = filterPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\n const cursor = collection\n .find({ _id: { $regex: `^${escaped}` } })\n .sort({ updatedAt: -1 })\n .limit(limit);\n\n const docs = await cursor.toArray();\n return docs.map(doc => ({\n id: String(doc._id).slice(prefix.length),\n updatedAt: doc.updatedAt.toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only close client if we created it\n if (ownClient && client) {\n await client.close();\n connected = false;\n db = undefined;\n }\n },\n\n // Lock methods are added dynamically below after first connection\n async tryAcquire(id: string, options?: { ttlMs?: number }): Promise<{ ownerToken: string } | null> {\n await ensureConnected();\n if (!lock) return null;\n return lock.tryAcquire(id, options);\n },\n\n async release(id: string, ownerToken: string): Promise<void> {\n await ensureConnected();\n if (!lock) return;\n return lock.release(id, ownerToken);\n },\n };\n\n // Only include lock methods if lock is configured\n if (!opts.lock) {\n delete store.tryAcquire;\n delete store.release;\n }\n\n return store;\n}\n","/**\n * MongoDB workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Db, Collection } from \"mongodb\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface MongoLockOptions {\n /**\n * Collection name for workflow locks.\n * @default 'workflow_lock'\n */\n lockCollectionName?: string;\n}\n\ninterface LockDocument {\n _id: string;\n ownerToken: string;\n expiresAt: Date;\n}\n\n/**\n * Create tryAcquire and release functions that use a MongoDB lock collection.\n * Caller must pass the same Db used for state (so one connection).\n */\nexport function createMongoLock(\n db: Db,\n options: MongoLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n ensureLockCollection(): Promise<void>;\n} {\n const lockCollectionName = options.lockCollectionName ?? \"workflow_lock\";\n const collection = db.collection<LockDocument>(lockCollectionName);\n\n async function ensureLockCollection(): Promise<void> {\n const collections = await db.listCollections({ name: lockCollectionName }).toArray();\n if (collections.length === 0) {\n await db.createCollection(lockCollectionName);\n }\n }\n\n async function tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const ownerToken = randomUUID();\n const expiresAt = new Date(Date.now() + ttlMs);\n\n await ensureLockCollection();\n\n // Atomic: insert or update only when no doc or doc is expired.\n // When lock exists and is unexpired, filter won't match and upsert\n // throws duplicate key error (E11000) - catch it and return null.\n try {\n const result = await collection.findOneAndUpdate(\n {\n _id: id,\n $or: [\n { expiresAt: { $lt: new Date() } },\n { expiresAt: { $exists: false } },\n ],\n },\n { $set: { ownerToken, expiresAt } },\n { upsert: true, returnDocument: \"after\" }\n );\n\n if (result && result.ownerToken === ownerToken) {\n return { ownerToken };\n }\n return null;\n } catch (error: unknown) {\n // Duplicate key error means lock exists and is not expired\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === 11000\n ) {\n return null;\n }\n throw error;\n }\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await collection.deleteOne({ _id: id, ownerToken });\n }\n\n return { tryAcquire, release, ensureLockCollection };\n}\n"],"mappings":"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","names":["index_exports","__export","mongo","__toCommonJS","import_mongodb","import_node_crypto","createMongoLock","db","options","lockCollectionName","collection","ensureLockCollection","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","error","release","mongo","urlOrOptions","opts","prefix","databaseName","urlMatch","collectionName","ownClient","client","db","connected","lock","ensureConnected","MongoClientImpl","createMongoLock","store","id","snapshot","collection","fullId","doc","options","filterPrefix","limit","escaped","ownerToken"]}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{MongoClient as M}from"mongodb";import{randomUUID as A}from"crypto";function b(d,o={}){let l=o.lockCollectionName??"workflow_lock",c=d.collection(l);async function f(){(await d.listCollections({name:l}).toArray()).length===0&&await d.createCollection(l)}async function u(n,r){let w=r?.ttlMs??6e4,i=A(),s=new Date(Date.now()+w);await f();try{let e=await c.findOneAndUpdate({_id:n,$or:[{expiresAt:{$lt:new Date}},{expiresAt:{$exists:!1}}]},{$set:{ownerToken:i,expiresAt:s}},{upsert:!0,returnDocument:"after"});return e&&e.ownerToken===i?{ownerToken:i}:null}catch(e){if(e&&typeof e=="object"&&"code"in e&&e.code===11e3)return null;throw e}}async function k(n,r){await c.deleteOne({_id:n,ownerToken:r})}return{tryAcquire:u,release:k,ensureLockCollection:f}}function D(d){let o=typeof d=="string"?{url:d}:d,l=o.prefix??"",c=o.database,f=o.url.match(/mongodb(?:\+srv)?:\/\/[^/]+\/([^?]+)/);!c&&f&&f[1]&&(c=f[1]),c=c??"awaitly";let u=o.collection??"awaitly_snapshots",k=!o.client,n=o.client,r,w=!1,i=null,s=async()=>(r&&w||(n||(n=new M(o.url,{directConnection:!o.url.includes("mongodb+srv://"),...o.clientOptions})),await n.connect(),w=!0,r=n.db(c),await r.collection(u).createIndex({updatedAt:-1},{background:!0}).catch(()=>{}),o.lock&&!i&&(i=b(r,o.lock))),r),e={async save(t,a){let p=(await s()).collection(u),g=l+t;await p.updateOne({_id:g},{$set:{snapshot:a,updatedAt:new Date}},{upsert:!0})},async load(t){let m=(await s()).collection(u),p=l+t,g=await m.findOne({_id:p});return g?g.snapshot:null},async delete(t){let m=(await s()).collection(u),p=l+t;await m.deleteOne({_id:p})},async list(t){let m=(await s()).collection(u),p=l+(t?.prefix??""),g=t?.limit??100;return(await m.find({_id:{$regex:`^${p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}`}}).sort({updatedAt:-1}).limit(g).toArray()).map(y=>({id:String(y._id).slice(l.length),updatedAt:y.updatedAt.toISOString()}))},async close(){k&&n&&(await n.close(),w=!1,r=void 0)},async tryAcquire(t,a){return await s(),i?i.tryAcquire(t,a):null},async release(t,a){if(await s(),!!i)return i.release(t,a)}};return o.lock||(delete e.tryAcquire,delete e.release),e}export{D as mongo};
1
+ import{MongoClient as S}from"mongodb";import{randomUUID as A}from"crypto";function b(d,o={}){let c=o.lockCollectionName??"workflow_lock",s=d.collection(c);async function f(){(await d.listCollections({name:c}).toArray()).length===0&&await d.createCollection(c)}async function p(n,r){let w=r?.ttlMs??6e4,i=A(),l=new Date(Date.now()+w);await f();try{let e=await s.findOneAndUpdate({_id:n,$or:[{expiresAt:{$lt:new Date}},{expiresAt:{$exists:!1}}]},{$set:{ownerToken:i,expiresAt:l}},{upsert:!0,returnDocument:"after"});return e&&e.ownerToken===i?{ownerToken:i}:null}catch(e){if(e&&typeof e=="object"&&"code"in e&&e.code===11e3)return null;throw e}}async function k(n,r){await s.deleteOne({_id:n,ownerToken:r})}return{tryAcquire:p,release:k,ensureLockCollection:f}}function O(d){let o=typeof d=="string"?{url:d}:d,c=o.prefix??"",s=o.database,f=o.url.match(/mongodb(?:\+srv)?:\/\/[^/]+\/([^?]+)/);!s&&f&&f[1]&&(s=f[1]),s=s??"awaitly";let p=o.collection??"awaitly_snapshots",k=!o.client,n=o.client,r,w=!1,i=null,l=async()=>(r&&w||(n||(n=new S(o.url,{directConnection:!o.url.includes("mongodb+srv://"),...o.clientOptions})),await n.connect(),w=!0,r=n.db(s),await r.collection(p).createIndex({updatedAt:-1},{background:!0}).catch(()=>{}),o.lock&&!i&&(i=b(r,o.lock))),r),e={async save(t,a){let u=(await l()).collection(p),g=c+t;await u.updateOne({_id:g},{$set:{snapshot:a,updatedAt:new Date}},{upsert:!0})},async load(t){let m=(await l()).collection(p),u=c+t,g=await m.findOne({_id:u});return g?g.snapshot:null},async delete(t){let m=(await l()).collection(p),u=c+t;await m.deleteOne({_id:u})},async list(t){let m=(await l()).collection(p),u=c+(t?.prefix??""),g=t?.limit??100,h=u.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return(await m.find({_id:{$regex:`^${h}`}}).sort({updatedAt:-1}).limit(g).toArray()).map(y=>({id:String(y._id).slice(c.length),updatedAt:y.updatedAt.toISOString()}))},async close(){k&&n&&(await n.close(),w=!1,r=void 0)},async tryAcquire(t,a){return await l(),i?i.tryAcquire(t,a):null},async release(t,a){if(await l(),!!i)return i.release(t,a)}};return o.lock||(delete e.tryAcquire,delete e.release),e}export{O as mongo};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/mongo-lock.ts"],"sourcesContent":["/**\n * awaitly-mongo\n *\n * MongoDB persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by MongoDB.\n */\n\nimport type { Db, MongoClientOptions } from \"mongodb\";\nimport { MongoClient as MongoClientImpl } from \"mongodb\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\nimport { createMongoLock, type MongoLockOptions } from \"./mongo-lock\";\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/durable\";\nexport type { MongoLockOptions } from \"./mongo-lock\";\n\n// =============================================================================\n// MongoOptions\n// =============================================================================\n\n/**\n * Options for the mongo() shorthand function.\n */\nexport interface MongoOptions {\n /** MongoDB connection URL. */\n url: string;\n /** Database name. @default 'awaitly' */\n database?: string;\n /** Collection name for snapshots. @default 'awaitly_snapshots' */\n collection?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own client. */\n client?: MongoClientImpl;\n /** MongoDB client options. */\n clientOptions?: MongoClientOptions;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: MongoLockOptions;\n}\n\n// =============================================================================\n// mongo() - One-liner Snapshot Store Setup\n// =============================================================================\n\n/**\n * Create a snapshot store backed by MongoDB.\n * This is the simplified one-liner API for workflow persistence.\n *\n * @example\n * ```typescript\n * import { mongo } from 'awaitly-mongo';\n *\n * // One-liner setup\n * const store = mongo('mongodb://localhost:27017/mydb');\n *\n * // Execute + persist\n * const wf = createWorkflow(deps);\n * await wf(myWorkflowFn);\n * await store.save('wf-123', wf.getSnapshot());\n *\n * // Restore\n * const snapshot = await store.load('wf-123');\n * const wf2 = createWorkflow(deps, { snapshot });\n * await wf2(myWorkflowFn);\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = mongo({\n * url: 'mongodb://localhost:27017',\n * database: 'myapp',\n * collection: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockCollectionName: 'my_workflow_locks' },\n * });\n * ```\n */\nexport function mongo(urlOrOptions: string | MongoOptions): SnapshotStore & Partial<WorkflowLock> {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const prefix = opts.prefix ?? \"\";\n\n // Parse database from URL if provided\n let databaseName = opts.database;\n const urlMatch = opts.url.match(/mongodb(?:\\+srv)?:\\/\\/[^/]+\\/([^?]+)/);\n if (!databaseName && urlMatch && urlMatch[1]) {\n databaseName = urlMatch[1];\n }\n databaseName = databaseName ?? \"awaitly\";\n\n const collectionName = opts.collection ?? \"awaitly_snapshots\";\n\n // Create or use existing client\n const ownClient = !opts.client;\n let client: MongoClientImpl | undefined = opts.client;\n let db: Db | undefined;\n let connected = false;\n let lock: { tryAcquire: WorkflowLock[\"tryAcquire\"]; release: WorkflowLock[\"release\"] } | null = null;\n\n const ensureConnected = async (): Promise<Db> => {\n if (db && connected) return db;\n\n if (!client) {\n client = new MongoClientImpl(opts.url, {\n directConnection: !opts.url.includes(\"mongodb+srv://\"),\n ...opts.clientOptions,\n });\n }\n\n await client.connect();\n connected = true;\n db = client.db(databaseName);\n\n // Create index on updatedAt for list queries\n const collection = db.collection(collectionName);\n await collection.createIndex({ updatedAt: -1 }, { background: true }).catch(() => {\n // Index may already exist, ignore error\n });\n\n // Create lock if requested\n if (opts.lock && !lock) {\n lock = createMongoLock(db, opts.lock);\n }\n\n return db;\n };\n\n const store: SnapshotStore & Partial<WorkflowLock> = {\n async save(id: string, snapshot: WorkflowSnapshot): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const fullId = prefix + id;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await collection.updateOne(\n { _id: fullId } as any,\n {\n $set: {\n snapshot,\n updatedAt: new Date(),\n },\n },\n { upsert: true }\n );\n },\n\n async load(id: string): Promise<WorkflowSnapshot | null> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const fullId = prefix + id;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const doc = await collection.findOne({ _id: fullId } as any);\n if (!doc) return null;\n return doc.snapshot as WorkflowSnapshot;\n },\n\n async delete(id: string): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const fullId = prefix + id;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await collection.deleteOne({ _id: fullId } as any);\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n const db = await ensureConnected();\n const collection = db.collection(collectionName);\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cursor = collection\n .find({ _id: { $regex: `^${filterPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}` } } as any)\n .sort({ updatedAt: -1 })\n .limit(limit);\n\n const docs = await cursor.toArray();\n return docs.map(doc => ({\n id: String(doc._id).slice(prefix.length),\n updatedAt: (doc.updatedAt as Date).toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only close client if we created it\n if (ownClient && client) {\n await client.close();\n connected = false;\n db = undefined;\n }\n },\n\n // Lock methods are added dynamically below after first connection\n async tryAcquire(id: string, options?: { ttlMs?: number }): Promise<{ ownerToken: string } | null> {\n await ensureConnected();\n if (!lock) return null;\n return lock.tryAcquire(id, options);\n },\n\n async release(id: string, ownerToken: string): Promise<void> {\n await ensureConnected();\n if (!lock) return;\n return lock.release(id, ownerToken);\n },\n };\n\n // Only include lock methods if lock is configured\n if (!opts.lock) {\n delete store.tryAcquire;\n delete store.release;\n }\n\n return store;\n}\n","/**\n * MongoDB workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Db, Collection } from \"mongodb\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface MongoLockOptions {\n /**\n * Collection name for workflow locks.\n * @default 'workflow_lock'\n */\n lockCollectionName?: string;\n}\n\ninterface LockDocument {\n _id: string;\n ownerToken: string;\n expiresAt: Date;\n}\n\n/**\n * Create tryAcquire and release functions that use a MongoDB lock collection.\n * Caller must pass the same Db used for state (so one connection).\n */\nexport function createMongoLock(\n db: Db,\n options: MongoLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n ensureLockCollection(): Promise<void>;\n} {\n const lockCollectionName = options.lockCollectionName ?? \"workflow_lock\";\n const collection = db.collection<LockDocument>(lockCollectionName);\n\n async function ensureLockCollection(): Promise<void> {\n const collections = await db.listCollections({ name: lockCollectionName }).toArray();\n if (collections.length === 0) {\n await db.createCollection(lockCollectionName);\n }\n }\n\n async function tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const ownerToken = randomUUID();\n const expiresAt = new Date(Date.now() + ttlMs);\n\n await ensureLockCollection();\n\n // Atomic: insert or update only when no doc or doc is expired.\n // When lock exists and is unexpired, filter won't match and upsert\n // throws duplicate key error (E11000) - catch it and return null.\n try {\n const result = await collection.findOneAndUpdate(\n {\n _id: id,\n $or: [\n { expiresAt: { $lt: new Date() } },\n { expiresAt: { $exists: false } },\n ],\n },\n { $set: { ownerToken, expiresAt } },\n { upsert: true, returnDocument: \"after\" }\n );\n\n if (result && result.ownerToken === ownerToken) {\n return { ownerToken };\n }\n return null;\n } catch (error: unknown) {\n // Duplicate key error means lock exists and is not expired\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === 11000\n ) {\n return null;\n }\n throw error;\n }\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await collection.deleteOne({ _id: id, ownerToken });\n }\n\n return { tryAcquire, release, ensureLockCollection };\n}\n"],"mappings":"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","names":["MongoClientImpl","randomUUID","createMongoLock","db","options","lockCollectionName","collection","ensureLockCollection","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","error","release","mongo","urlOrOptions","opts","prefix","databaseName","urlMatch","collectionName","ownClient","client","db","connected","lock","ensureConnected","MongoClientImpl","createMongoLock","store","id","snapshot","collection","fullId","doc","options","filterPrefix","limit","ownerToken"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/mongo-lock.ts"],"sourcesContent":["/**\n * awaitly-mongo\n *\n * MongoDB persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by MongoDB.\n */\n\nimport type { Db, MongoClientOptions } from \"mongodb\";\nimport { MongoClient as MongoClientImpl } from \"mongodb\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\nimport { createMongoLock, type MongoLockOptions } from \"./mongo-lock\";\n\n/** Document shape for the snapshots collection (string _id). */\ninterface SnapshotDoc {\n _id: string;\n snapshot: WorkflowSnapshot;\n updatedAt: Date;\n}\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/durable\";\nexport type { MongoLockOptions } from \"./mongo-lock\";\n\n// =============================================================================\n// MongoOptions\n// =============================================================================\n\n/**\n * Options for the mongo() shorthand function.\n */\nexport interface MongoOptions {\n /** MongoDB connection URL. */\n url: string;\n /** Database name. @default 'awaitly' */\n database?: string;\n /** Collection name for snapshots. @default 'awaitly_snapshots' */\n collection?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own client. */\n client?: MongoClientImpl;\n /** MongoDB client options. */\n clientOptions?: MongoClientOptions;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: MongoLockOptions;\n}\n\n// =============================================================================\n// mongo() - One-liner Snapshot Store Setup\n// =============================================================================\n\n/**\n * Create a snapshot store backed by MongoDB.\n * This is the simplified one-liner API for workflow persistence.\n *\n * @example\n * ```typescript\n * import { mongo } from 'awaitly-mongo';\n *\n * // One-liner setup\n * const store = mongo('mongodb://localhost:27017/mydb');\n *\n * // Execute + persist\n * const wf = createWorkflow(deps);\n * await wf(myWorkflowFn);\n * await store.save('wf-123', wf.getSnapshot());\n *\n * // Restore\n * const snapshot = await store.load('wf-123');\n * const wf2 = createWorkflow(deps, { snapshot });\n * await wf2(myWorkflowFn);\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = mongo({\n * url: 'mongodb://localhost:27017',\n * database: 'myapp',\n * collection: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockCollectionName: 'my_workflow_locks' },\n * });\n * ```\n */\nexport function mongo(urlOrOptions: string | MongoOptions): SnapshotStore & Partial<WorkflowLock> {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const prefix = opts.prefix ?? \"\";\n\n // Parse database from URL if provided\n let databaseName = opts.database;\n const urlMatch = opts.url.match(/mongodb(?:\\+srv)?:\\/\\/[^/]+\\/([^?]+)/);\n if (!databaseName && urlMatch && urlMatch[1]) {\n databaseName = urlMatch[1];\n }\n databaseName = databaseName ?? \"awaitly\";\n\n const collectionName = opts.collection ?? \"awaitly_snapshots\";\n\n // Create or use existing client\n const ownClient = !opts.client;\n let client: MongoClientImpl | undefined = opts.client;\n let db: Db | undefined;\n let connected = false;\n let lock: { tryAcquire: WorkflowLock[\"tryAcquire\"]; release: WorkflowLock[\"release\"] } | null = null;\n\n const ensureConnected = async (): Promise<Db> => {\n if (db && connected) return db;\n\n if (!client) {\n client = new MongoClientImpl(opts.url, {\n directConnection: !opts.url.includes(\"mongodb+srv://\"),\n ...opts.clientOptions,\n });\n }\n\n await client.connect();\n connected = true;\n db = client.db(databaseName);\n\n // Create index on updatedAt for list queries\n const collection = db.collection<SnapshotDoc>(collectionName);\n await collection.createIndex({ updatedAt: -1 }, { background: true }).catch(() => {\n // Index may already exist, ignore error\n });\n\n // Create lock if requested\n if (opts.lock && !lock) {\n lock = createMongoLock(db, opts.lock);\n }\n\n return db;\n };\n\n const store: SnapshotStore & Partial<WorkflowLock> = {\n async save(id: string, snapshot: WorkflowSnapshot): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const fullId = prefix + id;\n await collection.updateOne(\n { _id: fullId },\n {\n $set: {\n snapshot,\n updatedAt: new Date(),\n },\n },\n { upsert: true }\n );\n },\n\n async load(id: string): Promise<WorkflowSnapshot | null> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const fullId = prefix + id;\n const doc = await collection.findOne({ _id: fullId });\n if (!doc) return null;\n return doc.snapshot;\n },\n\n async delete(id: string): Promise<void> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const fullId = prefix + id;\n await collection.deleteOne({ _id: fullId });\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n const db = await ensureConnected();\n const collection = db.collection<SnapshotDoc>(collectionName);\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n const escaped = filterPrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\n const cursor = collection\n .find({ _id: { $regex: `^${escaped}` } })\n .sort({ updatedAt: -1 })\n .limit(limit);\n\n const docs = await cursor.toArray();\n return docs.map(doc => ({\n id: String(doc._id).slice(prefix.length),\n updatedAt: doc.updatedAt.toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only close client if we created it\n if (ownClient && client) {\n await client.close();\n connected = false;\n db = undefined;\n }\n },\n\n // Lock methods are added dynamically below after first connection\n async tryAcquire(id: string, options?: { ttlMs?: number }): Promise<{ ownerToken: string } | null> {\n await ensureConnected();\n if (!lock) return null;\n return lock.tryAcquire(id, options);\n },\n\n async release(id: string, ownerToken: string): Promise<void> {\n await ensureConnected();\n if (!lock) return;\n return lock.release(id, ownerToken);\n },\n };\n\n // Only include lock methods if lock is configured\n if (!opts.lock) {\n delete store.tryAcquire;\n delete store.release;\n }\n\n return store;\n}\n","/**\n * MongoDB workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Db, Collection } from \"mongodb\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface MongoLockOptions {\n /**\n * Collection name for workflow locks.\n * @default 'workflow_lock'\n */\n lockCollectionName?: string;\n}\n\ninterface LockDocument {\n _id: string;\n ownerToken: string;\n expiresAt: Date;\n}\n\n/**\n * Create tryAcquire and release functions that use a MongoDB lock collection.\n * Caller must pass the same Db used for state (so one connection).\n */\nexport function createMongoLock(\n db: Db,\n options: MongoLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n ensureLockCollection(): Promise<void>;\n} {\n const lockCollectionName = options.lockCollectionName ?? \"workflow_lock\";\n const collection = db.collection<LockDocument>(lockCollectionName);\n\n async function ensureLockCollection(): Promise<void> {\n const collections = await db.listCollections({ name: lockCollectionName }).toArray();\n if (collections.length === 0) {\n await db.createCollection(lockCollectionName);\n }\n }\n\n async function tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const ownerToken = randomUUID();\n const expiresAt = new Date(Date.now() + ttlMs);\n\n await ensureLockCollection();\n\n // Atomic: insert or update only when no doc or doc is expired.\n // When lock exists and is unexpired, filter won't match and upsert\n // throws duplicate key error (E11000) - catch it and return null.\n try {\n const result = await collection.findOneAndUpdate(\n {\n _id: id,\n $or: [\n { expiresAt: { $lt: new Date() } },\n { expiresAt: { $exists: false } },\n ],\n },\n { $set: { ownerToken, expiresAt } },\n { upsert: true, returnDocument: \"after\" }\n );\n\n if (result && result.ownerToken === ownerToken) {\n return { ownerToken };\n }\n return null;\n } catch (error: unknown) {\n // Duplicate key error means lock exists and is not expired\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === 11000\n ) {\n return null;\n }\n throw error;\n }\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await collection.deleteOne({ _id: id, ownerToken });\n }\n\n return { tryAcquire, release, ensureLockCollection };\n}\n"],"mappings":"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","names":["MongoClientImpl","randomUUID","createMongoLock","db","options","lockCollectionName","collection","ensureLockCollection","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","error","release","mongo","urlOrOptions","opts","prefix","databaseName","urlMatch","collectionName","ownClient","client","db","connected","lock","ensureConnected","MongoClientImpl","createMongoLock","store","id","snapshot","collection","fullId","doc","options","filterPrefix","limit","escaped","ownerToken"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awaitly-mongo",
3
- "version": "9.0.0",
3
+ "version": "10.0.0",
4
4
  "type": "module",
5
5
  "description": "MongoDB persistence adapter for awaitly workflows",
6
6
  "main": "./dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "license": "MIT",
40
40
  "peerDependencies": {
41
- "awaitly": "^1.19.0"
41
+ "awaitly": "^1.20.0"
42
42
  },
43
43
  "dependencies": {
44
44
  "mongodb": "^7.1.0"
@@ -50,7 +50,7 @@
50
50
  "tsup": "^8.5.1",
51
51
  "typescript": "^5.9.3",
52
52
  "vitest": "^4.0.18",
53
- "awaitly": "^1.19.0"
53
+ "awaitly": "^1.20.0"
54
54
  },
55
55
  "publishConfig": {
56
56
  "access": "public",