awaitly-postgres 1.0.0 → 3.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,35 +1,65 @@
1
- "use strict";var n=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var g=(r,e)=>{for(var t in e)n(r,t,{get:e[t],enumerable:!0})},P=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of c(e))!y.call(r,s)&&s!==t&&n(r,s,{get:()=>e[s],enumerable:!(i=p(e,s))||i.enumerable});return r};var m=r=>P(n({},"__esModule",{value:!0}),r);var E={};g(E,{PostgresKeyValueStore:()=>a,createPostgresPersistence:()=>h});module.exports=m(E);var l=require("pg"),a=class{pool;tableName;initialized=!1;initPromise=null;constructor(e){e.existingPool?this.pool=e.existingPool:e.connectionString?this.pool=new l.Pool({connectionString:e.connectionString,...e.pool}):this.pool=new l.Pool({host:e.host??"localhost",port:e.port??5432,database:e.database,user:e.user,password:e.password,...e.pool}),this.tableName=e.tableName??"awaitly_workflow_state"}async ensureInitialized(){if(!this.initialized)return this.initPromise?this.initPromise:(this.initPromise=(async()=>{try{await this.createTable(),this.initialized=!0}catch(e){throw this.initPromise=null,e}})(),this.initPromise)}async createTable(){let e=`
1
+ "use strict";var N=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var I=(o,e)=>{for(var t in e)N(o,t,{get:e[t],enumerable:!0})},b=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _(e))!x.call(o,i)&&i!==t&&N(o,i,{get:()=>e[i],enumerable:!(s=O(e,i))||s.enumerable});return o};var R=o=>b(N({},"__esModule",{value:!0}),o);var A={};I(A,{PostgresKeyValueStore:()=>P,createPostgresPersistence:()=>$});module.exports=R(A);var f=require("pg");var h=require("pg"),P=class{pool;tableName;initialized=!1;initPromise=null;constructor(e){e.existingPool?this.pool=e.existingPool:e.connectionString?this.pool=new h.Pool({connectionString:e.connectionString,...e.pool}):this.pool=new h.Pool({host:e.host??"localhost",port:e.port??5432,database:e.database,user:e.user,password:e.password,...e.pool}),this.tableName=e.tableName??"awaitly_workflow_state"}async ensureInitialized(){if(!this.initialized)return this.initPromise?this.initPromise:(this.initPromise=(async()=>{try{await this.createTable(),this.initialized=!0}catch(e){throw this.initPromise=null,e}})(),this.initPromise)}async createTable(){await this.pool.query(`
2
2
  CREATE TABLE IF NOT EXISTS ${this.tableName} (
3
3
  key TEXT PRIMARY KEY,
4
4
  value TEXT NOT NULL,
5
- expires_at TIMESTAMP
6
- );
7
-
5
+ expires_at TIMESTAMP,
6
+ updated_at TIMESTAMPTZ
7
+ )
8
+ `),await this.pool.query(`
9
+ ALTER TABLE ${this.tableName}
10
+ ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ
11
+ `),await this.pool.query(`
8
12
  CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at
9
13
  ON ${this.tableName}(expires_at)
10
- WHERE expires_at IS NOT NULL;
11
- `;await this.pool.query(e)}patternToLike(e){return e.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%")}async get(e){await this.ensureInitialized();let t=`
14
+ WHERE expires_at IS NOT NULL
15
+ `),await this.pool.query(`
16
+ CREATE INDEX IF NOT EXISTS idx_${this.tableName}_updated_at
17
+ ON ${this.tableName}(updated_at)
18
+ WHERE updated_at IS NOT NULL
19
+ `)}patternToLike(e){return e.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%")}async get(e){await this.ensureInitialized();let t=`
12
20
  SELECT value
13
21
  FROM ${this.tableName}
14
22
  WHERE key = $1
15
23
  AND (expires_at IS NULL OR expires_at > NOW())
16
- `,i=await this.pool.query(t,[e]);return i.rows.length===0?null:i.rows[0].value}async set(e,t,i){await this.ensureInitialized();let s=i?.ttl?new Date(Date.now()+i.ttl*1e3):null,o=`
17
- INSERT INTO ${this.tableName} (key, value, expires_at)
18
- VALUES ($1, $2, $3)
24
+ `,s=await this.pool.query(t,[e]);return s.rows.length===0?null:s.rows[0].value}async set(e,t,s){await this.ensureInitialized();let i=s?.ttl?new Date(Date.now()+s.ttl*1e3):null,n=`
25
+ INSERT INTO ${this.tableName} (key, value, expires_at, updated_at)
26
+ VALUES ($1, $2, $3, NOW())
19
27
  ON CONFLICT (key)
20
28
  DO UPDATE SET
21
29
  value = EXCLUDED.value,
22
- expires_at = EXCLUDED.expires_at
23
- `;await this.pool.query(o,[e,t,s])}async delete(e){await this.ensureInitialized();let t=`DELETE FROM ${this.tableName} WHERE key = $1`;return((await this.pool.query(t,[e])).rowCount??0)>0}async exists(e){await this.ensureInitialized();let t=`
30
+ expires_at = EXCLUDED.expires_at,
31
+ updated_at = NOW()
32
+ `;await this.pool.query(n,[e,t,i])}async delete(e){await this.ensureInitialized();let t=`DELETE FROM ${this.tableName} WHERE key = $1`;return((await this.pool.query(t,[e])).rowCount??0)>0}async exists(e){await this.ensureInitialized();let t=`
24
33
  SELECT 1
25
34
  FROM ${this.tableName}
26
35
  WHERE key = $1
27
36
  AND (expires_at IS NULL OR expires_at > NOW())
28
37
  LIMIT 1
29
- `;return(await this.pool.query(t,[e])).rows.length>0}async keys(e){await this.ensureInitialized();let t=this.patternToLike(e),i=`
38
+ `;return(await this.pool.query(t,[e])).rows.length>0}async keys(e){await this.ensureInitialized();let t=this.patternToLike(e),s=`
30
39
  SELECT key
31
40
  FROM ${this.tableName}
32
41
  WHERE key LIKE $1
33
42
  AND (expires_at IS NULL OR expires_at > NOW())
34
- `;return(await this.pool.query(i,[t])).rows.map(o=>o.key)}async close(){await this.pool.end()}};var u=require("awaitly/persistence");async function h(r={}){let{prefix:e,...t}=r,i=new a(t);return(0,u.createStatePersistence)(i,e)}0&&(module.exports={PostgresKeyValueStore,createPostgresPersistence});
43
+ `;return(await this.pool.query(s,[t])).rows.map(n=>n.key)}async listKeys(e,t={}){await this.ensureInitialized();let s=Math.min(Math.max(0,t.limit??100),1e4),i=Math.max(0,t.offset??0),n=t.orderBy==="key"?"key":"updated_at",g=t.orderDir==="asc"?"ASC":"DESC",p=this.patternToLike(e),u=["key LIKE $1","(expires_at IS NULL OR expires_at > NOW())"],d=[p],r=2;t.updatedBefore!=null&&(u.push(`updated_at < $${r}`),d.push(t.updatedBefore),r++),t.updatedAfter!=null&&(u.push(`updated_at > $${r}`),d.push(t.updatedAfter),r++);let l=u.join(" AND "),a=n==="updated_at"?" NULLS LAST":"",c=`
44
+ SELECT key
45
+ FROM ${this.tableName}
46
+ WHERE ${l}
47
+ ORDER BY ${n} ${g}${a}
48
+ LIMIT $${r} OFFSET $${r+1}
49
+ `,E=[...d,s,i],w=(await this.pool.query(c,E)).rows.map(T=>T.key),y;if(t.includeTotal===!0||i>0){let T=await this.pool.query(`SELECT COUNT(*) AS count FROM ${this.tableName} WHERE ${l}`,d);y=parseInt(T.rows[0]?.count??"0",10)}return{keys:w,total:y}}async deleteMany(e){return e.length===0?0:(await this.ensureInitialized(),(await this.pool.query(`DELETE FROM ${this.tableName} WHERE key = ANY($1::text[])`,[e])).rowCount??0)}async clear(){await this.ensureInitialized(),await this.pool.query(`TRUNCATE TABLE ${this.tableName}`)}async close(){await this.pool.end()}};var k=require("crypto");function L(o,e={}){let t=e.lockTableName??"awaitly_workflow_lock",s=`idx_${t.replace(/[^a-zA-Z0-9_]/g,"_")}_expires_at`;async function i(){await o.query(`
50
+ CREATE TABLE IF NOT EXISTS ${t} (
51
+ workflow_id TEXT PRIMARY KEY,
52
+ owner_token TEXT NOT NULL,
53
+ expires_at TIMESTAMPTZ NOT NULL
54
+ );
55
+ CREATE INDEX IF NOT EXISTS ${s} ON ${t}(expires_at);
56
+ `)}async function n(p,u){let d=u?.ttlMs??6e4,r=(0,k.randomUUID)(),l=new Date(Date.now()+d);await i();let a=await o.query(`
57
+ INSERT INTO ${t} (workflow_id, owner_token, expires_at)
58
+ VALUES ($1, $2, $3)
59
+ ON CONFLICT (workflow_id) DO UPDATE SET
60
+ owner_token = EXCLUDED.owner_token,
61
+ expires_at = EXCLUDED.expires_at
62
+ WHERE ${t}.expires_at < NOW()
63
+ RETURNING owner_token
64
+ `,[p,r,l]);return a.rowCount===1&&a.rows[0].owner_token===r?{ownerToken:r}:null}async function g(p,u){await o.query(`DELETE FROM ${t} WHERE workflow_id = $1 AND owner_token = $2`,[p,u])}return{tryAcquire:n,release:g,ensureLockTable:i}}var S=require("awaitly/persistence");async function $(o={}){let{prefix:e,lock:t,...s}=o,i=r=>r.slice((e??"workflow:state:").length),n=e??"workflow:state:",g=r=>`${n}${r}`,p=(r,l)=>Object.assign(r,{async listPage(a={}){let{keys:c,total:E}=await l.listKeys(`${n}*`,a),m=c.map(i),w=Math.min(Math.max(0,a.limit??100),1e4),y=m.length===w?(a.offset??0)+m.length:void 0;return{ids:m,total:E,nextOffset:y}},async deleteMany(a){if(a.length===0)return 0;let c=a.map(g);return l.deleteMany(c)},async clear(){return l.clear()}});if(t!==void 0){let r=s.existingPool??new f.Pool(s.connectionString?{connectionString:s.connectionString,...s.pool}:{host:s.host??"localhost",port:s.port??5432,database:s.database,user:s.user,password:s.password,...s.pool}),l=new P({...s,existingPool:r}),a=(0,S.createStatePersistence)(l,e),c=L(r,{lockTableName:t.lockTableName});return Object.assign(p(a,l),{tryAcquire:c.tryAcquire.bind(c),release:c.release.bind(c)})}let u=new P(s),d=(0,S.createStatePersistence)(u,e);return p(d,u)}0&&(module.exports={PostgresKeyValueStore,createPostgresPersistence});
35
65
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/postgres-store.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use StatePersistence backed by PostgreSQL.\n */\n\nimport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions } from \"./postgres-store\";\nimport { createStatePersistence, type StatePersistence } from \"awaitly/persistence\";\n\n/**\n * Options for creating PostgreSQL persistence.\n */\nexport interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {\n /**\n * Key prefix for state entries.\n * @default 'workflow:state:'\n */\n prefix?: string;\n}\n\n/**\n * Create a StatePersistence instance backed by PostgreSQL.\n *\n * The table is automatically created on first use.\n *\n * @param options - PostgreSQL connection and configuration options\n * @returns StatePersistence instance ready to use with durable.run()\n *\n * @example\n * ```typescript\n * import { createPostgresPersistence } from 'awaitly-postgres';\n * import { durable } from 'awaitly/durable';\n *\n * const store = await createPostgresPersistence({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const result = await durable.run(\n * { fetchUser, createOrder },\n * async (step, { fetchUser, createOrder }) => {\n * const user = await step(() => fetchUser('123'), { key: 'fetch-user' });\n * const order = await step(() => createOrder(user), { key: 'create-order' });\n * return order;\n * },\n * {\n * id: 'checkout-123',\n * store,\n * }\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Using individual connection options\n * const store = await createPostgresPersistence({\n * host: 'localhost',\n * port: 5432,\n * database: 'myapp',\n * user: 'postgres',\n * password: 'password',\n * tableName: 'custom_workflow_state',\n * });\n * ```\n */\nexport async function createPostgresPersistence(\n options: PostgresPersistenceOptions = {}\n): Promise<StatePersistence & { loadRaw(runId: string): Promise<import(\"awaitly/persistence\").SerializedState | undefined> }> {\n const { prefix, ...storeOptions } = options;\n\n const store = new PostgresKeyValueStore(storeOptions);\n return createStatePersistence(store, prefix);\n}\n\n/**\n * PostgreSQL KeyValueStore implementation.\n * Use this directly if you need more control over the store.\n *\n * @example\n * ```typescript\n * import { PostgresKeyValueStore } from 'awaitly-postgres';\n * import { createStatePersistence } from 'awaitly/persistence';\n *\n * const store = new PostgresKeyValueStore({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const persistence = createStatePersistence(store, 'custom:prefix:');\n * ```\n */\nexport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions };\n","/**\n * awaitly-postgres\n *\n * PostgreSQL KeyValueStore implementation for awaitly persistence.\n */\n\nimport type { Pool, PoolConfig, QueryResult } from \"pg\";\nimport { Pool as PgPool } from \"pg\";\nimport type { KeyValueStore } from \"awaitly/persistence\";\n\n/**\n * Options for PostgreSQL KeyValueStore.\n */\nexport interface PostgresKeyValueStoreOptions {\n /**\n * PostgreSQL connection string.\n * If provided, other connection options are ignored.\n *\n * @example 'postgresql://user:password@localhost:5432/dbname'\n */\n connectionString?: string;\n\n /**\n * Database host.\n * @default 'localhost'\n */\n host?: string;\n\n /**\n * Database port.\n * @default 5432\n */\n port?: number;\n\n /**\n * Database name.\n */\n database?: string;\n\n /**\n * Database user.\n */\n user?: string;\n\n /**\n * Database password.\n */\n password?: string;\n\n /**\n * Table name for storing key-value pairs.\n * @default 'awaitly_workflow_state'\n */\n tableName?: string;\n\n /**\n * Additional pool configuration options.\n * Ignored if `existingPool` is provided.\n */\n pool?: PoolConfig;\n\n /**\n * Existing PostgreSQL pool to use.\n * If provided, connection options are ignored.\n */\n existingPool?: Pool;\n}\n\n/**\n * PostgreSQL implementation of KeyValueStore.\n *\n * Automatically creates the required table on first use.\n * Supports TTL via expires_at column.\n */\nexport class PostgresKeyValueStore implements KeyValueStore {\n private pool: Pool;\n private tableName: string;\n private initialized: boolean = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: PostgresKeyValueStoreOptions) {\n if (options.existingPool) {\n // Use provided pool\n this.pool = options.existingPool;\n } else if (options.connectionString) {\n // Create pool from connection string\n this.pool = new PgPool({\n connectionString: options.connectionString,\n ...options.pool,\n });\n } else {\n // Create pool from individual options\n this.pool = new PgPool({\n host: options.host ?? \"localhost\",\n port: options.port ?? 5432,\n database: options.database,\n user: options.user,\n password: options.password,\n ...options.pool,\n });\n }\n\n this.tableName = options.tableName ?? \"awaitly_workflow_state\";\n }\n\n /**\n * Initialize the store by creating the table if it doesn't exist.\n * This is called automatically on first use.\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = (async () => {\n try {\n await this.createTable();\n this.initialized = true;\n } catch (error) {\n this.initPromise = null;\n throw error;\n }\n })();\n\n return this.initPromise;\n }\n\n /**\n * Create the table if it doesn't exist.\n */\n private async createTable(): Promise<void> {\n const query = `\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at TIMESTAMP\n );\n\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at \n ON ${this.tableName}(expires_at) \n WHERE expires_at IS NOT NULL;\n `;\n\n await this.pool.query(query);\n }\n\n /**\n * Convert glob pattern to SQL LIKE pattern.\n * Supports * wildcard (matches any characters).\n */\n private patternToLike(pattern: string): string {\n // Escape SQL LIKE special characters and convert * to %\n return pattern.replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\").replace(/\\*/g, \"%\");\n }\n\n async get(key: string): Promise<string | null> {\n await this.ensureInitialized();\n\n const query = `\n SELECT value \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ value: string }> = await this.pool.query(query, [key]);\n\n if (result.rows.length === 0) {\n return null;\n }\n\n return result.rows[0].value;\n }\n\n async set(key: string, value: string, options?: { ttl?: number }): Promise<void> {\n await this.ensureInitialized();\n\n const expiresAt = options?.ttl\n ? new Date(Date.now() + options.ttl * 1000)\n : null;\n\n const query = `\n INSERT INTO ${this.tableName} (key, value, expires_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (key) \n DO UPDATE SET \n value = EXCLUDED.value,\n expires_at = EXCLUDED.expires_at\n `;\n\n await this.pool.query(query, [key, value, expiresAt]);\n }\n\n async delete(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `DELETE FROM ${this.tableName} WHERE key = $1`;\n const result = await this.pool.query(query, [key]);\n\n return (result.rowCount ?? 0) > 0;\n }\n\n async exists(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `\n SELECT 1 \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n LIMIT 1\n `;\n\n const result = await this.pool.query(query, [key]);\n return result.rows.length > 0;\n }\n\n async keys(pattern: string): Promise<string[]> {\n await this.ensureInitialized();\n\n // Convert glob pattern to SQL LIKE\n const likePattern = this.patternToLike(pattern);\n\n const query = `\n SELECT key \n FROM ${this.tableName} \n WHERE key LIKE $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ key: string }> = await this.pool.query(query, [likePattern]);\n\n return result.rows.map((row) => row.key);\n }\n\n /**\n * Close the database connection pool.\n * Call this when done with the store.\n */\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n"],"mappings":"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","names":["index_exports","__export","PostgresKeyValueStore","createPostgresPersistence","__toCommonJS","import_pg","PostgresKeyValueStore","options","PgPool","error","query","pattern","key","result","value","expiresAt","likePattern","row","import_persistence","createPostgresPersistence","options","prefix","storeOptions","store","PostgresKeyValueStore"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/postgres-store.ts","../src/postgres-lock.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use StatePersistence backed by PostgreSQL.\n */\n\nimport { Pool as PgPool } from \"pg\";\nimport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions } from \"./postgres-store\";\nimport { createPostgresLock } from \"./postgres-lock\";\nimport {\n createStatePersistence,\n type StatePersistence,\n type SerializedState,\n type ListPageOptions,\n type ListPageResult,\n} from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\n\n/**\n * Options for cross-process locking (lease + owner token).\n * When set, the returned store implements WorkflowLock so only one process\n * runs a given workflow ID at a time (when durable.run allowConcurrent is false).\n */\nexport interface PostgresLockOptions {\n /**\n * Table name for workflow locks.\n * @default 'awaitly_workflow_lock'\n */\n lockTableName?: string;\n}\n\n/**\n * Options for creating PostgreSQL persistence.\n */\nexport interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {\n /**\n * Key prefix for state entries.\n * @default 'workflow:state:'\n */\n prefix?: string;\n\n /**\n * When set, the store implements WorkflowLock for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n lock?: PostgresLockOptions;\n}\n\n/**\n * Create a StatePersistence instance backed by PostgreSQL.\n *\n * The table is automatically created on first use.\n *\n * @param options - PostgreSQL connection and configuration options\n * @returns StatePersistence instance ready to use with durable.run()\n *\n * @example\n * ```typescript\n * import { createPostgresPersistence } from 'awaitly-postgres';\n * import { durable } from 'awaitly/durable';\n *\n * const store = await createPostgresPersistence({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const result = await durable.run(\n * { fetchUser, createOrder },\n * async (step, { fetchUser, createOrder }) => {\n * const user = await step(() => fetchUser('123'), { key: 'fetch-user' });\n * const order = await step(() => createOrder(user), { key: 'create-order' });\n * return order;\n * },\n * {\n * id: 'checkout-123',\n * store,\n * }\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Using individual connection options\n * const store = await createPostgresPersistence({\n * host: 'localhost',\n * port: 5432,\n * database: 'myapp',\n * user: 'postgres',\n * password: 'password',\n * tableName: 'custom_workflow_state',\n * });\n * ```\n */\nexport type PostgresStatePersistence = StatePersistence & {\n loadRaw(runId: string): Promise<SerializedState | undefined>;\n listPage(options?: ListPageOptions): Promise<ListPageResult>;\n deleteMany(ids: string[]): Promise<number>;\n clear(): Promise<void>;\n};\n\nexport type PostgresStatePersistenceWithLock = PostgresStatePersistence & WorkflowLock;\n\nexport async function createPostgresPersistence(\n options: PostgresPersistenceOptions = {}\n): Promise<PostgresStatePersistence | PostgresStatePersistenceWithLock> {\n const { prefix, lock: lockOptions, ...storeOptions } = options;\n\n const stripPrefix = (key: string): string =>\n key.slice((prefix ?? \"workflow:state:\").length);\n\n const effectivePrefix = prefix ?? \"workflow:state:\";\n const prefixKey = (runId: string): string => `${effectivePrefix}${runId}`;\n\n const addListPageAndDeleteMany = (\n statePersistence: StatePersistence & { loadRaw(runId: string): Promise<SerializedState | undefined> },\n store: PostgresKeyValueStore\n ): PostgresStatePersistence => {\n return Object.assign(statePersistence, {\n async listPage(options: ListPageOptions = {}): Promise<ListPageResult> {\n const { keys, total } = await store.listKeys(`${effectivePrefix}*`, options);\n const ids = keys.map(stripPrefix);\n const limit = Math.min(Math.max(0, options.limit ?? 100), 10_000);\n const nextOffset =\n ids.length === limit ? (options.offset ?? 0) + ids.length : undefined;\n return { ids, total, nextOffset };\n },\n async deleteMany(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n const keys = ids.map(prefixKey);\n return store.deleteMany(keys);\n },\n async clear(): Promise<void> {\n return store.clear();\n },\n });\n };\n\n if (lockOptions !== undefined) {\n const pool =\n storeOptions.existingPool ??\n new PgPool(\n storeOptions.connectionString\n ? { connectionString: storeOptions.connectionString, ...storeOptions.pool }\n : {\n host: storeOptions.host ?? \"localhost\",\n port: storeOptions.port ?? 5432,\n database: storeOptions.database,\n user: storeOptions.user,\n password: storeOptions.password,\n ...storeOptions.pool,\n }\n );\n const store = new PostgresKeyValueStore({ ...storeOptions, existingPool: pool });\n const statePersistence = createStatePersistence(store, prefix) as PostgresStatePersistence;\n const lock = createPostgresLock(pool, {\n lockTableName: lockOptions.lockTableName,\n });\n return Object.assign(addListPageAndDeleteMany(statePersistence, store), {\n tryAcquire: lock.tryAcquire.bind(lock),\n release: lock.release.bind(lock),\n });\n }\n\n const store = new PostgresKeyValueStore(storeOptions);\n const base = createStatePersistence(store, prefix);\n return addListPageAndDeleteMany(base as StatePersistence & { loadRaw(runId: string): Promise<SerializedState | undefined> }, store);\n}\n\n/**\n * PostgreSQL KeyValueStore implementation.\n * Use this directly if you need more control over the store.\n *\n * @example\n * ```typescript\n * import { PostgresKeyValueStore } from 'awaitly-postgres';\n * import { createStatePersistence } from 'awaitly/persistence';\n *\n * const store = new PostgresKeyValueStore({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const persistence = createStatePersistence(store, 'custom:prefix:');\n * ```\n */\nexport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions };\n","/**\n * awaitly-postgres\n *\n * PostgreSQL KeyValueStore implementation for awaitly persistence.\n */\n\nimport type { Pool, PoolConfig, QueryResult } from \"pg\";\nimport { Pool as PgPool } from \"pg\";\nimport type { KeyValueStore, ListPageOptions } from \"awaitly/persistence\";\n\n/**\n * Options for PostgreSQL KeyValueStore.\n */\nexport interface PostgresKeyValueStoreOptions {\n /**\n * PostgreSQL connection string.\n * If provided, other connection options are ignored.\n *\n * @example 'postgresql://user:password@localhost:5432/dbname'\n */\n connectionString?: string;\n\n /**\n * Database host.\n * @default 'localhost'\n */\n host?: string;\n\n /**\n * Database port.\n * @default 5432\n */\n port?: number;\n\n /**\n * Database name.\n */\n database?: string;\n\n /**\n * Database user.\n */\n user?: string;\n\n /**\n * Database password.\n */\n password?: string;\n\n /**\n * Table name for storing key-value pairs.\n * @default 'awaitly_workflow_state'\n */\n tableName?: string;\n\n /**\n * Additional pool configuration options.\n * Ignored if `existingPool` is provided.\n */\n pool?: PoolConfig;\n\n /**\n * Existing PostgreSQL pool to use.\n * If provided, connection options are ignored.\n */\n existingPool?: Pool;\n}\n\n/**\n * PostgreSQL implementation of KeyValueStore.\n *\n * Automatically creates the required table on first use.\n * Supports TTL via expires_at column.\n */\nexport class PostgresKeyValueStore implements KeyValueStore {\n private pool: Pool;\n private tableName: string;\n private initialized: boolean = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: PostgresKeyValueStoreOptions) {\n if (options.existingPool) {\n // Use provided pool\n this.pool = options.existingPool;\n } else if (options.connectionString) {\n // Create pool from connection string\n this.pool = new PgPool({\n connectionString: options.connectionString,\n ...options.pool,\n });\n } else {\n // Create pool from individual options\n this.pool = new PgPool({\n host: options.host ?? \"localhost\",\n port: options.port ?? 5432,\n database: options.database,\n user: options.user,\n password: options.password,\n ...options.pool,\n });\n }\n\n this.tableName = options.tableName ?? \"awaitly_workflow_state\";\n }\n\n /**\n * Initialize the store by creating the table if it doesn't exist.\n * This is called automatically on first use.\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = (async () => {\n try {\n await this.createTable();\n this.initialized = true;\n } catch (error) {\n this.initPromise = null;\n throw error;\n }\n })();\n\n return this.initPromise;\n }\n\n /**\n * Create the table if it doesn't exist.\n * Adds updated_at column to existing tables for listKeys ordering.\n */\n private async createTable(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at TIMESTAMP,\n updated_at TIMESTAMPTZ\n )\n `);\n\n // Add updated_at to existing tables that don't have it (before creating index)\n await this.pool.query(`\n ALTER TABLE ${this.tableName} \n ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ\n `);\n\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at \n ON ${this.tableName}(expires_at) \n WHERE expires_at IS NOT NULL\n `);\n\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_updated_at \n ON ${this.tableName}(updated_at) \n WHERE updated_at IS NOT NULL\n `);\n }\n\n /**\n * Convert glob pattern to SQL LIKE pattern.\n * Supports * wildcard (matches any characters).\n */\n private patternToLike(pattern: string): string {\n // Escape SQL LIKE special characters and convert * to %\n return pattern.replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\").replace(/\\*/g, \"%\");\n }\n\n async get(key: string): Promise<string | null> {\n await this.ensureInitialized();\n\n const query = `\n SELECT value \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ value: string }> = await this.pool.query(query, [key]);\n\n if (result.rows.length === 0) {\n return null;\n }\n\n return result.rows[0].value;\n }\n\n async set(key: string, value: string, options?: { ttl?: number }): Promise<void> {\n await this.ensureInitialized();\n\n const expiresAt = options?.ttl\n ? new Date(Date.now() + options.ttl * 1000)\n : null;\n\n const query = `\n INSERT INTO ${this.tableName} (key, value, expires_at, updated_at)\n VALUES ($1, $2, $3, NOW())\n ON CONFLICT (key) \n DO UPDATE SET \n value = EXCLUDED.value,\n expires_at = EXCLUDED.expires_at,\n updated_at = NOW()\n `;\n\n await this.pool.query(query, [key, value, expiresAt]);\n }\n\n async delete(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `DELETE FROM ${this.tableName} WHERE key = $1`;\n const result = await this.pool.query(query, [key]);\n\n return (result.rowCount ?? 0) > 0;\n }\n\n async exists(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `\n SELECT 1 \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n LIMIT 1\n `;\n\n const result = await this.pool.query(query, [key]);\n return result.rows.length > 0;\n }\n\n async keys(pattern: string): Promise<string[]> {\n await this.ensureInitialized();\n\n // Convert glob pattern to SQL LIKE\n const likePattern = this.patternToLike(pattern);\n\n const query = `\n SELECT key \n FROM ${this.tableName} \n WHERE key LIKE $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ key: string }> = await this.pool.query(query, [likePattern]);\n\n return result.rows.map((row) => row.key);\n }\n\n /**\n * List keys with pagination, filtering, and ordering.\n */\n async listKeys(\n pattern: string,\n options: ListPageOptions = {}\n ): Promise<{ keys: string[]; total?: number }> {\n await this.ensureInitialized();\n\n const limit = Math.min(Math.max(0, options.limit ?? 100), 10_000);\n const offset = Math.max(0, options.offset ?? 0);\n const orderBy = options.orderBy === \"key\" ? \"key\" : \"updated_at\";\n const orderDir = options.orderDir === \"asc\" ? \"ASC\" : \"DESC\";\n const likePattern = this.patternToLike(pattern);\n\n const conditions: string[] = [\n \"key LIKE $1\",\n \"(expires_at IS NULL OR expires_at > NOW())\",\n ];\n const args: unknown[] = [likePattern];\n let paramIndex = 2;\n\n if (options.updatedBefore != null) {\n conditions.push(`updated_at < $${paramIndex}`);\n args.push(options.updatedBefore);\n paramIndex++;\n }\n if (options.updatedAfter != null) {\n conditions.push(`updated_at > $${paramIndex}`);\n args.push(options.updatedAfter);\n paramIndex++;\n }\n\n const whereClause = conditions.join(\" AND \");\n const orderNulls = orderBy === \"updated_at\" ? \" NULLS LAST\" : \"\";\n\n const listQuery = `\n SELECT key \n FROM ${this.tableName} \n WHERE ${whereClause}\n ORDER BY ${orderBy} ${orderDir}${orderNulls}\n LIMIT $${paramIndex} OFFSET $${paramIndex + 1}\n `;\n const listArgs = [...args, limit, offset];\n\n const result: QueryResult<{ key: string }> = await this.pool.query(listQuery, listArgs);\n const keys = result.rows.map((row) => row.key);\n\n let total: number | undefined;\n if (options.includeTotal === true || offset > 0) {\n const countResult: QueryResult<{ count: string }> = await this.pool.query(\n `SELECT COUNT(*) AS count FROM ${this.tableName} WHERE ${whereClause}`,\n args\n );\n total = parseInt(countResult.rows[0]?.count ?? \"0\", 10);\n }\n\n return { keys, total };\n }\n\n /**\n * Delete multiple keys in one round-trip.\n */\n async deleteMany(keys: string[]): Promise<number> {\n if (keys.length === 0) return 0;\n await this.ensureInitialized();\n const result = await this.pool.query(\n `DELETE FROM ${this.tableName} WHERE key = ANY($1::text[])`,\n [keys]\n );\n return result.rowCount ?? 0;\n }\n\n /**\n * Remove all entries from the table (clear all workflow state).\n */\n async clear(): Promise<void> {\n await this.ensureInitialized();\n await this.pool.query(`TRUNCATE TABLE ${this.tableName}`);\n }\n\n /**\n * Close the database connection pool.\n * Call this when done with the store.\n */\n async close(): Promise<void> {\n await this.pool.end();\n }\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 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 return { tryAcquire, release, ensureLockTable };\n}\n"],"mappings":"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","names":["index_exports","__export","PostgresKeyValueStore","createPostgresPersistence","__toCommonJS","import_pg","import_pg","PostgresKeyValueStore","options","PgPool","error","pattern","key","query","result","value","expiresAt","likePattern","row","limit","offset","orderBy","orderDir","conditions","args","paramIndex","whereClause","orderNulls","listQuery","listArgs","keys","total","countResult","import_node_crypto","createPostgresLock","pool","options","lockTableName","safeIndexName","ensureLockTable","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","release","import_persistence","createPostgresPersistence","options","prefix","lockOptions","storeOptions","stripPrefix","key","effectivePrefix","prefixKey","runId","addListPageAndDeleteMany","statePersistence","store","keys","total","ids","limit","nextOffset","pool","PgPool","PostgresKeyValueStore","lock","createPostgresLock","base"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import * as awaitly_persistence from 'awaitly/persistence';
2
- import { KeyValueStore, StatePersistence } from 'awaitly/persistence';
3
1
  import { PoolConfig, Pool } from 'pg';
2
+ import { KeyValueStore, ListPageOptions, StatePersistence, SerializedState, ListPageResult } from 'awaitly/persistence';
3
+ import { WorkflowLock } from 'awaitly/durable';
4
4
 
5
5
  /**
6
6
  * awaitly-postgres
@@ -76,6 +76,7 @@ declare class PostgresKeyValueStore implements KeyValueStore {
76
76
  private ensureInitialized;
77
77
  /**
78
78
  * Create the table if it doesn't exist.
79
+ * Adds updated_at column to existing tables for listKeys ordering.
79
80
  */
80
81
  private createTable;
81
82
  /**
@@ -90,6 +91,21 @@ declare class PostgresKeyValueStore implements KeyValueStore {
90
91
  delete(key: string): Promise<boolean>;
91
92
  exists(key: string): Promise<boolean>;
92
93
  keys(pattern: string): Promise<string[]>;
94
+ /**
95
+ * List keys with pagination, filtering, and ordering.
96
+ */
97
+ listKeys(pattern: string, options?: ListPageOptions): Promise<{
98
+ keys: string[];
99
+ total?: number;
100
+ }>;
101
+ /**
102
+ * Delete multiple keys in one round-trip.
103
+ */
104
+ deleteMany(keys: string[]): Promise<number>;
105
+ /**
106
+ * Remove all entries from the table (clear all workflow state).
107
+ */
108
+ clear(): Promise<void>;
93
109
  /**
94
110
  * Close the database connection pool.
95
111
  * Call this when done with the store.
@@ -97,6 +113,25 @@ declare class PostgresKeyValueStore implements KeyValueStore {
97
113
  close(): Promise<void>;
98
114
  }
99
115
 
116
+ /**
117
+ * awaitly-postgres
118
+ *
119
+ * PostgreSQL persistence adapter for awaitly workflows.
120
+ * Provides ready-to-use StatePersistence backed by PostgreSQL.
121
+ */
122
+
123
+ /**
124
+ * Options for cross-process locking (lease + owner token).
125
+ * When set, the returned store implements WorkflowLock so only one process
126
+ * runs a given workflow ID at a time (when durable.run allowConcurrent is false).
127
+ */
128
+ interface PostgresLockOptions {
129
+ /**
130
+ * Table name for workflow locks.
131
+ * @default 'awaitly_workflow_lock'
132
+ */
133
+ lockTableName?: string;
134
+ }
100
135
  /**
101
136
  * Options for creating PostgreSQL persistence.
102
137
  */
@@ -106,6 +141,11 @@ interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {
106
141
  * @default 'workflow:state:'
107
142
  */
108
143
  prefix?: string;
144
+ /**
145
+ * When set, the store implements WorkflowLock for cross-process concurrency control.
146
+ * Uses a lease (TTL) + owner token; release verifies the token.
147
+ */
148
+ lock?: PostgresLockOptions;
109
149
  }
110
150
  /**
111
151
  * Create a StatePersistence instance backed by PostgreSQL.
@@ -151,8 +191,13 @@ interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {
151
191
  * });
152
192
  * ```
153
193
  */
154
- declare function createPostgresPersistence(options?: PostgresPersistenceOptions): Promise<StatePersistence & {
155
- loadRaw(runId: string): Promise<awaitly_persistence.SerializedState | undefined>;
156
- }>;
194
+ type PostgresStatePersistence = StatePersistence & {
195
+ loadRaw(runId: string): Promise<SerializedState | undefined>;
196
+ listPage(options?: ListPageOptions): Promise<ListPageResult>;
197
+ deleteMany(ids: string[]): Promise<number>;
198
+ clear(): Promise<void>;
199
+ };
200
+ type PostgresStatePersistenceWithLock = PostgresStatePersistence & WorkflowLock;
201
+ declare function createPostgresPersistence(options?: PostgresPersistenceOptions): Promise<PostgresStatePersistence | PostgresStatePersistenceWithLock>;
157
202
 
158
- export { PostgresKeyValueStore, type PostgresKeyValueStoreOptions, type PostgresPersistenceOptions, createPostgresPersistence };
203
+ export { PostgresKeyValueStore, type PostgresKeyValueStoreOptions, type PostgresLockOptions, type PostgresPersistenceOptions, type PostgresStatePersistence, type PostgresStatePersistenceWithLock, createPostgresPersistence };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import * as awaitly_persistence from 'awaitly/persistence';
2
- import { KeyValueStore, StatePersistence } from 'awaitly/persistence';
3
1
  import { PoolConfig, Pool } from 'pg';
2
+ import { KeyValueStore, ListPageOptions, StatePersistence, SerializedState, ListPageResult } from 'awaitly/persistence';
3
+ import { WorkflowLock } from 'awaitly/durable';
4
4
 
5
5
  /**
6
6
  * awaitly-postgres
@@ -76,6 +76,7 @@ declare class PostgresKeyValueStore implements KeyValueStore {
76
76
  private ensureInitialized;
77
77
  /**
78
78
  * Create the table if it doesn't exist.
79
+ * Adds updated_at column to existing tables for listKeys ordering.
79
80
  */
80
81
  private createTable;
81
82
  /**
@@ -90,6 +91,21 @@ declare class PostgresKeyValueStore implements KeyValueStore {
90
91
  delete(key: string): Promise<boolean>;
91
92
  exists(key: string): Promise<boolean>;
92
93
  keys(pattern: string): Promise<string[]>;
94
+ /**
95
+ * List keys with pagination, filtering, and ordering.
96
+ */
97
+ listKeys(pattern: string, options?: ListPageOptions): Promise<{
98
+ keys: string[];
99
+ total?: number;
100
+ }>;
101
+ /**
102
+ * Delete multiple keys in one round-trip.
103
+ */
104
+ deleteMany(keys: string[]): Promise<number>;
105
+ /**
106
+ * Remove all entries from the table (clear all workflow state).
107
+ */
108
+ clear(): Promise<void>;
93
109
  /**
94
110
  * Close the database connection pool.
95
111
  * Call this when done with the store.
@@ -97,6 +113,25 @@ declare class PostgresKeyValueStore implements KeyValueStore {
97
113
  close(): Promise<void>;
98
114
  }
99
115
 
116
+ /**
117
+ * awaitly-postgres
118
+ *
119
+ * PostgreSQL persistence adapter for awaitly workflows.
120
+ * Provides ready-to-use StatePersistence backed by PostgreSQL.
121
+ */
122
+
123
+ /**
124
+ * Options for cross-process locking (lease + owner token).
125
+ * When set, the returned store implements WorkflowLock so only one process
126
+ * runs a given workflow ID at a time (when durable.run allowConcurrent is false).
127
+ */
128
+ interface PostgresLockOptions {
129
+ /**
130
+ * Table name for workflow locks.
131
+ * @default 'awaitly_workflow_lock'
132
+ */
133
+ lockTableName?: string;
134
+ }
100
135
  /**
101
136
  * Options for creating PostgreSQL persistence.
102
137
  */
@@ -106,6 +141,11 @@ interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {
106
141
  * @default 'workflow:state:'
107
142
  */
108
143
  prefix?: string;
144
+ /**
145
+ * When set, the store implements WorkflowLock for cross-process concurrency control.
146
+ * Uses a lease (TTL) + owner token; release verifies the token.
147
+ */
148
+ lock?: PostgresLockOptions;
109
149
  }
110
150
  /**
111
151
  * Create a StatePersistence instance backed by PostgreSQL.
@@ -151,8 +191,13 @@ interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {
151
191
  * });
152
192
  * ```
153
193
  */
154
- declare function createPostgresPersistence(options?: PostgresPersistenceOptions): Promise<StatePersistence & {
155
- loadRaw(runId: string): Promise<awaitly_persistence.SerializedState | undefined>;
156
- }>;
194
+ type PostgresStatePersistence = StatePersistence & {
195
+ loadRaw(runId: string): Promise<SerializedState | undefined>;
196
+ listPage(options?: ListPageOptions): Promise<ListPageResult>;
197
+ deleteMany(ids: string[]): Promise<number>;
198
+ clear(): Promise<void>;
199
+ };
200
+ type PostgresStatePersistenceWithLock = PostgresStatePersistence & WorkflowLock;
201
+ declare function createPostgresPersistence(options?: PostgresPersistenceOptions): Promise<PostgresStatePersistence | PostgresStatePersistenceWithLock>;
157
202
 
158
- export { PostgresKeyValueStore, type PostgresKeyValueStoreOptions, type PostgresPersistenceOptions, createPostgresPersistence };
203
+ export { PostgresKeyValueStore, type PostgresKeyValueStoreOptions, type PostgresLockOptions, type PostgresPersistenceOptions, type PostgresStatePersistence, type PostgresStatePersistenceWithLock, createPostgresPersistence };
package/dist/index.js CHANGED
@@ -1,35 +1,65 @@
1
- import{Pool as n}from"pg";var r=class{pool;tableName;initialized=!1;initPromise=null;constructor(e){e.existingPool?this.pool=e.existingPool:e.connectionString?this.pool=new n({connectionString:e.connectionString,...e.pool}):this.pool=new n({host:e.host??"localhost",port:e.port??5432,database:e.database,user:e.user,password:e.password,...e.pool}),this.tableName=e.tableName??"awaitly_workflow_state"}async ensureInitialized(){if(!this.initialized)return this.initPromise?this.initPromise:(this.initPromise=(async()=>{try{await this.createTable(),this.initialized=!0}catch(e){throw this.initPromise=null,e}})(),this.initPromise)}async createTable(){let e=`
1
+ import{Pool as L}from"pg";import{Pool as N}from"pg";var g=class{pool;tableName;initialized=!1;initPromise=null;constructor(e){e.existingPool?this.pool=e.existingPool:e.connectionString?this.pool=new N({connectionString:e.connectionString,...e.pool}):this.pool=new N({host:e.host??"localhost",port:e.port??5432,database:e.database,user:e.user,password:e.password,...e.pool}),this.tableName=e.tableName??"awaitly_workflow_state"}async ensureInitialized(){if(!this.initialized)return this.initPromise?this.initPromise:(this.initPromise=(async()=>{try{await this.createTable(),this.initialized=!0}catch(e){throw this.initPromise=null,e}})(),this.initPromise)}async createTable(){await this.pool.query(`
2
2
  CREATE TABLE IF NOT EXISTS ${this.tableName} (
3
3
  key TEXT PRIMARY KEY,
4
4
  value TEXT NOT NULL,
5
- expires_at TIMESTAMP
6
- );
7
-
5
+ expires_at TIMESTAMP,
6
+ updated_at TIMESTAMPTZ
7
+ )
8
+ `),await this.pool.query(`
9
+ ALTER TABLE ${this.tableName}
10
+ ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ
11
+ `),await this.pool.query(`
8
12
  CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at
9
13
  ON ${this.tableName}(expires_at)
10
- WHERE expires_at IS NOT NULL;
11
- `;await this.pool.query(e)}patternToLike(e){return e.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%")}async get(e){await this.ensureInitialized();let t=`
14
+ WHERE expires_at IS NOT NULL
15
+ `),await this.pool.query(`
16
+ CREATE INDEX IF NOT EXISTS idx_${this.tableName}_updated_at
17
+ ON ${this.tableName}(updated_at)
18
+ WHERE updated_at IS NOT NULL
19
+ `)}patternToLike(e){return e.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%")}async get(e){await this.ensureInitialized();let t=`
12
20
  SELECT value
13
21
  FROM ${this.tableName}
14
22
  WHERE key = $1
15
23
  AND (expires_at IS NULL OR expires_at > NOW())
16
- `,i=await this.pool.query(t,[e]);return i.rows.length===0?null:i.rows[0].value}async set(e,t,i){await this.ensureInitialized();let o=i?.ttl?new Date(Date.now()+i.ttl*1e3):null,s=`
17
- INSERT INTO ${this.tableName} (key, value, expires_at)
18
- VALUES ($1, $2, $3)
24
+ `,s=await this.pool.query(t,[e]);return s.rows.length===0?null:s.rows[0].value}async set(e,t,s){await this.ensureInitialized();let n=s?.ttl?new Date(Date.now()+s.ttl*1e3):null,o=`
25
+ INSERT INTO ${this.tableName} (key, value, expires_at, updated_at)
26
+ VALUES ($1, $2, $3, NOW())
19
27
  ON CONFLICT (key)
20
28
  DO UPDATE SET
21
29
  value = EXCLUDED.value,
22
- expires_at = EXCLUDED.expires_at
23
- `;await this.pool.query(s,[e,t,o])}async delete(e){await this.ensureInitialized();let t=`DELETE FROM ${this.tableName} WHERE key = $1`;return((await this.pool.query(t,[e])).rowCount??0)>0}async exists(e){await this.ensureInitialized();let t=`
30
+ expires_at = EXCLUDED.expires_at,
31
+ updated_at = NOW()
32
+ `;await this.pool.query(o,[e,t,n])}async delete(e){await this.ensureInitialized();let t=`DELETE FROM ${this.tableName} WHERE key = $1`;return((await this.pool.query(t,[e])).rowCount??0)>0}async exists(e){await this.ensureInitialized();let t=`
24
33
  SELECT 1
25
34
  FROM ${this.tableName}
26
35
  WHERE key = $1
27
36
  AND (expires_at IS NULL OR expires_at > NOW())
28
37
  LIMIT 1
29
- `;return(await this.pool.query(t,[e])).rows.length>0}async keys(e){await this.ensureInitialized();let t=this.patternToLike(e),i=`
38
+ `;return(await this.pool.query(t,[e])).rows.length>0}async keys(e){await this.ensureInitialized();let t=this.patternToLike(e),s=`
30
39
  SELECT key
31
40
  FROM ${this.tableName}
32
41
  WHERE key LIKE $1
33
42
  AND (expires_at IS NULL OR expires_at > NOW())
34
- `;return(await this.pool.query(i,[t])).rows.map(s=>s.key)}async close(){await this.pool.end()}};import{createStatePersistence as l}from"awaitly/persistence";async function g(a={}){let{prefix:e,...t}=a,i=new r(t);return l(i,e)}export{r as PostgresKeyValueStore,g as createPostgresPersistence};
43
+ `;return(await this.pool.query(s,[t])).rows.map(o=>o.key)}async listKeys(e,t={}){await this.ensureInitialized();let s=Math.min(Math.max(0,t.limit??100),1e4),n=Math.max(0,t.offset??0),o=t.orderBy==="key"?"key":"updated_at",P=t.orderDir==="asc"?"ASC":"DESC",c=this.patternToLike(e),l=["key LIKE $1","(expires_at IS NULL OR expires_at > NOW())"],p=[c],r=2;t.updatedBefore!=null&&(l.push(`updated_at < $${r}`),p.push(t.updatedBefore),r++),t.updatedAfter!=null&&(l.push(`updated_at > $${r}`),p.push(t.updatedAfter),r++);let a=l.join(" AND "),i=o==="updated_at"?" NULLS LAST":"",u=`
44
+ SELECT key
45
+ FROM ${this.tableName}
46
+ WHERE ${a}
47
+ ORDER BY ${o} ${P}${i}
48
+ LIMIT $${r} OFFSET $${r+1}
49
+ `,E=[...p,s,n],w=(await this.pool.query(u,E)).rows.map(T=>T.key),y;if(t.includeTotal===!0||n>0){let T=await this.pool.query(`SELECT COUNT(*) AS count FROM ${this.tableName} WHERE ${a}`,p);y=parseInt(T.rows[0]?.count??"0",10)}return{keys:w,total:y}}async deleteMany(e){return e.length===0?0:(await this.ensureInitialized(),(await this.pool.query(`DELETE FROM ${this.tableName} WHERE key = ANY($1::text[])`,[e])).rowCount??0)}async clear(){await this.ensureInitialized(),await this.pool.query(`TRUNCATE TABLE ${this.tableName}`)}async close(){await this.pool.end()}};import{randomUUID as k}from"crypto";function h(d,e={}){let t=e.lockTableName??"awaitly_workflow_lock",s=`idx_${t.replace(/[^a-zA-Z0-9_]/g,"_")}_expires_at`;async function n(){await d.query(`
50
+ CREATE TABLE IF NOT EXISTS ${t} (
51
+ workflow_id TEXT PRIMARY KEY,
52
+ owner_token TEXT NOT NULL,
53
+ expires_at TIMESTAMPTZ NOT NULL
54
+ );
55
+ CREATE INDEX IF NOT EXISTS ${s} ON ${t}(expires_at);
56
+ `)}async function o(c,l){let p=l?.ttlMs??6e4,r=k(),a=new Date(Date.now()+p);await n();let i=await d.query(`
57
+ INSERT INTO ${t} (workflow_id, owner_token, expires_at)
58
+ VALUES ($1, $2, $3)
59
+ ON CONFLICT (workflow_id) DO UPDATE SET
60
+ owner_token = EXCLUDED.owner_token,
61
+ expires_at = EXCLUDED.expires_at
62
+ WHERE ${t}.expires_at < NOW()
63
+ RETURNING owner_token
64
+ `,[c,r,a]);return i.rowCount===1&&i.rows[0].owner_token===r?{ownerToken:r}:null}async function P(c,l){await d.query(`DELETE FROM ${t} WHERE workflow_id = $1 AND owner_token = $2`,[c,l])}return{tryAcquire:o,release:P,ensureLockTable:n}}import{createStatePersistence as S}from"awaitly/persistence";async function A(d={}){let{prefix:e,lock:t,...s}=d,n=r=>r.slice((e??"workflow:state:").length),o=e??"workflow:state:",P=r=>`${o}${r}`,c=(r,a)=>Object.assign(r,{async listPage(i={}){let{keys:u,total:E}=await a.listKeys(`${o}*`,i),m=u.map(n),w=Math.min(Math.max(0,i.limit??100),1e4),y=m.length===w?(i.offset??0)+m.length:void 0;return{ids:m,total:E,nextOffset:y}},async deleteMany(i){if(i.length===0)return 0;let u=i.map(P);return a.deleteMany(u)},async clear(){return a.clear()}});if(t!==void 0){let r=s.existingPool??new L(s.connectionString?{connectionString:s.connectionString,...s.pool}:{host:s.host??"localhost",port:s.port??5432,database:s.database,user:s.user,password:s.password,...s.pool}),a=new g({...s,existingPool:r}),i=S(a,e),u=h(r,{lockTableName:t.lockTableName});return Object.assign(c(i,a),{tryAcquire:u.tryAcquire.bind(u),release:u.release.bind(u)})}let l=new g(s),p=S(l,e);return c(p,l)}export{g as PostgresKeyValueStore,A as createPostgresPersistence};
35
65
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/postgres-store.ts","../src/index.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL KeyValueStore implementation for awaitly persistence.\n */\n\nimport type { Pool, PoolConfig, QueryResult } from \"pg\";\nimport { Pool as PgPool } from \"pg\";\nimport type { KeyValueStore } from \"awaitly/persistence\";\n\n/**\n * Options for PostgreSQL KeyValueStore.\n */\nexport interface PostgresKeyValueStoreOptions {\n /**\n * PostgreSQL connection string.\n * If provided, other connection options are ignored.\n *\n * @example 'postgresql://user:password@localhost:5432/dbname'\n */\n connectionString?: string;\n\n /**\n * Database host.\n * @default 'localhost'\n */\n host?: string;\n\n /**\n * Database port.\n * @default 5432\n */\n port?: number;\n\n /**\n * Database name.\n */\n database?: string;\n\n /**\n * Database user.\n */\n user?: string;\n\n /**\n * Database password.\n */\n password?: string;\n\n /**\n * Table name for storing key-value pairs.\n * @default 'awaitly_workflow_state'\n */\n tableName?: string;\n\n /**\n * Additional pool configuration options.\n * Ignored if `existingPool` is provided.\n */\n pool?: PoolConfig;\n\n /**\n * Existing PostgreSQL pool to use.\n * If provided, connection options are ignored.\n */\n existingPool?: Pool;\n}\n\n/**\n * PostgreSQL implementation of KeyValueStore.\n *\n * Automatically creates the required table on first use.\n * Supports TTL via expires_at column.\n */\nexport class PostgresKeyValueStore implements KeyValueStore {\n private pool: Pool;\n private tableName: string;\n private initialized: boolean = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: PostgresKeyValueStoreOptions) {\n if (options.existingPool) {\n // Use provided pool\n this.pool = options.existingPool;\n } else if (options.connectionString) {\n // Create pool from connection string\n this.pool = new PgPool({\n connectionString: options.connectionString,\n ...options.pool,\n });\n } else {\n // Create pool from individual options\n this.pool = new PgPool({\n host: options.host ?? \"localhost\",\n port: options.port ?? 5432,\n database: options.database,\n user: options.user,\n password: options.password,\n ...options.pool,\n });\n }\n\n this.tableName = options.tableName ?? \"awaitly_workflow_state\";\n }\n\n /**\n * Initialize the store by creating the table if it doesn't exist.\n * This is called automatically on first use.\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = (async () => {\n try {\n await this.createTable();\n this.initialized = true;\n } catch (error) {\n this.initPromise = null;\n throw error;\n }\n })();\n\n return this.initPromise;\n }\n\n /**\n * Create the table if it doesn't exist.\n */\n private async createTable(): Promise<void> {\n const query = `\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at TIMESTAMP\n );\n\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at \n ON ${this.tableName}(expires_at) \n WHERE expires_at IS NOT NULL;\n `;\n\n await this.pool.query(query);\n }\n\n /**\n * Convert glob pattern to SQL LIKE pattern.\n * Supports * wildcard (matches any characters).\n */\n private patternToLike(pattern: string): string {\n // Escape SQL LIKE special characters and convert * to %\n return pattern.replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\").replace(/\\*/g, \"%\");\n }\n\n async get(key: string): Promise<string | null> {\n await this.ensureInitialized();\n\n const query = `\n SELECT value \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ value: string }> = await this.pool.query(query, [key]);\n\n if (result.rows.length === 0) {\n return null;\n }\n\n return result.rows[0].value;\n }\n\n async set(key: string, value: string, options?: { ttl?: number }): Promise<void> {\n await this.ensureInitialized();\n\n const expiresAt = options?.ttl\n ? new Date(Date.now() + options.ttl * 1000)\n : null;\n\n const query = `\n INSERT INTO ${this.tableName} (key, value, expires_at)\n VALUES ($1, $2, $3)\n ON CONFLICT (key) \n DO UPDATE SET \n value = EXCLUDED.value,\n expires_at = EXCLUDED.expires_at\n `;\n\n await this.pool.query(query, [key, value, expiresAt]);\n }\n\n async delete(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `DELETE FROM ${this.tableName} WHERE key = $1`;\n const result = await this.pool.query(query, [key]);\n\n return (result.rowCount ?? 0) > 0;\n }\n\n async exists(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `\n SELECT 1 \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n LIMIT 1\n `;\n\n const result = await this.pool.query(query, [key]);\n return result.rows.length > 0;\n }\n\n async keys(pattern: string): Promise<string[]> {\n await this.ensureInitialized();\n\n // Convert glob pattern to SQL LIKE\n const likePattern = this.patternToLike(pattern);\n\n const query = `\n SELECT key \n FROM ${this.tableName} \n WHERE key LIKE $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ key: string }> = await this.pool.query(query, [likePattern]);\n\n return result.rows.map((row) => row.key);\n }\n\n /**\n * Close the database connection pool.\n * Call this when done with the store.\n */\n async close(): Promise<void> {\n await this.pool.end();\n }\n}\n","/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use StatePersistence backed by PostgreSQL.\n */\n\nimport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions } from \"./postgres-store\";\nimport { createStatePersistence, type StatePersistence } from \"awaitly/persistence\";\n\n/**\n * Options for creating PostgreSQL persistence.\n */\nexport interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {\n /**\n * Key prefix for state entries.\n * @default 'workflow:state:'\n */\n prefix?: string;\n}\n\n/**\n * Create a StatePersistence instance backed by PostgreSQL.\n *\n * The table is automatically created on first use.\n *\n * @param options - PostgreSQL connection and configuration options\n * @returns StatePersistence instance ready to use with durable.run()\n *\n * @example\n * ```typescript\n * import { createPostgresPersistence } from 'awaitly-postgres';\n * import { durable } from 'awaitly/durable';\n *\n * const store = await createPostgresPersistence({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const result = await durable.run(\n * { fetchUser, createOrder },\n * async (step, { fetchUser, createOrder }) => {\n * const user = await step(() => fetchUser('123'), { key: 'fetch-user' });\n * const order = await step(() => createOrder(user), { key: 'create-order' });\n * return order;\n * },\n * {\n * id: 'checkout-123',\n * store,\n * }\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Using individual connection options\n * const store = await createPostgresPersistence({\n * host: 'localhost',\n * port: 5432,\n * database: 'myapp',\n * user: 'postgres',\n * password: 'password',\n * tableName: 'custom_workflow_state',\n * });\n * ```\n */\nexport async function createPostgresPersistence(\n options: PostgresPersistenceOptions = {}\n): Promise<StatePersistence & { loadRaw(runId: string): Promise<import(\"awaitly/persistence\").SerializedState | undefined> }> {\n const { prefix, ...storeOptions } = options;\n\n const store = new PostgresKeyValueStore(storeOptions);\n return createStatePersistence(store, prefix);\n}\n\n/**\n * PostgreSQL KeyValueStore implementation.\n * Use this directly if you need more control over the store.\n *\n * @example\n * ```typescript\n * import { PostgresKeyValueStore } from 'awaitly-postgres';\n * import { createStatePersistence } from 'awaitly/persistence';\n *\n * const store = new PostgresKeyValueStore({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const persistence = createStatePersistence(store, 'custom:prefix:');\n * ```\n */\nexport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions };\n"],"mappings":"AAOA,OAAS,QAAQA,MAAc,KAmExB,IAAMC,EAAN,KAAqD,CAClD,KACA,UACA,YAAuB,GACvB,YAAoC,KAE5C,YAAYC,EAAuC,CAC7CA,EAAQ,aAEV,KAAK,KAAOA,EAAQ,aACXA,EAAQ,iBAEjB,KAAK,KAAO,IAAIF,EAAO,CACrB,iBAAkBE,EAAQ,iBAC1B,GAAGA,EAAQ,IACb,CAAC,EAGD,KAAK,KAAO,IAAIF,EAAO,CACrB,KAAME,EAAQ,MAAQ,YACtB,KAAMA,EAAQ,MAAQ,KACtB,SAAUA,EAAQ,SAClB,KAAMA,EAAQ,KACd,SAAUA,EAAQ,SAClB,GAAGA,EAAQ,IACb,CAAC,EAGH,KAAK,UAAYA,EAAQ,WAAa,wBACxC,CAMA,MAAc,mBAAmC,CAC/C,GAAI,MAAK,YAIT,OAAI,KAAK,YACA,KAAK,aAGd,KAAK,aAAe,SAAY,CAC9B,GAAI,CACF,MAAM,KAAK,YAAY,EACvB,KAAK,YAAc,EACrB,OAASC,EAAO,CACd,WAAK,YAAc,KACbA,CACR,CACF,GAAG,EAEI,KAAK,YACd,CAKA,MAAc,aAA6B,CACzC,IAAMC,EAAQ;AAAA,mCACiB,KAAK,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCAMV,KAAK,SAAS;AAAA,WAC1C,KAAK,SAAS;AAAA;AAAA,MAIrB,MAAM,KAAK,KAAK,MAAMA,CAAK,CAC7B,CAMQ,cAAcC,EAAyB,CAE7C,OAAOA,EAAQ,QAAQ,KAAM,KAAK,EAAE,QAAQ,KAAM,KAAK,EAAE,QAAQ,MAAO,GAAG,CAC7E,CAEA,MAAM,IAAIC,EAAqC,CAC7C,MAAM,KAAK,kBAAkB,EAE7B,IAAMF,EAAQ;AAAA;AAAA,aAEL,KAAK,SAAS;AAAA;AAAA;AAAA,MAKjBG,EAAyC,MAAM,KAAK,KAAK,MAAMH,EAAO,CAACE,CAAG,CAAC,EAEjF,OAAIC,EAAO,KAAK,SAAW,EAClB,KAGFA,EAAO,KAAK,CAAC,EAAE,KACxB,CAEA,MAAM,IAAID,EAAaE,EAAeN,EAA2C,CAC/E,MAAM,KAAK,kBAAkB,EAE7B,IAAMO,EAAYP,GAAS,IACvB,IAAI,KAAK,KAAK,IAAI,EAAIA,EAAQ,IAAM,GAAI,EACxC,KAEEE,EAAQ;AAAA,oBACE,KAAK,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQ9B,MAAM,KAAK,KAAK,MAAMA,EAAO,CAACE,EAAKE,EAAOC,CAAS,CAAC,CACtD,CAEA,MAAM,OAAOH,EAA+B,CAC1C,MAAM,KAAK,kBAAkB,EAE7B,IAAMF,EAAQ,eAAe,KAAK,SAAS,kBAG3C,QAFe,MAAM,KAAK,KAAK,MAAMA,EAAO,CAACE,CAAG,CAAC,GAElC,UAAY,GAAK,CAClC,CAEA,MAAM,OAAOA,EAA+B,CAC1C,MAAM,KAAK,kBAAkB,EAE7B,IAAMF,EAAQ;AAAA;AAAA,aAEL,KAAK,SAAS;AAAA;AAAA;AAAA;AAAA,MAOvB,OADe,MAAM,KAAK,KAAK,MAAMA,EAAO,CAACE,CAAG,CAAC,GACnC,KAAK,OAAS,CAC9B,CAEA,MAAM,KAAKD,EAAoC,CAC7C,MAAM,KAAK,kBAAkB,EAG7B,IAAMK,EAAc,KAAK,cAAcL,CAAO,EAExCD,EAAQ;AAAA;AAAA,aAEL,KAAK,SAAS;AAAA;AAAA;AAAA,MAOvB,OAF6C,MAAM,KAAK,KAAK,MAAMA,EAAO,CAACM,CAAW,CAAC,GAEzE,KAAK,IAAKC,GAAQA,EAAI,GAAG,CACzC,CAMA,MAAM,OAAuB,CAC3B,MAAM,KAAK,KAAK,IAAI,CACtB,CACF,EC9OA,OAAS,0BAAAC,MAAqD,sBAyD9D,eAAsBC,EACpBC,EAAsC,CAAC,EACqF,CAC5H,GAAM,CAAE,OAAAC,EAAQ,GAAGC,CAAa,EAAIF,EAE9BG,EAAQ,IAAIC,EAAsBF,CAAY,EACpD,OAAOJ,EAAuBK,EAAOF,CAAM,CAC7C","names":["PgPool","PostgresKeyValueStore","options","error","query","pattern","key","result","value","expiresAt","likePattern","row","createStatePersistence","createPostgresPersistence","options","prefix","storeOptions","store","PostgresKeyValueStore"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/postgres-store.ts","../src/postgres-lock.ts"],"sourcesContent":["/**\n * awaitly-postgres\n *\n * PostgreSQL persistence adapter for awaitly workflows.\n * Provides ready-to-use StatePersistence backed by PostgreSQL.\n */\n\nimport { Pool as PgPool } from \"pg\";\nimport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions } from \"./postgres-store\";\nimport { createPostgresLock } from \"./postgres-lock\";\nimport {\n createStatePersistence,\n type StatePersistence,\n type SerializedState,\n type ListPageOptions,\n type ListPageResult,\n} from \"awaitly/persistence\";\nimport type { WorkflowLock } from \"awaitly/durable\";\n\n/**\n * Options for cross-process locking (lease + owner token).\n * When set, the returned store implements WorkflowLock so only one process\n * runs a given workflow ID at a time (when durable.run allowConcurrent is false).\n */\nexport interface PostgresLockOptions {\n /**\n * Table name for workflow locks.\n * @default 'awaitly_workflow_lock'\n */\n lockTableName?: string;\n}\n\n/**\n * Options for creating PostgreSQL persistence.\n */\nexport interface PostgresPersistenceOptions extends PostgresKeyValueStoreOptions {\n /**\n * Key prefix for state entries.\n * @default 'workflow:state:'\n */\n prefix?: string;\n\n /**\n * When set, the store implements WorkflowLock for cross-process concurrency control.\n * Uses a lease (TTL) + owner token; release verifies the token.\n */\n lock?: PostgresLockOptions;\n}\n\n/**\n * Create a StatePersistence instance backed by PostgreSQL.\n *\n * The table is automatically created on first use.\n *\n * @param options - PostgreSQL connection and configuration options\n * @returns StatePersistence instance ready to use with durable.run()\n *\n * @example\n * ```typescript\n * import { createPostgresPersistence } from 'awaitly-postgres';\n * import { durable } from 'awaitly/durable';\n *\n * const store = await createPostgresPersistence({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const result = await durable.run(\n * { fetchUser, createOrder },\n * async (step, { fetchUser, createOrder }) => {\n * const user = await step(() => fetchUser('123'), { key: 'fetch-user' });\n * const order = await step(() => createOrder(user), { key: 'create-order' });\n * return order;\n * },\n * {\n * id: 'checkout-123',\n * store,\n * }\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Using individual connection options\n * const store = await createPostgresPersistence({\n * host: 'localhost',\n * port: 5432,\n * database: 'myapp',\n * user: 'postgres',\n * password: 'password',\n * tableName: 'custom_workflow_state',\n * });\n * ```\n */\nexport type PostgresStatePersistence = StatePersistence & {\n loadRaw(runId: string): Promise<SerializedState | undefined>;\n listPage(options?: ListPageOptions): Promise<ListPageResult>;\n deleteMany(ids: string[]): Promise<number>;\n clear(): Promise<void>;\n};\n\nexport type PostgresStatePersistenceWithLock = PostgresStatePersistence & WorkflowLock;\n\nexport async function createPostgresPersistence(\n options: PostgresPersistenceOptions = {}\n): Promise<PostgresStatePersistence | PostgresStatePersistenceWithLock> {\n const { prefix, lock: lockOptions, ...storeOptions } = options;\n\n const stripPrefix = (key: string): string =>\n key.slice((prefix ?? \"workflow:state:\").length);\n\n const effectivePrefix = prefix ?? \"workflow:state:\";\n const prefixKey = (runId: string): string => `${effectivePrefix}${runId}`;\n\n const addListPageAndDeleteMany = (\n statePersistence: StatePersistence & { loadRaw(runId: string): Promise<SerializedState | undefined> },\n store: PostgresKeyValueStore\n ): PostgresStatePersistence => {\n return Object.assign(statePersistence, {\n async listPage(options: ListPageOptions = {}): Promise<ListPageResult> {\n const { keys, total } = await store.listKeys(`${effectivePrefix}*`, options);\n const ids = keys.map(stripPrefix);\n const limit = Math.min(Math.max(0, options.limit ?? 100), 10_000);\n const nextOffset =\n ids.length === limit ? (options.offset ?? 0) + ids.length : undefined;\n return { ids, total, nextOffset };\n },\n async deleteMany(ids: string[]): Promise<number> {\n if (ids.length === 0) return 0;\n const keys = ids.map(prefixKey);\n return store.deleteMany(keys);\n },\n async clear(): Promise<void> {\n return store.clear();\n },\n });\n };\n\n if (lockOptions !== undefined) {\n const pool =\n storeOptions.existingPool ??\n new PgPool(\n storeOptions.connectionString\n ? { connectionString: storeOptions.connectionString, ...storeOptions.pool }\n : {\n host: storeOptions.host ?? \"localhost\",\n port: storeOptions.port ?? 5432,\n database: storeOptions.database,\n user: storeOptions.user,\n password: storeOptions.password,\n ...storeOptions.pool,\n }\n );\n const store = new PostgresKeyValueStore({ ...storeOptions, existingPool: pool });\n const statePersistence = createStatePersistence(store, prefix) as PostgresStatePersistence;\n const lock = createPostgresLock(pool, {\n lockTableName: lockOptions.lockTableName,\n });\n return Object.assign(addListPageAndDeleteMany(statePersistence, store), {\n tryAcquire: lock.tryAcquire.bind(lock),\n release: lock.release.bind(lock),\n });\n }\n\n const store = new PostgresKeyValueStore(storeOptions);\n const base = createStatePersistence(store, prefix);\n return addListPageAndDeleteMany(base as StatePersistence & { loadRaw(runId: string): Promise<SerializedState | undefined> }, store);\n}\n\n/**\n * PostgreSQL KeyValueStore implementation.\n * Use this directly if you need more control over the store.\n *\n * @example\n * ```typescript\n * import { PostgresKeyValueStore } from 'awaitly-postgres';\n * import { createStatePersistence } from 'awaitly/persistence';\n *\n * const store = new PostgresKeyValueStore({\n * connectionString: process.env.DATABASE_URL,\n * });\n *\n * const persistence = createStatePersistence(store, 'custom:prefix:');\n * ```\n */\nexport { PostgresKeyValueStore, type PostgresKeyValueStoreOptions };\n","/**\n * awaitly-postgres\n *\n * PostgreSQL KeyValueStore implementation for awaitly persistence.\n */\n\nimport type { Pool, PoolConfig, QueryResult } from \"pg\";\nimport { Pool as PgPool } from \"pg\";\nimport type { KeyValueStore, ListPageOptions } from \"awaitly/persistence\";\n\n/**\n * Options for PostgreSQL KeyValueStore.\n */\nexport interface PostgresKeyValueStoreOptions {\n /**\n * PostgreSQL connection string.\n * If provided, other connection options are ignored.\n *\n * @example 'postgresql://user:password@localhost:5432/dbname'\n */\n connectionString?: string;\n\n /**\n * Database host.\n * @default 'localhost'\n */\n host?: string;\n\n /**\n * Database port.\n * @default 5432\n */\n port?: number;\n\n /**\n * Database name.\n */\n database?: string;\n\n /**\n * Database user.\n */\n user?: string;\n\n /**\n * Database password.\n */\n password?: string;\n\n /**\n * Table name for storing key-value pairs.\n * @default 'awaitly_workflow_state'\n */\n tableName?: string;\n\n /**\n * Additional pool configuration options.\n * Ignored if `existingPool` is provided.\n */\n pool?: PoolConfig;\n\n /**\n * Existing PostgreSQL pool to use.\n * If provided, connection options are ignored.\n */\n existingPool?: Pool;\n}\n\n/**\n * PostgreSQL implementation of KeyValueStore.\n *\n * Automatically creates the required table on first use.\n * Supports TTL via expires_at column.\n */\nexport class PostgresKeyValueStore implements KeyValueStore {\n private pool: Pool;\n private tableName: string;\n private initialized: boolean = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: PostgresKeyValueStoreOptions) {\n if (options.existingPool) {\n // Use provided pool\n this.pool = options.existingPool;\n } else if (options.connectionString) {\n // Create pool from connection string\n this.pool = new PgPool({\n connectionString: options.connectionString,\n ...options.pool,\n });\n } else {\n // Create pool from individual options\n this.pool = new PgPool({\n host: options.host ?? \"localhost\",\n port: options.port ?? 5432,\n database: options.database,\n user: options.user,\n password: options.password,\n ...options.pool,\n });\n }\n\n this.tableName = options.tableName ?? \"awaitly_workflow_state\";\n }\n\n /**\n * Initialize the store by creating the table if it doesn't exist.\n * This is called automatically on first use.\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = (async () => {\n try {\n await this.createTable();\n this.initialized = true;\n } catch (error) {\n this.initPromise = null;\n throw error;\n }\n })();\n\n return this.initPromise;\n }\n\n /**\n * Create the table if it doesn't exist.\n * Adds updated_at column to existing tables for listKeys ordering.\n */\n private async createTable(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at TIMESTAMP,\n updated_at TIMESTAMPTZ\n )\n `);\n\n // Add updated_at to existing tables that don't have it (before creating index)\n await this.pool.query(`\n ALTER TABLE ${this.tableName} \n ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ\n `);\n\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at \n ON ${this.tableName}(expires_at) \n WHERE expires_at IS NOT NULL\n `);\n\n await this.pool.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_updated_at \n ON ${this.tableName}(updated_at) \n WHERE updated_at IS NOT NULL\n `);\n }\n\n /**\n * Convert glob pattern to SQL LIKE pattern.\n * Supports * wildcard (matches any characters).\n */\n private patternToLike(pattern: string): string {\n // Escape SQL LIKE special characters and convert * to %\n return pattern.replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\").replace(/\\*/g, \"%\");\n }\n\n async get(key: string): Promise<string | null> {\n await this.ensureInitialized();\n\n const query = `\n SELECT value \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ value: string }> = await this.pool.query(query, [key]);\n\n if (result.rows.length === 0) {\n return null;\n }\n\n return result.rows[0].value;\n }\n\n async set(key: string, value: string, options?: { ttl?: number }): Promise<void> {\n await this.ensureInitialized();\n\n const expiresAt = options?.ttl\n ? new Date(Date.now() + options.ttl * 1000)\n : null;\n\n const query = `\n INSERT INTO ${this.tableName} (key, value, expires_at, updated_at)\n VALUES ($1, $2, $3, NOW())\n ON CONFLICT (key) \n DO UPDATE SET \n value = EXCLUDED.value,\n expires_at = EXCLUDED.expires_at,\n updated_at = NOW()\n `;\n\n await this.pool.query(query, [key, value, expiresAt]);\n }\n\n async delete(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `DELETE FROM ${this.tableName} WHERE key = $1`;\n const result = await this.pool.query(query, [key]);\n\n return (result.rowCount ?? 0) > 0;\n }\n\n async exists(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const query = `\n SELECT 1 \n FROM ${this.tableName} \n WHERE key = $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n LIMIT 1\n `;\n\n const result = await this.pool.query(query, [key]);\n return result.rows.length > 0;\n }\n\n async keys(pattern: string): Promise<string[]> {\n await this.ensureInitialized();\n\n // Convert glob pattern to SQL LIKE\n const likePattern = this.patternToLike(pattern);\n\n const query = `\n SELECT key \n FROM ${this.tableName} \n WHERE key LIKE $1 \n AND (expires_at IS NULL OR expires_at > NOW())\n `;\n\n const result: QueryResult<{ key: string }> = await this.pool.query(query, [likePattern]);\n\n return result.rows.map((row) => row.key);\n }\n\n /**\n * List keys with pagination, filtering, and ordering.\n */\n async listKeys(\n pattern: string,\n options: ListPageOptions = {}\n ): Promise<{ keys: string[]; total?: number }> {\n await this.ensureInitialized();\n\n const limit = Math.min(Math.max(0, options.limit ?? 100), 10_000);\n const offset = Math.max(0, options.offset ?? 0);\n const orderBy = options.orderBy === \"key\" ? \"key\" : \"updated_at\";\n const orderDir = options.orderDir === \"asc\" ? \"ASC\" : \"DESC\";\n const likePattern = this.patternToLike(pattern);\n\n const conditions: string[] = [\n \"key LIKE $1\",\n \"(expires_at IS NULL OR expires_at > NOW())\",\n ];\n const args: unknown[] = [likePattern];\n let paramIndex = 2;\n\n if (options.updatedBefore != null) {\n conditions.push(`updated_at < $${paramIndex}`);\n args.push(options.updatedBefore);\n paramIndex++;\n }\n if (options.updatedAfter != null) {\n conditions.push(`updated_at > $${paramIndex}`);\n args.push(options.updatedAfter);\n paramIndex++;\n }\n\n const whereClause = conditions.join(\" AND \");\n const orderNulls = orderBy === \"updated_at\" ? \" NULLS LAST\" : \"\";\n\n const listQuery = `\n SELECT key \n FROM ${this.tableName} \n WHERE ${whereClause}\n ORDER BY ${orderBy} ${orderDir}${orderNulls}\n LIMIT $${paramIndex} OFFSET $${paramIndex + 1}\n `;\n const listArgs = [...args, limit, offset];\n\n const result: QueryResult<{ key: string }> = await this.pool.query(listQuery, listArgs);\n const keys = result.rows.map((row) => row.key);\n\n let total: number | undefined;\n if (options.includeTotal === true || offset > 0) {\n const countResult: QueryResult<{ count: string }> = await this.pool.query(\n `SELECT COUNT(*) AS count FROM ${this.tableName} WHERE ${whereClause}`,\n args\n );\n total = parseInt(countResult.rows[0]?.count ?? \"0\", 10);\n }\n\n return { keys, total };\n }\n\n /**\n * Delete multiple keys in one round-trip.\n */\n async deleteMany(keys: string[]): Promise<number> {\n if (keys.length === 0) return 0;\n await this.ensureInitialized();\n const result = await this.pool.query(\n `DELETE FROM ${this.tableName} WHERE key = ANY($1::text[])`,\n [keys]\n );\n return result.rowCount ?? 0;\n }\n\n /**\n * Remove all entries from the table (clear all workflow state).\n */\n async clear(): Promise<void> {\n await this.ensureInitialized();\n await this.pool.query(`TRUNCATE TABLE ${this.tableName}`);\n }\n\n /**\n * Close the database connection pool.\n * Call this when done with the store.\n */\n async close(): Promise<void> {\n await this.pool.end();\n }\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 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 return { tryAcquire, release, ensureLockTable };\n}\n"],"mappings":"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","names":["PgPool","PgPool","PostgresKeyValueStore","options","error","pattern","key","query","result","value","expiresAt","likePattern","row","limit","offset","orderBy","orderDir","conditions","args","paramIndex","whereClause","orderNulls","listQuery","listArgs","keys","total","countResult","randomUUID","createPostgresLock","pool","options","lockTableName","safeIndexName","ensureLockTable","tryAcquire","id","opts","ttlMs","ownerToken","expiresAt","result","release","createStatePersistence","createPostgresPersistence","options","prefix","lockOptions","storeOptions","stripPrefix","key","effectivePrefix","prefixKey","runId","addListPageAndDeleteMany","statePersistence","store","keys","total","ids","limit","nextOffset","pool","PgPool","PostgresKeyValueStore","lock","createPostgresLock","base"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awaitly-postgres",
3
- "version": "1.0.0",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "PostgreSQL persistence adapter for awaitly workflows",
6
6
  "main": "./dist/index.cjs",
@@ -38,20 +38,20 @@
38
38
  },
39
39
  "license": "MIT",
40
40
  "peerDependencies": {
41
- "awaitly": "^1.11.0"
41
+ "awaitly": "^1.13.0"
42
42
  },
43
43
  "dependencies": {
44
- "pg": "^8.13.1"
44
+ "pg": "^8.17.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@total-typescript/ts-reset": "^0.6.1",
48
48
  "@total-typescript/tsconfig": "^1.0.4",
49
- "@types/node": "^25.0.10",
50
- "@types/pg": "^8.11.10",
49
+ "@types/node": "^25.1.0",
50
+ "@types/pg": "^8.16.0",
51
51
  "tsup": "^8.5.1",
52
52
  "typescript": "^5.9.3",
53
53
  "vitest": "^4.0.18",
54
- "awaitly": "^1.11.0"
54
+ "awaitly": "^1.13.0"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public",