awaitly-postgres 25.0.0 → 27.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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  PostgreSQL persistence adapter for [awaitly](https://github.com/jagreehal/awaitly) workflows.
4
4
 
5
+ Provides a ready-to-use snapshot store backed by PostgreSQL. The store accepts both workflow snapshots and resume state, so it plugs straight into `durable.run` and `createWorkflow`.
6
+
5
7
  ## Installation
6
8
 
7
9
  ```bash
@@ -15,18 +17,16 @@ yarn add awaitly-postgres pg
15
17
  ## Quick Start
16
18
 
17
19
  ```typescript
18
- import { createPostgresPersistence } from 'awaitly-postgres';
20
+ import { postgres } from 'awaitly-postgres';
19
21
  import { durable } from 'awaitly/durable';
20
22
 
21
- const store = await createPostgresPersistence({
22
- connectionString: process.env.DATABASE_URL,
23
- });
23
+ const store = postgres(process.env.DATABASE_URL!);
24
24
 
25
25
  const result = await durable.run(
26
26
  { fetchUser, createOrder },
27
27
  async ({ step, deps: { fetchUser, createOrder } }) => {
28
- const user = await step('fetchUser', () => fetchUser('123'), { key: 'fetch-user' });
29
- const order = await step('createOrder', () => createOrder(user), { key: 'create-order' });
28
+ const user = await step('fetch-user', () => fetchUser('123'));
29
+ const order = await step('create-order', () => createOrder(user));
30
30
  return order;
31
31
  },
32
32
  {
@@ -38,77 +38,101 @@ const result = await durable.run(
38
38
 
39
39
  ## Configuration
40
40
 
41
- ### Connection String
41
+ ### Connection URL
42
42
 
43
43
  ```typescript
44
- const store = await createPostgresPersistence({
45
- connectionString: 'postgresql://user:password@localhost:5432/dbname',
46
- });
44
+ const store = postgres('postgresql://user:password@localhost:5432/dbname');
47
45
  ```
48
46
 
49
- ### Individual Options
47
+ ### Options
50
48
 
51
49
  ```typescript
52
- const store = await createPostgresPersistence({
53
- host: 'localhost',
54
- port: 5432,
55
- database: 'myapp',
56
- user: 'postgres',
57
- password: 'password',
58
- tableName: 'custom_workflow_state', // optional, default: 'awaitly_workflow_state'
59
- prefix: 'myapp:workflow:', // optional, default: 'workflow:state:'
50
+ const store = postgres({
51
+ url: process.env.DATABASE_URL!,
52
+ table: 'my_workflow_snapshots', // optional, default: 'awaitly_snapshots'
53
+ prefix: 'orders:', // optional ID prefix, default: ''
54
+ autoCreateTable: true, // optional, default: true
60
55
  });
61
56
  ```
62
57
 
63
- ### Using Existing Pool
58
+ ### Using an Existing Pool
64
59
 
65
60
  ```typescript
66
61
  import { Pool } from 'pg';
67
- import { createPostgresPersistence } from 'awaitly-postgres';
62
+ import { postgres } from 'awaitly-postgres';
68
63
 
69
64
  const pool = new Pool({ connectionString: process.env.DATABASE_URL });
70
- const store = await createPostgresPersistence({
71
- existingPool: pool,
65
+ const store = postgres({
66
+ url: process.env.DATABASE_URL!,
67
+ pool,
72
68
  });
73
69
  ```
74
70
 
75
- ### Pool Configuration
71
+ ### Cross-Process Locking
72
+
73
+ To ensure only one process runs a given workflow ID at a time, pass the `lock` option. The store then implements `WorkflowLock`, and `durable.run` acquires the lock before running (unless `allowConcurrent: true`) and releases it when done:
76
74
 
77
75
  ```typescript
78
- const store = await createPostgresPersistence({
79
- connectionString: process.env.DATABASE_URL,
80
- pool: {
81
- max: 20,
82
- idleTimeoutMillis: 30000,
83
- connectionTimeoutMillis: 2000,
84
- },
76
+ const store = postgres({
77
+ url: process.env.DATABASE_URL!,
78
+ lock: { lockTableName: 'awaitly_workflow_lock' }, // optional; default table name
85
79
  });
86
80
  ```
87
81
 
82
+ ## Using with createWorkflow
83
+
84
+ The store also works directly with workflow resume state:
85
+
86
+ ```typescript
87
+ import { postgres } from 'awaitly-postgres';
88
+ import { createWorkflow } from 'awaitly/workflow';
89
+
90
+ const store = postgres('postgresql://localhost/mydb');
91
+ const workflow = createWorkflow(deps);
92
+
93
+ // Run and persist resume state
94
+ const { result, resumeState } = await workflow.runWithState(fn);
95
+ await store.save('wf-123', resumeState);
96
+
97
+ // Restore later
98
+ const saved = await store.loadResumeState('wf-123');
99
+ if (saved) await workflow.run(fn, { resumeState: saved });
100
+ ```
101
+
102
+ ## Store API
103
+
104
+ ```typescript
105
+ store.save(id, state); // WorkflowSnapshot or ResumeState
106
+ store.load(id); // returns whichever was stored
107
+ store.loadResumeState(id); // ResumeState | null
108
+ store.delete(id);
109
+ store.list({ prefix, limit }); // [{ id, updatedAt }]
110
+ store.close();
111
+ ```
112
+
88
113
  ## Table Schema
89
114
 
90
115
  The adapter automatically creates a table with the following schema:
91
116
 
92
117
  ```sql
93
- CREATE TABLE awaitly_workflow_state (
94
- key TEXT PRIMARY KEY,
95
- value TEXT NOT NULL,
96
- expires_at TIMESTAMP
118
+ CREATE TABLE IF NOT EXISTS awaitly_snapshots (
119
+ id TEXT PRIMARY KEY,
120
+ snapshot JSONB NOT NULL,
121
+ updated_at TIMESTAMPTZ DEFAULT NOW()
97
122
  );
98
123
 
99
- CREATE INDEX idx_awaitly_workflow_state_expires_at
100
- ON awaitly_workflow_state(expires_at)
101
- WHERE expires_at IS NOT NULL;
124
+ CREATE INDEX IF NOT EXISTS awaitly_snapshots_updated_at_idx
125
+ ON awaitly_snapshots (updated_at DESC);
102
126
  ```
103
127
 
104
- The table is created automatically on first use. You can customize the table name via the `tableName` option.
128
+ The table is created automatically on first use (disable with `autoCreateTable: false`). You can customize the table name via the `table` option.
105
129
 
106
130
  ## Features
107
131
 
108
132
  - ✅ Automatic table creation
109
- - ✅ TTL support (automatic expiration)
110
- - ✅ Connection pooling
111
- - ✅ Pattern matching for key queries
133
+ - ✅ Stores workflow snapshots and resume state (JSONB)
134
+ - ✅ Optional cross-process locking (`WorkflowLock`)
135
+ - ✅ Bring your own `pg` pool
112
136
  - ✅ Zero configuration required
113
137
 
114
138
  ## Requirements
package/dist/index.cjs CHANGED
@@ -1,11 +1,11 @@
1
- "use strict";var T=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var L=(r,t)=>{for(var e in t)T(r,e,{get:t[e],enumerable:!0})},N=(r,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let l of y(t))!_.call(r,l)&&l!==e&&T(r,l,{get:()=>t[l],enumerable:!(a=k(t,l))||a.enumerable});return r};var R=r=>N(T({},"__esModule",{value:!0}),r);var O={};L(O,{postgres:()=>I});module.exports=R(O);var P=require("pg"),i=require("awaitly/workflow");var f=require("crypto");function g(r,t={}){let e=t.lockTableName??"awaitly_workflow_lock",a=`idx_${e.replace(/[^a-zA-Z0-9_]/g,"_")}_expires_at`;async function l(){await r.query(`
1
+ "use strict";var T=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var L=(r,t)=>{for(var e in t)T(r,e,{get:t[e],enumerable:!0})},N=(r,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let l of y(t))!_.call(r,l)&&l!==e&&T(r,l,{get:()=>t[l],enumerable:!(a=k(t,l))||a.enumerable});return r};var R=r=>N(T({},"__esModule",{value:!0}),r);var O={};L(O,{postgres:()=>I});module.exports=R(O);var P=require("pg"),i=require("awaitly/persistence");var f=require("crypto");function g(r,t={}){let e=t.lockTableName??"awaitly_workflow_lock",a=`idx_${e.replace(/[^a-zA-Z0-9_]/g,"_")}_expires_at`;async function l(){await r.query(`
2
2
  CREATE TABLE IF NOT EXISTS ${e} (
3
3
  workflow_id TEXT PRIMARY KEY,
4
4
  owner_token TEXT NOT NULL,
5
5
  expires_at TIMESTAMPTZ NOT NULL
6
6
  );
7
7
  CREATE INDEX IF NOT EXISTS ${a} ON ${e}(expires_at);
8
- `)}async function m(d,n){let c=n?.ttlMs??6e4,s=(0,f.randomUUID)(),o=new Date(Date.now()+c);await l();let p=await r.query(`
8
+ `)}async function m(p,n){let d=n?.ttlMs??6e4,s=(0,f.randomUUID)(),o=new Date(Date.now()+d);await l();let c=await r.query(`
9
9
  INSERT INTO ${e} (workflow_id, owner_token, expires_at)
10
10
  VALUES ($1, $2, $3)
11
11
  ON CONFLICT (workflow_id) DO UPDATE SET
@@ -13,7 +13,7 @@
13
13
  expires_at = EXCLUDED.expires_at
14
14
  WHERE ${e}.expires_at < NOW()
15
15
  RETURNING owner_token
16
- `,[d,s,o]);return p.rowCount===1&&p.rows[0].owner_token===s?{ownerToken:s}:null}async function u(d,n){await r.query(`DELETE FROM ${e} WHERE workflow_id = $1 AND owner_token = $2`,[d,n])}async function E(d,n,c){let s=c?.ttlMs??6e4,o=new Date(Date.now()+s);return((await r.query(`UPDATE ${e} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,[o,d,n])).rowCount??0)>0}return{tryAcquire:m,release:u,renew:E,ensureLockTable:l}}var A=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function I(r){let t=typeof r=="string"?{url:r}:r,e=t.table??"awaitly_snapshots";if(!A.test(e))throw new Error(`Invalid table name: ${e}. Must be alphanumeric with underscores.`);let a=t.prefix??"",l=t.autoCreateTable??!0,m=!t.pool,u=t.pool??new P.Pool({connectionString:t.url}),E=!1,d=async()=>{!l||E||(await u.query(`
16
+ `,[p,s,o]);return c.rowCount===1&&c.rows[0].owner_token===s?{ownerToken:s}:null}async function u(p,n){await r.query(`DELETE FROM ${e} WHERE workflow_id = $1 AND owner_token = $2`,[p,n])}async function E(p,n,d){let s=d?.ttlMs??6e4,o=new Date(Date.now()+s);return((await r.query(`UPDATE ${e} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,[o,p,n])).rowCount??0)>0}return{tryAcquire:m,release:u,renew:E,ensureLockTable:l}}var A=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function I(r){let t=typeof r=="string"?{url:r}:r,e=t.table??"awaitly_snapshots";if(!A.test(e))throw new Error(`Invalid table name: ${e}. Must be alphanumeric with underscores.`);let a=t.prefix??"",l=t.autoCreateTable??!0,m=!t.pool,u=t.pool??new P.Pool({connectionString:t.url}),E=!1,p=async()=>{!l||E||(await u.query(`
17
17
  CREATE TABLE IF NOT EXISTS ${e} (
18
18
  id TEXT PRIMARY KEY,
19
19
  snapshot JSONB NOT NULL,
@@ -21,10 +21,10 @@
21
21
  )
22
22
  `),await u.query(`
23
23
  CREATE INDEX IF NOT EXISTS ${e}_updated_at_idx ON ${e} (updated_at DESC)
24
- `),E=!0)},n=t.lock?g(u,t.lock):null,c={async save(s,o){await d();let p=a+s,w=(0,i.isResumeState)(o)?(0,i.serializeResumeState)(o):o;await u.query(`INSERT INTO ${e} (id, snapshot, updated_at)
24
+ `),E=!0)},n=t.lock?g(u,t.lock):null,d={async save(s,o){await p();let c=a+s,w=(0,i.isResumeState)(o)?(0,i.serializeResumeState)(o):o;await u.query(`INSERT INTO ${e} (id, snapshot, updated_at)
25
25
  VALUES ($1, $2, NOW())
26
- ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,[p,JSON.stringify(w)])},async load(s){await d();let o=a+s,p=await u.query(`SELECT snapshot FROM ${e} WHERE id = $1`,[o]);if(p.rows.length===0)return null;let w=p.rows[0].snapshot;return(0,i.isSerializedResumeState)(w)?(0,i.deserializeResumeState)(w):((0,i.isWorkflowSnapshot)(w),w)},async loadResumeState(s){let o=await c.load(s);return o===null?null:(0,i.isResumeState)(o)?o:null},async delete(s){await d();let o=a+s;await u.query(`DELETE FROM ${e} WHERE id = $1`,[o])},async list(s){await d();let o=a+(s?.prefix??""),p=s?.limit??100;return(await u.query(`SELECT id, updated_at FROM ${e}
26
+ ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,[c,JSON.stringify(w)])},async load(s){await p();let o=a+s,c=await u.query(`SELECT snapshot FROM ${e} WHERE id = $1`,[o]);if(c.rows.length===0)return null;let w=c.rows[0].snapshot;return(0,i.isSerializedResumeState)(w)?(0,i.deserializeResumeState)(w):((0,i.isWorkflowSnapshot)(w),w)},async loadResumeState(s){let o=await d.load(s);return o===null?null:(0,i.isResumeState)(o)?o:null},async delete(s){await p();let o=a+s;await u.query(`DELETE FROM ${e} WHERE id = $1`,[o])},async list(s){await p();let o=a+(s?.prefix??""),c=s?.limit??100;return(await u.query(`SELECT id, updated_at FROM ${e}
27
27
  WHERE id LIKE $1
28
28
  ORDER BY updated_at DESC
29
- LIMIT $2`,[o+"%",p])).rows.map(S=>({id:S.id.slice(a.length),updatedAt:S.updated_at.toISOString()}))},async close(){m&&await u.end()}};return n&&(c.tryAcquire=n.tryAcquire.bind(n),c.release=n.release.bind(n),c.renew=n.renew.bind(n)),c}0&&(module.exports={postgres});
29
+ LIMIT $2`,[o+"%",c])).rows.map(S=>({id:S.id.slice(a.length),updatedAt:S.updated_at.toISOString()}))},async close(){m&&await u.end()}};return n&&(d.tryAcquire=n.tryAcquire.bind(n),d.release=n.release.bind(n),d.renew=n.renew.bind(n)),d}0&&(module.exports={postgres});
30
30
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/postgres-lock.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by PostgreSQL.\n * Supports both WorkflowSnapshot and ResumeState (serialized via serializeResumeState).\n */\n\nimport { Pool as PgPool } from \"pg\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\nimport {\n type ResumeState,\n type StoreSaveInput,\n type StoreLoadResult,\n isWorkflowSnapshot,\n isResumeState,\n isSerializedResumeState,\n serializeResumeState,\n deserializeResumeState,\n} from \"awaitly/workflow\";\nimport { createPostgresLock, type PostgresLockOptions } from \"./postgres-lock\";\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/durable\";\nexport type { PostgresLockOptions } from \"./postgres-lock\";\nexport type { StoreSaveInput, StoreLoadResult } from \"awaitly/workflow\";\n\n// =============================================================================\n// PostgresOptions\n// =============================================================================\n\n/**\n * Options for the postgres() shorthand function.\n */\nexport interface PostgresOptions {\n /** PostgreSQL connection URL. */\n url: string;\n /** Table name for snapshots. @default 'awaitly_snapshots' */\n table?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own pool. */\n pool?: PgPool;\n /** Auto-create table on first use. @default true */\n autoCreateTable?: boolean;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: PostgresLockOptions;\n}\n\n// =============================================================================\n// postgres() - One-liner Snapshot Store Setup\n// =============================================================================\n\nconst SAFE_TABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Create a snapshot store backed by PostgreSQL.\n * Save accepts WorkflowSnapshot or ResumeState; load returns whichever was stored.\n * Use loadResumeState(id) for type-safe restore, or toResumeState(await store.load(id)).\n *\n * @example\n * ```typescript\n * import { postgres } from 'awaitly-postgres';\n * import { createWorkflow, toResumeState } from 'awaitly/workflow';\n *\n * const store = postgres('postgresql://localhost/mydb');\n * const workflow = createWorkflow(deps);\n *\n * // Run and persist resume state\n * const { result, resumeState } = await workflow.runWithState(fn);\n * await store.save('wf-123', resumeState);\n *\n * // Restore\n * const resumeState = await store.loadResumeState('wf-123');\n * if (resumeState) await workflow.run(fn, { resumeState });\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = postgres({\n * url: 'postgresql://localhost/mydb',\n * table: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockTableName: 'my_workflow_locks' },\n * });\n * ```\n */\n/** Postgres store with widened save/load for WorkflowSnapshot and ResumeState. Compatible with SnapshotStore for snapshot-only usage. */\nexport interface PostgresStore extends Partial<WorkflowLock> {\n save(id: string, state: StoreSaveInput): Promise<void>;\n load(id: string): Promise<StoreLoadResult>;\n loadResumeState(id: string): Promise<ResumeState | null>;\n delete(id: string): Promise<void>;\n list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>>;\n close(): Promise<void>;\n}\n\nexport function postgres(urlOrOptions: string | PostgresOptions): PostgresStore {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const tableName = opts.table ?? \"awaitly_snapshots\";\n\n if (!SAFE_TABLE_NAME.test(tableName)) {\n throw new Error(`Invalid table name: ${tableName}. Must be alphanumeric with underscores.`);\n }\n\n const prefix = opts.prefix ?? \"\";\n const autoCreateTable = opts.autoCreateTable ?? true;\n\n // Create or use existing pool\n const ownPool = !opts.pool;\n const pool = opts.pool ?? new PgPool({ connectionString: opts.url });\n let tableCreated = false;\n\n const ensureTable = async (): Promise<void> => {\n if (!autoCreateTable || tableCreated) return;\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${tableName} (\n id TEXT PRIMARY KEY,\n snapshot JSONB NOT NULL,\n updated_at TIMESTAMPTZ DEFAULT NOW()\n )\n `);\n await pool.query(`\n CREATE INDEX IF NOT EXISTS ${tableName}_updated_at_idx ON ${tableName} (updated_at DESC)\n `);\n tableCreated = true;\n };\n\n // Create lock if requested\n const lock = opts.lock ? createPostgresLock(pool, opts.lock) : null;\n\n const store: PostgresStore = {\n async save(id: string, state: StoreSaveInput): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n const toStore = isResumeState(state) ? serializeResumeState(state) : state;\n await pool.query(\n `INSERT INTO ${tableName} (id, snapshot, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,\n [fullId, JSON.stringify(toStore)]\n );\n },\n\n async load(id: string): Promise<StoreLoadResult> {\n await ensureTable();\n const fullId = prefix + id;\n const result = await pool.query(\n `SELECT snapshot FROM ${tableName} WHERE id = $1`,\n [fullId]\n );\n if (result.rows.length === 0) return null;\n const raw = result.rows[0].snapshot;\n if (isSerializedResumeState(raw)) return deserializeResumeState(raw);\n if (isWorkflowSnapshot(raw)) return raw;\n return raw as WorkflowSnapshot;\n },\n\n async loadResumeState(id: string): Promise<ResumeState | null> {\n const loaded = await store.load(id);\n if (loaded === null) return null;\n if (isResumeState(loaded)) return loaded;\n return null;\n },\n\n async delete(id: string): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n await pool.query(`DELETE FROM ${tableName} WHERE id = $1`, [fullId]);\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n await ensureTable();\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n\n const result = await pool.query(\n `SELECT id, updated_at FROM ${tableName}\n WHERE id LIKE $1\n ORDER BY updated_at DESC\n LIMIT $2`,\n [filterPrefix + \"%\", limit]\n );\n\n return result.rows.map(row => ({\n id: (row.id as string).slice(prefix.length),\n updatedAt: (row.updated_at as Date).toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only end pool if we created it\n if (ownPool) {\n await pool.end();\n }\n },\n };\n\n // Add lock methods if lock is configured\n if (lock) {\n store.tryAcquire = lock.tryAcquire.bind(lock);\n store.release = lock.release.bind(lock);\n store.renew = lock.renew.bind(lock);\n }\n\n return store;\n}\n","/**\n * PostgreSQL workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface PostgresLockOptions {\n /**\n * Table name for workflow locks.\n * @default 'awaitly_workflow_lock'\n */\n lockTableName?: string;\n}\n\n/**\n * Create tryAcquire and release functions that use a PostgreSQL lock table.\n * Caller must pass the same pool used for state (so one connection pool).\n */\nexport function createPostgresLock(\n pool: Pool,\n options: PostgresLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n renew(id: string, ownerToken: string, opts?: { ttlMs?: number }): Promise<boolean>;\n ensureLockTable(): Promise<void>;\n} {\n const lockTableName = options.lockTableName ?? \"awaitly_workflow_lock\";\n\n const safeIndexName = `idx_${lockTableName.replace(/[^a-zA-Z0-9_]/g, \"_\")}_expires_at`;\n\n async function ensureLockTable(): Promise<void> {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${lockTableName} (\n workflow_id TEXT PRIMARY KEY,\n owner_token TEXT NOT NULL,\n expires_at TIMESTAMPTZ NOT NULL\n );\n CREATE INDEX IF NOT EXISTS ${safeIndexName} ON ${lockTableName}(expires_at);\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 ensureLockTable();\n\n // Insert new row or update only if current row is expired (or missing).\n const result = await pool.query(\n `\n INSERT INTO ${lockTableName} (workflow_id, owner_token, expires_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (workflow_id) DO UPDATE SET\n owner_token = EXCLUDED.owner_token,\n expires_at = EXCLUDED.expires_at\n WHERE ${lockTableName}.expires_at < NOW()\n RETURNING owner_token\n `,\n [id, ownerToken, expiresAt]\n );\n\n if (result.rowCount === 1 && result.rows[0].owner_token === ownerToken) {\n return { ownerToken };\n }\n return null;\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await pool.query(\n `DELETE FROM ${lockTableName} WHERE workflow_id = $1 AND owner_token = $2`,\n [id, ownerToken]\n );\n }\n\n async function renew(\n id: string,\n ownerToken: string,\n opts?: { ttlMs?: number }\n ): Promise<boolean> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const expiresAt = new Date(Date.now() + ttlMs);\n\n const result = await pool.query(\n `UPDATE ${lockTableName} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,\n [expiresAt, id, ownerToken]\n );\n\n return (result.rowCount ?? 0) > 0;\n }\n\n return { tryAcquire, release, renew, ensureLockTable };\n}\n"],"mappings":"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","names":["index_exports","__export","postgres","__toCommonJS","import_pg","import_workflow","import_node_crypto","createPostgresLock","pool","options","lockTableName","safeIndexName","ensureLockTable","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","release","renew","SAFE_TABLE_NAME","postgres","urlOrOptions","opts","tableName","prefix","autoCreateTable","ownPool","pool","PgPool","tableCreated","ensureTable","lock","createPostgresLock","store","id","state","fullId","toStore","result","raw","loaded","options","filterPrefix","limit","row"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/postgres-lock.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by PostgreSQL.\n * Supports both WorkflowSnapshot and ResumeState (serialized via serializeResumeState).\n */\n\nimport { Pool as PgPool } from \"pg\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/persistence\";\nimport {\n type ResumeState,\n type StoreSaveInput,\n type StoreLoadResult,\n isWorkflowSnapshot,\n isResumeState,\n isSerializedResumeState,\n serializeResumeState,\n deserializeResumeState,\n} from \"awaitly/persistence\";\nimport { createPostgresLock, type PostgresLockOptions } from \"./postgres-lock\";\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/persistence\";\nexport type { PostgresLockOptions } from \"./postgres-lock\";\nexport type { StoreSaveInput, StoreLoadResult } from \"awaitly/persistence\";\n\n// =============================================================================\n// PostgresOptions\n// =============================================================================\n\n/**\n * Options for the postgres() shorthand function.\n */\nexport interface PostgresOptions {\n /** PostgreSQL connection URL. */\n url: string;\n /** Table name for snapshots. @default 'awaitly_snapshots' */\n table?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own pool. */\n pool?: PgPool;\n /** Auto-create table on first use. @default true */\n autoCreateTable?: boolean;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: PostgresLockOptions;\n}\n\n// =============================================================================\n// postgres() - One-liner Snapshot Store Setup\n// =============================================================================\n\nconst SAFE_TABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Create a snapshot store backed by PostgreSQL.\n * Save accepts WorkflowSnapshot or ResumeState; load returns whichever was stored.\n * Use loadResumeState(id) for type-safe restore, or toResumeState(await store.load(id)).\n *\n * @example\n * ```typescript\n * import { postgres } from 'awaitly-postgres';\n * import { createWorkflow } from 'awaitly/workflow';\n * import { toResumeState } from 'awaitly/persistence';\n *\n * const store = postgres('postgresql://localhost/mydb');\n * const workflow = createWorkflow(deps);\n *\n * // Run and persist resume state\n * const { result, resumeState } = await workflow.runWithState(fn);\n * await store.save('wf-123', resumeState);\n *\n * // Restore\n * const resumeState = await store.loadResumeState('wf-123');\n * if (resumeState) await workflow.run(fn, { resumeState });\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = postgres({\n * url: 'postgresql://localhost/mydb',\n * table: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockTableName: 'my_workflow_locks' },\n * });\n * ```\n */\n/** Postgres store with widened save/load for WorkflowSnapshot and ResumeState. Compatible with SnapshotStore for snapshot-only usage. */\nexport interface PostgresStore extends Partial<WorkflowLock> {\n save(id: string, state: StoreSaveInput): Promise<void>;\n load(id: string): Promise<StoreLoadResult>;\n loadResumeState(id: string): Promise<ResumeState | null>;\n delete(id: string): Promise<void>;\n list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>>;\n close(): Promise<void>;\n}\n\nexport function postgres(urlOrOptions: string | PostgresOptions): PostgresStore {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const tableName = opts.table ?? \"awaitly_snapshots\";\n\n if (!SAFE_TABLE_NAME.test(tableName)) {\n throw new Error(`Invalid table name: ${tableName}. Must be alphanumeric with underscores.`);\n }\n\n const prefix = opts.prefix ?? \"\";\n const autoCreateTable = opts.autoCreateTable ?? true;\n\n // Create or use existing pool\n const ownPool = !opts.pool;\n const pool = opts.pool ?? new PgPool({ connectionString: opts.url });\n let tableCreated = false;\n\n const ensureTable = async (): Promise<void> => {\n if (!autoCreateTable || tableCreated) return;\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${tableName} (\n id TEXT PRIMARY KEY,\n snapshot JSONB NOT NULL,\n updated_at TIMESTAMPTZ DEFAULT NOW()\n )\n `);\n await pool.query(`\n CREATE INDEX IF NOT EXISTS ${tableName}_updated_at_idx ON ${tableName} (updated_at DESC)\n `);\n tableCreated = true;\n };\n\n // Create lock if requested\n const lock = opts.lock ? createPostgresLock(pool, opts.lock) : null;\n\n const store: PostgresStore = {\n async save(id: string, state: StoreSaveInput): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n const toStore = isResumeState(state) ? serializeResumeState(state) : state;\n await pool.query(\n `INSERT INTO ${tableName} (id, snapshot, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,\n [fullId, JSON.stringify(toStore)]\n );\n },\n\n async load(id: string): Promise<StoreLoadResult> {\n await ensureTable();\n const fullId = prefix + id;\n const result = await pool.query(\n `SELECT snapshot FROM ${tableName} WHERE id = $1`,\n [fullId]\n );\n if (result.rows.length === 0) return null;\n const raw = result.rows[0].snapshot;\n if (isSerializedResumeState(raw)) return deserializeResumeState(raw);\n if (isWorkflowSnapshot(raw)) return raw;\n return raw as WorkflowSnapshot;\n },\n\n async loadResumeState(id: string): Promise<ResumeState | null> {\n const loaded = await store.load(id);\n if (loaded === null) return null;\n if (isResumeState(loaded)) return loaded;\n return null;\n },\n\n async delete(id: string): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n await pool.query(`DELETE FROM ${tableName} WHERE id = $1`, [fullId]);\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n await ensureTable();\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n\n const result = await pool.query(\n `SELECT id, updated_at FROM ${tableName}\n WHERE id LIKE $1\n ORDER BY updated_at DESC\n LIMIT $2`,\n [filterPrefix + \"%\", limit]\n );\n\n return result.rows.map(row => ({\n id: (row.id as string).slice(prefix.length),\n updatedAt: (row.updated_at as Date).toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only end pool if we created it\n if (ownPool) {\n await pool.end();\n }\n },\n };\n\n // Add lock methods if lock is configured\n if (lock) {\n store.tryAcquire = lock.tryAcquire.bind(lock);\n store.release = lock.release.bind(lock);\n store.renew = lock.renew.bind(lock);\n }\n\n return store;\n}\n","/**\n * PostgreSQL workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface PostgresLockOptions {\n /**\n * Table name for workflow locks.\n * @default 'awaitly_workflow_lock'\n */\n lockTableName?: string;\n}\n\n/**\n * Create tryAcquire and release functions that use a PostgreSQL lock table.\n * Caller must pass the same pool used for state (so one connection pool).\n */\nexport function createPostgresLock(\n pool: Pool,\n options: PostgresLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n renew(id: string, ownerToken: string, opts?: { ttlMs?: number }): Promise<boolean>;\n ensureLockTable(): Promise<void>;\n} {\n const lockTableName = options.lockTableName ?? \"awaitly_workflow_lock\";\n\n const safeIndexName = `idx_${lockTableName.replace(/[^a-zA-Z0-9_]/g, \"_\")}_expires_at`;\n\n async function ensureLockTable(): Promise<void> {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${lockTableName} (\n workflow_id TEXT PRIMARY KEY,\n owner_token TEXT NOT NULL,\n expires_at TIMESTAMPTZ NOT NULL\n );\n CREATE INDEX IF NOT EXISTS ${safeIndexName} ON ${lockTableName}(expires_at);\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 ensureLockTable();\n\n // Insert new row or update only if current row is expired (or missing).\n const result = await pool.query(\n `\n INSERT INTO ${lockTableName} (workflow_id, owner_token, expires_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (workflow_id) DO UPDATE SET\n owner_token = EXCLUDED.owner_token,\n expires_at = EXCLUDED.expires_at\n WHERE ${lockTableName}.expires_at < NOW()\n RETURNING owner_token\n `,\n [id, ownerToken, expiresAt]\n );\n\n if (result.rowCount === 1 && result.rows[0].owner_token === ownerToken) {\n return { ownerToken };\n }\n return null;\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await pool.query(\n `DELETE FROM ${lockTableName} WHERE workflow_id = $1 AND owner_token = $2`,\n [id, ownerToken]\n );\n }\n\n async function renew(\n id: string,\n ownerToken: string,\n opts?: { ttlMs?: number }\n ): Promise<boolean> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const expiresAt = new Date(Date.now() + ttlMs);\n\n const result = await pool.query(\n `UPDATE ${lockTableName} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,\n [expiresAt, id, ownerToken]\n );\n\n return (result.rowCount ?? 0) > 0;\n }\n\n return { tryAcquire, release, renew, ensureLockTable };\n}\n"],"mappings":"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","names":["index_exports","__export","postgres","__toCommonJS","import_pg","import_persistence","import_node_crypto","createPostgresLock","pool","options","lockTableName","safeIndexName","ensureLockTable","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","release","renew","SAFE_TABLE_NAME","postgres","urlOrOptions","opts","tableName","prefix","autoCreateTable","ownPool","pool","PgPool","tableCreated","ensureTable","lock","createPostgresLock","store","id","state","fullId","toStore","result","raw","loaded","options","filterPrefix","limit","row"]}
package/dist/index.d.cts CHANGED
@@ -1,9 +1,6 @@
1
1
  import { Pool } from 'pg';
2
- import { WorkflowLock } from 'awaitly/durable';
3
- export { WorkflowLock } from 'awaitly/durable';
4
- import { StoreSaveInput, StoreLoadResult, ResumeState } from 'awaitly/workflow';
5
- export { StoreLoadResult, StoreSaveInput } from 'awaitly/workflow';
6
- export { SnapshotStore, WorkflowSnapshot } from 'awaitly/persistence';
2
+ import { WorkflowLock, StoreSaveInput, StoreLoadResult, ResumeState } from 'awaitly/persistence';
3
+ export { SnapshotStore, StoreLoadResult, StoreSaveInput, WorkflowLock, WorkflowSnapshot } from 'awaitly/persistence';
7
4
 
8
5
  /**
9
6
  * PostgreSQL workflow lock (lease) for cross-process concurrency control.
@@ -51,7 +48,8 @@ interface PostgresOptions {
51
48
  * @example
52
49
  * ```typescript
53
50
  * import { postgres } from 'awaitly-postgres';
54
- * import { createWorkflow, toResumeState } from 'awaitly/workflow';
51
+ * import { createWorkflow } from 'awaitly/workflow';
52
+ * import { toResumeState } from 'awaitly/persistence';
55
53
  *
56
54
  * const store = postgres('postgresql://localhost/mydb');
57
55
  * const workflow = createWorkflow(deps);
package/dist/index.d.ts CHANGED
@@ -1,9 +1,6 @@
1
1
  import { Pool } from 'pg';
2
- import { WorkflowLock } from 'awaitly/durable';
3
- export { WorkflowLock } from 'awaitly/durable';
4
- import { StoreSaveInput, StoreLoadResult, ResumeState } from 'awaitly/workflow';
5
- export { StoreLoadResult, StoreSaveInput } from 'awaitly/workflow';
6
- export { SnapshotStore, WorkflowSnapshot } from 'awaitly/persistence';
2
+ import { WorkflowLock, StoreSaveInput, StoreLoadResult, ResumeState } from 'awaitly/persistence';
3
+ export { SnapshotStore, StoreLoadResult, StoreSaveInput, WorkflowLock, WorkflowSnapshot } from 'awaitly/persistence';
7
4
 
8
5
  /**
9
6
  * PostgreSQL workflow lock (lease) for cross-process concurrency control.
@@ -51,7 +48,8 @@ interface PostgresOptions {
51
48
  * @example
52
49
  * ```typescript
53
50
  * import { postgres } from 'awaitly-postgres';
54
- * import { createWorkflow, toResumeState } from 'awaitly/workflow';
51
+ * import { createWorkflow } from 'awaitly/workflow';
52
+ * import { toResumeState } from 'awaitly/persistence';
55
53
  *
56
54
  * const store = postgres('postgresql://localhost/mydb');
57
55
  * const workflow = createWorkflow(deps);
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import{Pool as g}from"pg";import{isWorkflowSnapshot as P,isResumeState as S,isSerializedResumeState as k,serializeResumeState as y,deserializeResumeState as _}from"awaitly/workflow";import{randomUUID as f}from"crypto";function T(u,s={}){let e=s.lockTableName??"awaitly_workflow_lock",p=`idx_${e.replace(/[^a-zA-Z0-9_]/g,"_")}_expires_at`;async function c(){await u.query(`
1
+ import{Pool as g}from"pg";import{isWorkflowSnapshot as P,isResumeState as S,isSerializedResumeState as k,serializeResumeState as y,deserializeResumeState as _}from"awaitly/persistence";import{randomUUID as f}from"crypto";function T(u,s={}){let e=s.lockTableName??"awaitly_workflow_lock",c=`idx_${e.replace(/[^a-zA-Z0-9_]/g,"_")}_expires_at`;async function d(){await u.query(`
2
2
  CREATE TABLE IF NOT EXISTS ${e} (
3
3
  workflow_id TEXT PRIMARY KEY,
4
4
  owner_token TEXT NOT NULL,
5
5
  expires_at TIMESTAMPTZ NOT NULL
6
6
  );
7
- CREATE INDEX IF NOT EXISTS ${p} ON ${e}(expires_at);
8
- `)}async function E(i,r){let l=r?.ttlMs??6e4,o=f(),t=new Date(Date.now()+l);await c();let a=await u.query(`
7
+ CREATE INDEX IF NOT EXISTS ${c} ON ${e}(expires_at);
8
+ `)}async function E(i,r){let l=r?.ttlMs??6e4,o=f(),t=new Date(Date.now()+l);await d();let a=await u.query(`
9
9
  INSERT INTO ${e} (workflow_id, owner_token, expires_at)
10
10
  VALUES ($1, $2, $3)
11
11
  ON CONFLICT (workflow_id) DO UPDATE SET
@@ -13,7 +13,7 @@ import{Pool as g}from"pg";import{isWorkflowSnapshot as P,isResumeState as S,isSe
13
13
  expires_at = EXCLUDED.expires_at
14
14
  WHERE ${e}.expires_at < NOW()
15
15
  RETURNING owner_token
16
- `,[i,o,t]);return a.rowCount===1&&a.rows[0].owner_token===o?{ownerToken:o}:null}async function n(i,r){await u.query(`DELETE FROM ${e} WHERE workflow_id = $1 AND owner_token = $2`,[i,r])}async function w(i,r,l){let o=l?.ttlMs??6e4,t=new Date(Date.now()+o);return((await u.query(`UPDATE ${e} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,[t,i,r])).rowCount??0)>0}return{tryAcquire:E,release:n,renew:w,ensureLockTable:c}}var L=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function $(u){let s=typeof u=="string"?{url:u}:u,e=s.table??"awaitly_snapshots";if(!L.test(e))throw new Error(`Invalid table name: ${e}. Must be alphanumeric with underscores.`);let p=s.prefix??"",c=s.autoCreateTable??!0,E=!s.pool,n=s.pool??new g({connectionString:s.url}),w=!1,i=async()=>{!c||w||(await n.query(`
16
+ `,[i,o,t]);return a.rowCount===1&&a.rows[0].owner_token===o?{ownerToken:o}:null}async function n(i,r){await u.query(`DELETE FROM ${e} WHERE workflow_id = $1 AND owner_token = $2`,[i,r])}async function w(i,r,l){let o=l?.ttlMs??6e4,t=new Date(Date.now()+o);return((await u.query(`UPDATE ${e} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,[t,i,r])).rowCount??0)>0}return{tryAcquire:E,release:n,renew:w,ensureLockTable:d}}var L=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function $(u){let s=typeof u=="string"?{url:u}:u,e=s.table??"awaitly_snapshots";if(!L.test(e))throw new Error(`Invalid table name: ${e}. Must be alphanumeric with underscores.`);let c=s.prefix??"",d=s.autoCreateTable??!0,E=!s.pool,n=s.pool??new g({connectionString:s.url}),w=!1,i=async()=>{!d||w||(await n.query(`
17
17
  CREATE TABLE IF NOT EXISTS ${e} (
18
18
  id TEXT PRIMARY KEY,
19
19
  snapshot JSONB NOT NULL,
@@ -21,10 +21,10 @@ import{Pool as g}from"pg";import{isWorkflowSnapshot as P,isResumeState as S,isSe
21
21
  )
22
22
  `),await n.query(`
23
23
  CREATE INDEX IF NOT EXISTS ${e}_updated_at_idx ON ${e} (updated_at DESC)
24
- `),w=!0)},r=s.lock?T(n,s.lock):null,l={async save(o,t){await i();let a=p+o,d=S(t)?y(t):t;await n.query(`INSERT INTO ${e} (id, snapshot, updated_at)
24
+ `),w=!0)},r=s.lock?T(n,s.lock):null,l={async save(o,t){await i();let a=c+o,p=S(t)?y(t):t;await n.query(`INSERT INTO ${e} (id, snapshot, updated_at)
25
25
  VALUES ($1, $2, NOW())
26
- ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,[a,JSON.stringify(d)])},async load(o){await i();let t=p+o,a=await n.query(`SELECT snapshot FROM ${e} WHERE id = $1`,[t]);if(a.rows.length===0)return null;let d=a.rows[0].snapshot;return k(d)?_(d):(P(d),d)},async loadResumeState(o){let t=await l.load(o);return t===null?null:S(t)?t:null},async delete(o){await i();let t=p+o;await n.query(`DELETE FROM ${e} WHERE id = $1`,[t])},async list(o){await i();let t=p+(o?.prefix??""),a=o?.limit??100;return(await n.query(`SELECT id, updated_at FROM ${e}
26
+ ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,[a,JSON.stringify(p)])},async load(o){await i();let t=c+o,a=await n.query(`SELECT snapshot FROM ${e} WHERE id = $1`,[t]);if(a.rows.length===0)return null;let p=a.rows[0].snapshot;return k(p)?_(p):(P(p),p)},async loadResumeState(o){let t=await l.load(o);return t===null?null:S(t)?t:null},async delete(o){await i();let t=c+o;await n.query(`DELETE FROM ${e} WHERE id = $1`,[t])},async list(o){await i();let t=c+(o?.prefix??""),a=o?.limit??100;return(await n.query(`SELECT id, updated_at FROM ${e}
27
27
  WHERE id LIKE $1
28
28
  ORDER BY updated_at DESC
29
- LIMIT $2`,[t+"%",a])).rows.map(m=>({id:m.id.slice(p.length),updatedAt:m.updated_at.toISOString()}))},async close(){E&&await n.end()}};return r&&(l.tryAcquire=r.tryAcquire.bind(r),l.release=r.release.bind(r),l.renew=r.renew.bind(r)),l}export{$ as postgres};
29
+ LIMIT $2`,[t+"%",a])).rows.map(m=>({id:m.id.slice(c.length),updatedAt:m.updated_at.toISOString()}))},async close(){E&&await n.end()}};return r&&(l.tryAcquire=r.tryAcquire.bind(r),l.release=r.release.bind(r),l.renew=r.renew.bind(r)),l}export{$ as postgres};
30
30
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/postgres-lock.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by PostgreSQL.\n * Supports both WorkflowSnapshot and ResumeState (serialized via serializeResumeState).\n */\n\nimport { Pool as PgPool } from \"pg\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\nimport {\n type ResumeState,\n type StoreSaveInput,\n type StoreLoadResult,\n isWorkflowSnapshot,\n isResumeState,\n isSerializedResumeState,\n serializeResumeState,\n deserializeResumeState,\n} from \"awaitly/workflow\";\nimport { createPostgresLock, type PostgresLockOptions } from \"./postgres-lock\";\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/durable\";\nexport type { PostgresLockOptions } from \"./postgres-lock\";\nexport type { StoreSaveInput, StoreLoadResult } from \"awaitly/workflow\";\n\n// =============================================================================\n// PostgresOptions\n// =============================================================================\n\n/**\n * Options for the postgres() shorthand function.\n */\nexport interface PostgresOptions {\n /** PostgreSQL connection URL. */\n url: string;\n /** Table name for snapshots. @default 'awaitly_snapshots' */\n table?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own pool. */\n pool?: PgPool;\n /** Auto-create table on first use. @default true */\n autoCreateTable?: boolean;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: PostgresLockOptions;\n}\n\n// =============================================================================\n// postgres() - One-liner Snapshot Store Setup\n// =============================================================================\n\nconst SAFE_TABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Create a snapshot store backed by PostgreSQL.\n * Save accepts WorkflowSnapshot or ResumeState; load returns whichever was stored.\n * Use loadResumeState(id) for type-safe restore, or toResumeState(await store.load(id)).\n *\n * @example\n * ```typescript\n * import { postgres } from 'awaitly-postgres';\n * import { createWorkflow, toResumeState } from 'awaitly/workflow';\n *\n * const store = postgres('postgresql://localhost/mydb');\n * const workflow = createWorkflow(deps);\n *\n * // Run and persist resume state\n * const { result, resumeState } = await workflow.runWithState(fn);\n * await store.save('wf-123', resumeState);\n *\n * // Restore\n * const resumeState = await store.loadResumeState('wf-123');\n * if (resumeState) await workflow.run(fn, { resumeState });\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = postgres({\n * url: 'postgresql://localhost/mydb',\n * table: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockTableName: 'my_workflow_locks' },\n * });\n * ```\n */\n/** Postgres store with widened save/load for WorkflowSnapshot and ResumeState. Compatible with SnapshotStore for snapshot-only usage. */\nexport interface PostgresStore extends Partial<WorkflowLock> {\n save(id: string, state: StoreSaveInput): Promise<void>;\n load(id: string): Promise<StoreLoadResult>;\n loadResumeState(id: string): Promise<ResumeState | null>;\n delete(id: string): Promise<void>;\n list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>>;\n close(): Promise<void>;\n}\n\nexport function postgres(urlOrOptions: string | PostgresOptions): PostgresStore {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const tableName = opts.table ?? \"awaitly_snapshots\";\n\n if (!SAFE_TABLE_NAME.test(tableName)) {\n throw new Error(`Invalid table name: ${tableName}. Must be alphanumeric with underscores.`);\n }\n\n const prefix = opts.prefix ?? \"\";\n const autoCreateTable = opts.autoCreateTable ?? true;\n\n // Create or use existing pool\n const ownPool = !opts.pool;\n const pool = opts.pool ?? new PgPool({ connectionString: opts.url });\n let tableCreated = false;\n\n const ensureTable = async (): Promise<void> => {\n if (!autoCreateTable || tableCreated) return;\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${tableName} (\n id TEXT PRIMARY KEY,\n snapshot JSONB NOT NULL,\n updated_at TIMESTAMPTZ DEFAULT NOW()\n )\n `);\n await pool.query(`\n CREATE INDEX IF NOT EXISTS ${tableName}_updated_at_idx ON ${tableName} (updated_at DESC)\n `);\n tableCreated = true;\n };\n\n // Create lock if requested\n const lock = opts.lock ? createPostgresLock(pool, opts.lock) : null;\n\n const store: PostgresStore = {\n async save(id: string, state: StoreSaveInput): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n const toStore = isResumeState(state) ? serializeResumeState(state) : state;\n await pool.query(\n `INSERT INTO ${tableName} (id, snapshot, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,\n [fullId, JSON.stringify(toStore)]\n );\n },\n\n async load(id: string): Promise<StoreLoadResult> {\n await ensureTable();\n const fullId = prefix + id;\n const result = await pool.query(\n `SELECT snapshot FROM ${tableName} WHERE id = $1`,\n [fullId]\n );\n if (result.rows.length === 0) return null;\n const raw = result.rows[0].snapshot;\n if (isSerializedResumeState(raw)) return deserializeResumeState(raw);\n if (isWorkflowSnapshot(raw)) return raw;\n return raw as WorkflowSnapshot;\n },\n\n async loadResumeState(id: string): Promise<ResumeState | null> {\n const loaded = await store.load(id);\n if (loaded === null) return null;\n if (isResumeState(loaded)) return loaded;\n return null;\n },\n\n async delete(id: string): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n await pool.query(`DELETE FROM ${tableName} WHERE id = $1`, [fullId]);\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n await ensureTable();\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n\n const result = await pool.query(\n `SELECT id, updated_at FROM ${tableName}\n WHERE id LIKE $1\n ORDER BY updated_at DESC\n LIMIT $2`,\n [filterPrefix + \"%\", limit]\n );\n\n return result.rows.map(row => ({\n id: (row.id as string).slice(prefix.length),\n updatedAt: (row.updated_at as Date).toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only end pool if we created it\n if (ownPool) {\n await pool.end();\n }\n },\n };\n\n // Add lock methods if lock is configured\n if (lock) {\n store.tryAcquire = lock.tryAcquire.bind(lock);\n store.release = lock.release.bind(lock);\n store.renew = lock.renew.bind(lock);\n }\n\n return store;\n}\n","/**\n * PostgreSQL workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface PostgresLockOptions {\n /**\n * Table name for workflow locks.\n * @default 'awaitly_workflow_lock'\n */\n lockTableName?: string;\n}\n\n/**\n * Create tryAcquire and release functions that use a PostgreSQL lock table.\n * Caller must pass the same pool used for state (so one connection pool).\n */\nexport function createPostgresLock(\n pool: Pool,\n options: PostgresLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n renew(id: string, ownerToken: string, opts?: { ttlMs?: number }): Promise<boolean>;\n ensureLockTable(): Promise<void>;\n} {\n const lockTableName = options.lockTableName ?? \"awaitly_workflow_lock\";\n\n const safeIndexName = `idx_${lockTableName.replace(/[^a-zA-Z0-9_]/g, \"_\")}_expires_at`;\n\n async function ensureLockTable(): Promise<void> {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${lockTableName} (\n workflow_id TEXT PRIMARY KEY,\n owner_token TEXT NOT NULL,\n expires_at TIMESTAMPTZ NOT NULL\n );\n CREATE INDEX IF NOT EXISTS ${safeIndexName} ON ${lockTableName}(expires_at);\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 ensureLockTable();\n\n // Insert new row or update only if current row is expired (or missing).\n const result = await pool.query(\n `\n INSERT INTO ${lockTableName} (workflow_id, owner_token, expires_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (workflow_id) DO UPDATE SET\n owner_token = EXCLUDED.owner_token,\n expires_at = EXCLUDED.expires_at\n WHERE ${lockTableName}.expires_at < NOW()\n RETURNING owner_token\n `,\n [id, ownerToken, expiresAt]\n );\n\n if (result.rowCount === 1 && result.rows[0].owner_token === ownerToken) {\n return { ownerToken };\n }\n return null;\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await pool.query(\n `DELETE FROM ${lockTableName} WHERE workflow_id = $1 AND owner_token = $2`,\n [id, ownerToken]\n );\n }\n\n async function renew(\n id: string,\n ownerToken: string,\n opts?: { ttlMs?: number }\n ): Promise<boolean> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const expiresAt = new Date(Date.now() + ttlMs);\n\n const result = await pool.query(\n `UPDATE ${lockTableName} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,\n [expiresAt, id, ownerToken]\n );\n\n return (result.rowCount ?? 0) > 0;\n }\n\n return { tryAcquire, release, renew, ensureLockTable };\n}\n"],"mappings":"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","names":["PgPool","isWorkflowSnapshot","isResumeState","isSerializedResumeState","serializeResumeState","deserializeResumeState","randomUUID","createPostgresLock","pool","options","lockTableName","safeIndexName","ensureLockTable","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","release","renew","SAFE_TABLE_NAME","postgres","urlOrOptions","opts","tableName","prefix","autoCreateTable","ownPool","pool","PgPool","tableCreated","ensureTable","lock","createPostgresLock","store","id","state","fullId","toStore","isResumeState","serializeResumeState","result","raw","isSerializedResumeState","deserializeResumeState","isWorkflowSnapshot","loaded","options","filterPrefix","limit","row"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/postgres-lock.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use SnapshotStore backed by PostgreSQL.\n * Supports both WorkflowSnapshot and ResumeState (serialized via serializeResumeState).\n */\n\nimport { Pool as PgPool } from \"pg\";\nimport type { WorkflowSnapshot, SnapshotStore } from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/persistence\";\nimport {\n type ResumeState,\n type StoreSaveInput,\n type StoreLoadResult,\n isWorkflowSnapshot,\n isResumeState,\n isSerializedResumeState,\n serializeResumeState,\n deserializeResumeState,\n} from \"awaitly/persistence\";\nimport { createPostgresLock, type PostgresLockOptions } from \"./postgres-lock\";\n\n// Re-export types for convenience\nexport type { SnapshotStore, WorkflowSnapshot } from \"awaitly/persistence\";\nexport type { WorkflowLock } from \"awaitly/persistence\";\nexport type { PostgresLockOptions } from \"./postgres-lock\";\nexport type { StoreSaveInput, StoreLoadResult } from \"awaitly/persistence\";\n\n// =============================================================================\n// PostgresOptions\n// =============================================================================\n\n/**\n * Options for the postgres() shorthand function.\n */\nexport interface PostgresOptions {\n /** PostgreSQL connection URL. */\n url: string;\n /** Table name for snapshots. @default 'awaitly_snapshots' */\n table?: string;\n /** Key prefix for IDs. @default '' */\n prefix?: string;\n /** Bring your own pool. */\n pool?: PgPool;\n /** Auto-create table on first use. @default true */\n autoCreateTable?: boolean;\n /** Cross-process lock options. When set, the store implements WorkflowLock. */\n lock?: PostgresLockOptions;\n}\n\n// =============================================================================\n// postgres() - One-liner Snapshot Store Setup\n// =============================================================================\n\nconst SAFE_TABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Create a snapshot store backed by PostgreSQL.\n * Save accepts WorkflowSnapshot or ResumeState; load returns whichever was stored.\n * Use loadResumeState(id) for type-safe restore, or toResumeState(await store.load(id)).\n *\n * @example\n * ```typescript\n * import { postgres } from 'awaitly-postgres';\n * import { createWorkflow } from 'awaitly/workflow';\n * import { toResumeState } from 'awaitly/persistence';\n *\n * const store = postgres('postgresql://localhost/mydb');\n * const workflow = createWorkflow(deps);\n *\n * // Run and persist resume state\n * const { result, resumeState } = await workflow.runWithState(fn);\n * await store.save('wf-123', resumeState);\n *\n * // Restore\n * const resumeState = await store.loadResumeState('wf-123');\n * if (resumeState) await workflow.run(fn, { resumeState });\n * ```\n *\n * @example\n * ```typescript\n * // With options including cross-process locking\n * const store = postgres({\n * url: 'postgresql://localhost/mydb',\n * table: 'my_workflow_snapshots',\n * prefix: 'orders:',\n * lock: { lockTableName: 'my_workflow_locks' },\n * });\n * ```\n */\n/** Postgres store with widened save/load for WorkflowSnapshot and ResumeState. Compatible with SnapshotStore for snapshot-only usage. */\nexport interface PostgresStore extends Partial<WorkflowLock> {\n save(id: string, state: StoreSaveInput): Promise<void>;\n load(id: string): Promise<StoreLoadResult>;\n loadResumeState(id: string): Promise<ResumeState | null>;\n delete(id: string): Promise<void>;\n list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>>;\n close(): Promise<void>;\n}\n\nexport function postgres(urlOrOptions: string | PostgresOptions): PostgresStore {\n const opts = typeof urlOrOptions === \"string\" ? { url: urlOrOptions } : urlOrOptions;\n const tableName = opts.table ?? \"awaitly_snapshots\";\n\n if (!SAFE_TABLE_NAME.test(tableName)) {\n throw new Error(`Invalid table name: ${tableName}. Must be alphanumeric with underscores.`);\n }\n\n const prefix = opts.prefix ?? \"\";\n const autoCreateTable = opts.autoCreateTable ?? true;\n\n // Create or use existing pool\n const ownPool = !opts.pool;\n const pool = opts.pool ?? new PgPool({ connectionString: opts.url });\n let tableCreated = false;\n\n const ensureTable = async (): Promise<void> => {\n if (!autoCreateTable || tableCreated) return;\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${tableName} (\n id TEXT PRIMARY KEY,\n snapshot JSONB NOT NULL,\n updated_at TIMESTAMPTZ DEFAULT NOW()\n )\n `);\n await pool.query(`\n CREATE INDEX IF NOT EXISTS ${tableName}_updated_at_idx ON ${tableName} (updated_at DESC)\n `);\n tableCreated = true;\n };\n\n // Create lock if requested\n const lock = opts.lock ? createPostgresLock(pool, opts.lock) : null;\n\n const store: PostgresStore = {\n async save(id: string, state: StoreSaveInput): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n const toStore = isResumeState(state) ? serializeResumeState(state) : state;\n await pool.query(\n `INSERT INTO ${tableName} (id, snapshot, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (id) DO UPDATE SET snapshot = $2, updated_at = NOW()`,\n [fullId, JSON.stringify(toStore)]\n );\n },\n\n async load(id: string): Promise<StoreLoadResult> {\n await ensureTable();\n const fullId = prefix + id;\n const result = await pool.query(\n `SELECT snapshot FROM ${tableName} WHERE id = $1`,\n [fullId]\n );\n if (result.rows.length === 0) return null;\n const raw = result.rows[0].snapshot;\n if (isSerializedResumeState(raw)) return deserializeResumeState(raw);\n if (isWorkflowSnapshot(raw)) return raw;\n return raw as WorkflowSnapshot;\n },\n\n async loadResumeState(id: string): Promise<ResumeState | null> {\n const loaded = await store.load(id);\n if (loaded === null) return null;\n if (isResumeState(loaded)) return loaded;\n return null;\n },\n\n async delete(id: string): Promise<void> {\n await ensureTable();\n const fullId = prefix + id;\n await pool.query(`DELETE FROM ${tableName} WHERE id = $1`, [fullId]);\n },\n\n async list(options?: { prefix?: string; limit?: number }): Promise<Array<{ id: string; updatedAt: string }>> {\n await ensureTable();\n const filterPrefix = prefix + (options?.prefix ?? \"\");\n const limit = options?.limit ?? 100;\n\n const result = await pool.query(\n `SELECT id, updated_at FROM ${tableName}\n WHERE id LIKE $1\n ORDER BY updated_at DESC\n LIMIT $2`,\n [filterPrefix + \"%\", limit]\n );\n\n return result.rows.map(row => ({\n id: (row.id as string).slice(prefix.length),\n updatedAt: (row.updated_at as Date).toISOString(),\n }));\n },\n\n async close(): Promise<void> {\n // Only end pool if we created it\n if (ownPool) {\n await pool.end();\n }\n },\n };\n\n // Add lock methods if lock is configured\n if (lock) {\n store.tryAcquire = lock.tryAcquire.bind(lock);\n store.release = lock.release.bind(lock);\n store.renew = lock.renew.bind(lock);\n }\n\n return store;\n}\n","/**\n * PostgreSQL workflow lock (lease) for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n\nimport type { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface PostgresLockOptions {\n /**\n * Table name for workflow locks.\n * @default 'awaitly_workflow_lock'\n */\n lockTableName?: string;\n}\n\n/**\n * Create tryAcquire and release functions that use a PostgreSQL lock table.\n * Caller must pass the same pool used for state (so one connection pool).\n */\nexport function createPostgresLock(\n pool: Pool,\n options: PostgresLockOptions = {}\n): {\n tryAcquire(\n id: string,\n opts?: { ttlMs?: number }\n ): Promise<{ ownerToken: string } | null>;\n release(id: string, ownerToken: string): Promise<void>;\n renew(id: string, ownerToken: string, opts?: { ttlMs?: number }): Promise<boolean>;\n ensureLockTable(): Promise<void>;\n} {\n const lockTableName = options.lockTableName ?? \"awaitly_workflow_lock\";\n\n const safeIndexName = `idx_${lockTableName.replace(/[^a-zA-Z0-9_]/g, \"_\")}_expires_at`;\n\n async function ensureLockTable(): Promise<void> {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${lockTableName} (\n workflow_id TEXT PRIMARY KEY,\n owner_token TEXT NOT NULL,\n expires_at TIMESTAMPTZ NOT NULL\n );\n CREATE INDEX IF NOT EXISTS ${safeIndexName} ON ${lockTableName}(expires_at);\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 ensureLockTable();\n\n // Insert new row or update only if current row is expired (or missing).\n const result = await pool.query(\n `\n INSERT INTO ${lockTableName} (workflow_id, owner_token, expires_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (workflow_id) DO UPDATE SET\n owner_token = EXCLUDED.owner_token,\n expires_at = EXCLUDED.expires_at\n WHERE ${lockTableName}.expires_at < NOW()\n RETURNING owner_token\n `,\n [id, ownerToken, expiresAt]\n );\n\n if (result.rowCount === 1 && result.rows[0].owner_token === ownerToken) {\n return { ownerToken };\n }\n return null;\n }\n\n async function release(id: string, ownerToken: string): Promise<void> {\n await pool.query(\n `DELETE FROM ${lockTableName} WHERE workflow_id = $1 AND owner_token = $2`,\n [id, ownerToken]\n );\n }\n\n async function renew(\n id: string,\n ownerToken: string,\n opts?: { ttlMs?: number }\n ): Promise<boolean> {\n const ttlMs = opts?.ttlMs ?? 60_000;\n const expiresAt = new Date(Date.now() + ttlMs);\n\n const result = await pool.query(\n `UPDATE ${lockTableName} SET expires_at = $1 WHERE workflow_id = $2 AND owner_token = $3`,\n [expiresAt, id, ownerToken]\n );\n\n return (result.rowCount ?? 0) > 0;\n }\n\n return { tryAcquire, release, renew, ensureLockTable };\n}\n"],"mappings":"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","names":["PgPool","isWorkflowSnapshot","isResumeState","isSerializedResumeState","serializeResumeState","deserializeResumeState","randomUUID","createPostgresLock","pool","options","lockTableName","safeIndexName","ensureLockTable","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","release","renew","SAFE_TABLE_NAME","postgres","urlOrOptions","opts","tableName","prefix","autoCreateTable","ownPool","pool","PgPool","tableCreated","ensureTable","lock","createPostgresLock","store","id","state","fullId","toStore","isResumeState","serializeResumeState","result","raw","isSerializedResumeState","deserializeResumeState","isWorkflowSnapshot","loaded","options","filterPrefix","limit","row"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awaitly-postgres",
3
- "version": "25.0.0",
3
+ "version": "27.0.0",
4
4
  "type": "module",
5
5
  "description": "PostgreSQL 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.35.0"
41
+ "awaitly": "^3.0.0"
42
42
  },
43
43
  "dependencies": {
44
44
  "pg": "8.21.0"
@@ -50,8 +50,8 @@
50
50
  "@types/pg": "8.20.0",
51
51
  "tsup": "8.5.1",
52
52
  "typescript": "5.9.3",
53
- "vitest": "4.1.9",
54
- "awaitly": "^1.35.0"
53
+ "vitest": "4.1.10",
54
+ "awaitly": "^3.0.0"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public",