@dudousxd/nestjs-durable-store-drizzle 0.22.0 → 0.24.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/pg/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(pg_exports, {
25
25
  DURABLE_PG_DDL: () => DURABLE_PG_DDL,
26
26
  DrizzlePgStateStore: () => DrizzlePgStateStore,
27
27
  drizzlePgExecutor: () => drizzlePgExecutor,
28
+ drizzlePgSchemaRepairWarning: () => drizzlePgSchemaRepairWarning,
28
29
  durableBufferedEvents: () => durableBufferedEvents,
29
30
  durableBufferedSignals: () => durableBufferedSignals,
30
31
  durablePgManagedTables: () => durablePgManagedTables,
@@ -41,7 +42,41 @@ __export(pg_exports, {
41
42
  module.exports = __toCommonJS(pg_exports);
42
43
 
43
44
  // src/pg/ddl.ts
45
+ var import_drizzle_orm2 = require("drizzle-orm");
46
+
47
+ // src/pg/executor.ts
44
48
  var import_drizzle_orm = require("drizzle-orm");
49
+ function toDrizzleSql(text2, params = []) {
50
+ const chunks = [];
51
+ let last = 0;
52
+ for (const match of text2.matchAll(/\$(\d+)/g)) {
53
+ const index2 = Number(match[1]) - 1;
54
+ if (index2 < 0 || index2 >= params.length) throw new Error(`placeholder ${match[0]} has no bound parameter`);
55
+ chunks.push(import_drizzle_orm.sql.raw(text2.slice(last, match.index)), import_drizzle_orm.sql`${params[index2]}`);
56
+ last = (match.index ?? 0) + match[0].length;
57
+ }
58
+ chunks.push(import_drizzle_orm.sql.raw(text2.slice(last)));
59
+ return import_drizzle_orm.sql.join(chunks);
60
+ }
61
+ __name(toDrizzleSql, "toDrizzleSql");
62
+ function rowsOf(result) {
63
+ if (Array.isArray(result)) return result;
64
+ return result?.rows ?? [];
65
+ }
66
+ __name(rowsOf, "rowsOf");
67
+ function drizzlePgExecutor(db) {
68
+ return {
69
+ dialect: "postgres",
70
+ escapeId: /* @__PURE__ */ __name((id) => `"${id.replace(/"/g, '""')}"`, "escapeId"),
71
+ query: /* @__PURE__ */ __name(async (text2, params) => rowsOf(await db.execute(toDrizzleSql(text2, params))), "query"),
72
+ transaction: /* @__PURE__ */ __name((fn) => db.transaction((tx) => fn({
73
+ query: /* @__PURE__ */ __name(async (text2, params) => rowsOf(await tx.execute(toDrizzleSql(text2, params))), "query")
74
+ })), "transaction")
75
+ };
76
+ }
77
+ __name(drizzlePgExecutor, "drizzlePgExecutor");
78
+
79
+ // src/pg/ddl.ts
45
80
  var DURABLE_PG_DDL = [
46
81
  `CREATE TABLE IF NOT EXISTS "durable_workflow_runs" (
47
82
  "id" text PRIMARY KEY NOT NULL,
@@ -134,11 +169,71 @@ var DURABLE_PG_DDL = [
134
169
  `CREATE INDEX IF NOT EXISTS "durable_schedules_due_idx" ON "durable_schedules" USING btree ("paused","next_fire_at")`
135
170
  ];
136
171
  var SCHEMA_LOCK_KEY = "durable_schema";
137
- async function ensureDrizzlePgDurableSchema(db) {
138
- await db.transaction(async (tx) => {
139
- await tx.execute(import_drizzle_orm.sql`SELECT pg_advisory_xact_lock(hashtext(${SCHEMA_LOCK_KEY}))`);
140
- for (const statement of DURABLE_PG_DDL) await tx.execute(import_drizzle_orm.sql.raw(statement));
172
+ function drizzlePgSchemaRepairWarning(repairs) {
173
+ return [
174
+ `[nestjs-durable] store-drizzle repaired the durable schema at boot: ${repairs.join("; ")}.`,
175
+ "The database had drifted from the schema this library version expects (it was created by an",
176
+ "older version, or a migration left these out), so they were applied at runtime and nothing",
177
+ "recorded them in your migration history. To make them permanent, generate a drizzle-kit",
178
+ "migration from `durablePgSchema` (or apply `DURABLE_PG_DDL` in a migration). This warning",
179
+ "appears only when a repair was applied; a schema that is already current logs nothing."
180
+ ].join(" ");
181
+ }
182
+ __name(drizzlePgSchemaRepairWarning, "drizzlePgSchemaRepairWarning");
183
+ function ddlTarget(statement) {
184
+ const table = statement.match(/^CREATE TABLE IF NOT EXISTS "([^"]+)"/i);
185
+ if (table?.[1]) return {
186
+ kind: "table",
187
+ table: table[1]
188
+ };
189
+ const index2 = statement.match(/^CREATE (?:UNIQUE )?INDEX (?:CONCURRENTLY )?IF NOT EXISTS "([^"]+)" ON "([^"]+)"/i);
190
+ if (index2?.[1] && index2[2]) return {
191
+ kind: "index",
192
+ index: index2[1],
193
+ table: index2[2]
194
+ };
195
+ const column = statement.match(/^ALTER TABLE "([^"]+)" ADD COLUMN IF NOT EXISTS "([^"]+)"/i);
196
+ if (column?.[1] && column[2]) return {
197
+ kind: "column",
198
+ table: column[1],
199
+ column: column[2]
200
+ };
201
+ return void 0;
202
+ }
203
+ __name(ddlTarget, "ddlTarget");
204
+ async function ensureDrizzlePgDurableSchema(db, options = {}) {
205
+ const logger = options.logger ?? console;
206
+ const { repairs, freshInstall } = await db.transaction(async (tx) => {
207
+ await tx.execute(import_drizzle_orm2.sql`SELECT pg_advisory_xact_lock(hashtext(${SCHEMA_LOCK_KEY}))`);
208
+ const tables = new Set(rowsOf(await tx.execute(import_drizzle_orm2.sql.raw("SELECT tablename AS name FROM pg_tables WHERE schemaname = current_schema() AND left(tablename, 8) = 'durable_'"))).map((row) => row.name));
209
+ const indexes = new Set(rowsOf(await tx.execute(import_drizzle_orm2.sql.raw("SELECT indexname AS name FROM pg_indexes WHERE schemaname = current_schema() AND left(tablename, 8) = 'durable_'"))).map((row) => row.name));
210
+ const columns = new Set(rowsOf(await tx.execute(import_drizzle_orm2.sql.raw("SELECT table_name || '.' || column_name AS name FROM information_schema.columns WHERE table_schema = current_schema() AND left(table_name, 8) = 'durable_'"))).map((row) => row.name));
211
+ const created = /* @__PURE__ */ new Set();
212
+ const ddlTables = /* @__PURE__ */ new Set();
213
+ const applied = [];
214
+ for (const statement of DURABLE_PG_DDL) {
215
+ const target = ddlTarget(statement);
216
+ await tx.execute(import_drizzle_orm2.sql.raw(statement));
217
+ if (!target) continue;
218
+ if (target.kind === "table") {
219
+ ddlTables.add(target.table);
220
+ if (!tables.has(target.table)) {
221
+ created.add(target.table);
222
+ applied.push(`created table ${target.table}`);
223
+ }
224
+ } else if (created.has(target.table)) {
225
+ } else if (target.kind === "index" && !indexes.has(target.index)) {
226
+ applied.push(`created index ${target.index} on ${target.table}`);
227
+ } else if (target.kind === "column" && !columns.has(`${target.table}.${target.column}`)) {
228
+ applied.push(`added column ${target.table}.${target.column}`);
229
+ }
230
+ }
231
+ return {
232
+ repairs: applied,
233
+ freshInstall: created.size === ddlTables.size
234
+ };
141
235
  });
236
+ if (repairs.length > 0 && !freshInstall) logger.warn(drizzlePgSchemaRepairWarning(repairs));
142
237
  }
143
238
  __name(ensureDrizzlePgDurableSchema, "ensureDrizzlePgDurableSchema");
144
239
 
@@ -146,38 +241,6 @@ __name(ensureDrizzlePgDurableSchema, "ensureDrizzlePgDurableSchema");
146
241
  var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
147
242
  var import_drizzle_orm4 = require("drizzle-orm");
148
243
 
149
- // src/pg/executor.ts
150
- var import_drizzle_orm2 = require("drizzle-orm");
151
- function toDrizzleSql(text2, params = []) {
152
- const chunks = [];
153
- let last = 0;
154
- for (const match of text2.matchAll(/\$(\d+)/g)) {
155
- const index2 = Number(match[1]) - 1;
156
- if (index2 < 0 || index2 >= params.length) throw new Error(`placeholder ${match[0]} has no bound parameter`);
157
- chunks.push(import_drizzle_orm2.sql.raw(text2.slice(last, match.index)), import_drizzle_orm2.sql`${params[index2]}`);
158
- last = (match.index ?? 0) + match[0].length;
159
- }
160
- chunks.push(import_drizzle_orm2.sql.raw(text2.slice(last)));
161
- return import_drizzle_orm2.sql.join(chunks);
162
- }
163
- __name(toDrizzleSql, "toDrizzleSql");
164
- function rowsOf(result) {
165
- if (Array.isArray(result)) return result;
166
- return result?.rows ?? [];
167
- }
168
- __name(rowsOf, "rowsOf");
169
- function drizzlePgExecutor(db) {
170
- return {
171
- dialect: "postgres",
172
- escapeId: /* @__PURE__ */ __name((id) => `"${id.replace(/"/g, '""')}"`, "escapeId"),
173
- query: /* @__PURE__ */ __name(async (text2, params) => rowsOf(await db.execute(toDrizzleSql(text2, params))), "query"),
174
- transaction: /* @__PURE__ */ __name((fn) => db.transaction((tx) => fn({
175
- query: /* @__PURE__ */ __name(async (text2, params) => rowsOf(await tx.execute(toDrizzleSql(text2, params))), "query")
176
- })), "transaction")
177
- };
178
- }
179
- __name(drizzlePgExecutor, "drizzlePgExecutor");
180
-
181
244
  // src/pg/schema.ts
182
245
  var import_drizzle_orm3 = require("drizzle-orm");
183
246
  var import_pg_core = require("drizzle-orm/pg-core");
@@ -349,7 +412,14 @@ function anyOf(conditions) {
349
412
  }
350
413
  __name(anyOf, "anyOf");
351
414
  function namespaceFilter(namespace) {
352
- return namespace === void 0 ? void 0 : (0, import_drizzle_orm4.eq)(durableWorkflowRuns.namespace, namespace);
415
+ if (namespace === void 0) return void 0;
416
+ if (namespace === DEFAULT_NAMESPACE || namespace === "") {
417
+ return (0, import_drizzle_orm4.inArray)(durableWorkflowRuns.namespace, [
418
+ DEFAULT_NAMESPACE,
419
+ ""
420
+ ]);
421
+ }
422
+ return (0, import_drizzle_orm4.eq)(durableWorkflowRuns.namespace, namespace);
353
423
  }
354
424
  __name(namespaceFilter, "namespaceFilter");
355
425
  function hasTag(tag) {
@@ -383,7 +453,9 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
383
453
  /** Idempotent CREATE TABLE / INDEX IF NOT EXISTS under an advisory lock — called on boot when the
384
454
  * module's `autoSchema` is on. Turn `autoSchema` off if drizzle-kit owns these tables. */
385
455
  async ensureSchema() {
386
- await ensureDrizzlePgDurableSchema(this.db);
456
+ await ensureDrizzlePgDurableSchema(this.db, {
457
+ logger: this.options.schemaLogger
458
+ });
387
459
  }
388
460
  /** The read-scope predicate on the runs table (undefined when the store is unscoped). */
389
461
  scope() {
@@ -548,12 +620,17 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
548
620
  ]), namespaceFilter(namespace), this.scope()));
549
621
  return rows.map(fromRunRow);
550
622
  }
551
- async listOrphanedRuns(nowMs, limit, namespace) {
623
+ async listOrphanedRuns(nowMs, limit, namespace, ownedBy) {
552
624
  if (limit <= 0) return [];
553
625
  const rows = await this.db.select().from(durableWorkflowRuns).where((0, import_drizzle_orm4.and)((0, import_drizzle_orm4.inArray)(durableWorkflowRuns.status, [
554
626
  "running",
555
627
  "cancelling"
556
- ]), (0, import_drizzle_orm4.or)((0, import_drizzle_orm4.isNull)(durableWorkflowRuns.lockedUntil), (0, import_drizzle_orm4.lte)(durableWorkflowRuns.lockedUntil, new Date(nowMs))), namespaceFilter(namespace), this.scope())).orderBy((0, import_drizzle_orm4.asc)(durableWorkflowRuns.createdAt)).limit(limit);
628
+ ]), (0, import_drizzle_orm4.or)(
629
+ (0, import_drizzle_orm4.isNull)(durableWorkflowRuns.lockedUntil),
630
+ (0, import_drizzle_orm4.lte)(durableWorkflowRuns.lockedUntil, new Date(nowMs)),
631
+ // A live lease left by a previous process with the caller's own instance id.
632
+ ownedBy ? (0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(durableWorkflowRuns.lockedBy, ownedBy.owner), (0, import_drizzle_orm4.lt)(durableWorkflowRuns.lockedUntil, new Date(ownedBy.lockedBefore))) : void 0
633
+ ), namespaceFilter(namespace), this.scope())).orderBy((0, import_drizzle_orm4.asc)(durableWorkflowRuns.createdAt)).limit(limit);
557
634
  return rows.map(fromRunRow);
558
635
  }
559
636
  /** FIFO pick-up, served by `durable_workflow_runs_namespace_status_idx`. The engine then leases
@@ -576,11 +653,11 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
576
653
  });
577
654
  return rows.length === 1;
578
655
  }
579
- async releaseRunLock(runId) {
656
+ async releaseRunLock(runId, owner) {
580
657
  await this.db.update(durableWorkflowRuns).set({
581
658
  lockedBy: null,
582
659
  lockedUntil: null
583
- }).where((0, import_drizzle_orm4.eq)(durableWorkflowRuns.id, runId));
660
+ }).where(owner === void 0 ? (0, import_drizzle_orm4.eq)(durableWorkflowRuns.id, runId) : (0, import_drizzle_orm4.and)((0, import_drizzle_orm4.eq)(durableWorkflowRuns.id, runId), (0, import_drizzle_orm4.eq)(durableWorkflowRuns.lockedBy, owner)));
584
661
  }
585
662
  async renewRunLock(runId, owner, leaseUntilMs) {
586
663
  const rows = await this.db.update(durableWorkflowRuns).set({
@@ -971,7 +1048,7 @@ function toRunRow(run) {
971
1048
  tags: run.tags ?? null,
972
1049
  searchAttributes: run.searchAttributes ?? null,
973
1050
  priority: run.priority ?? null,
974
- namespace: run.namespace ?? DEFAULT_NAMESPACE,
1051
+ namespace: run.namespace || DEFAULT_NAMESPACE,
975
1052
  origin: run.origin ?? null,
976
1053
  createdAt: run.createdAt,
977
1054
  updatedAt: run.updatedAt
@@ -994,7 +1071,7 @@ function toRunPatch(patch) {
994
1071
  if ("tags" in patch) row.tags = patch.tags ?? null;
995
1072
  if ("searchAttributes" in patch) row.searchAttributes = patch.searchAttributes ?? null;
996
1073
  if ("priority" in patch) row.priority = patch.priority ?? null;
997
- if ("namespace" in patch) row.namespace = patch.namespace ?? DEFAULT_NAMESPACE;
1074
+ if ("namespace" in patch) row.namespace = patch.namespace || DEFAULT_NAMESPACE;
998
1075
  if ("origin" in patch) row.origin = patch.origin ?? null;
999
1076
  if (patch.createdAt != null) row.createdAt = patch.createdAt;
1000
1077
  if (patch.updatedAt != null) row.updatedAt = patch.updatedAt;
@@ -1018,7 +1095,7 @@ function fromRunRow(row) {
1018
1095
  tags: row.tags ?? void 0,
1019
1096
  searchAttributes: row.searchAttributes ?? void 0,
1020
1097
  priority: row.priority ?? void 0,
1021
- namespace: row.namespace,
1098
+ namespace: row.namespace || DEFAULT_NAMESPACE,
1022
1099
  origin: row.origin ?? void 0,
1023
1100
  createdAt: row.createdAt,
1024
1101
  updatedAt: row.updatedAt
@@ -1128,6 +1205,7 @@ __name(excludedSet, "excludedSet");
1128
1205
  DURABLE_PG_DDL,
1129
1206
  DrizzlePgStateStore,
1130
1207
  drizzlePgExecutor,
1208
+ drizzlePgSchemaRepairWarning,
1131
1209
  durableBufferedEvents,
1132
1210
  durableBufferedSignals,
1133
1211
  durablePgManagedTables,