@adonis-agora/durable 0.20.0 → 0.21.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/src/codec-state-store.d.ts +2 -1
  3. package/dist/src/codec-state-store.d.ts.map +1 -1
  4. package/dist/src/codec-state-store.js +8 -0
  5. package/dist/src/codec-state-store.js.map +1 -1
  6. package/dist/src/control-plane-redis/index.d.ts +1 -1
  7. package/dist/src/control-plane-redis/index.d.ts.map +1 -1
  8. package/dist/src/control-plane-redis/index.js.map +1 -1
  9. package/dist/src/control-plane-redis/redis-control-plane.d.ts +70 -29
  10. package/dist/src/control-plane-redis/redis-control-plane.d.ts.map +1 -1
  11. package/dist/src/control-plane-redis/redis-control-plane.js +16 -6
  12. package/dist/src/control-plane-redis/redis-control-plane.js.map +1 -1
  13. package/dist/src/dashboard/index.d.ts +1 -1
  14. package/dist/src/dashboard/index.js +1 -1
  15. package/dist/src/engine.d.ts +10 -1
  16. package/dist/src/engine.d.ts.map +1 -1
  17. package/dist/src/engine.js +86 -18
  18. package/dist/src/engine.js.map +1 -1
  19. package/dist/src/index.d.ts +3 -2
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +5 -1
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/interfaces.d.ts +16 -0
  24. package/dist/src/interfaces.d.ts.map +1 -1
  25. package/dist/src/step-discovery.d.ts +3 -2
  26. package/dist/src/step-discovery.d.ts.map +1 -1
  27. package/dist/src/step-discovery.js +8 -5
  28. package/dist/src/step-discovery.js.map +1 -1
  29. package/dist/src/stores/lucid-schema.d.ts +18 -1
  30. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  31. package/dist/src/stores/lucid-schema.js +44 -1
  32. package/dist/src/stores/lucid-schema.js.map +1 -1
  33. package/dist/src/stores/lucid.d.ts +2 -1
  34. package/dist/src/stores/lucid.d.ts.map +1 -1
  35. package/dist/src/stores/lucid.js +31 -0
  36. package/dist/src/stores/lucid.js.map +1 -1
  37. package/dist/src/testing/in-memory-state-store.d.ts +2 -1
  38. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  39. package/dist/src/testing/in-memory-state-store.js +12 -0
  40. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  41. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  42. package/dist/src/testing-kit/state-store-conformance.js +47 -0
  43. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  44. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  45. package/dist/src/transports/bullmq/bullmq-transport.js +1 -1
  46. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  47. package/dist/src/workflow-discovery.d.ts +33 -3
  48. package/dist/src/workflow-discovery.d.ts.map +1 -1
  49. package/dist/src/workflow-discovery.js +40 -8
  50. package/dist/src/workflow-discovery.js.map +1 -1
  51. package/dist/stubs/database/migrations/create_durable_tables.stub +33 -89
  52. package/package.json +7 -4
@@ -2,102 +2,46 @@
2
2
  exports({ to: app.migrationsPath(`${new Date().getTime()}_create_durable_tables.ts`) })
3
3
  }}}
4
4
  import { BaseSchema } from '@adonisjs/lucid/schema'
5
+ import db from '@adonisjs/lucid/services/db'
6
+ import { createDurableTables, dropDurableTables } from '@adonis-agora/durable'
5
7
 
6
8
  /**
7
- * Durable workflow tables for the @adonis-agora/durable `lucid` state store: runs, step checkpoints, the
8
- * search-attribute side-table, signal waiters, buffered signals and buffered events. JSON payloads are stored as TEXT (the store
9
- * (de)serializes them) and timestamps as epoch-ms integers, so the schema is portable across
10
- * SQLite / Postgres / MySQL.
9
+ * Durable workflow tables for the @adonis-agora/durable `lucid` state store: runs, step checkpoints,
10
+ * the search-attribute side-table, signal waiters, buffered signals and buffered events.
11
+ *
12
+ * This migration DELEGATES to the library instead of reproducing its DDL. The schema belongs to the
13
+ * library — `LucidStateStore` decides what columns it reads and writes — so a hand-copied snapshot
14
+ * here can only ever be right until the next release. It was wrong before: the stub shipped without
15
+ * `last_heartbeat_at` / `heartbeat_progress`, which the store writes on every step beat, and nothing
16
+ * failed only because the provider's boot-time `ensureSchema()` silently `ALTER`ed them in. Calling
17
+ * `createDurableTables` makes that drift impossible rather than merely tested for.
18
+ *
19
+ * `createDurableTables` is idempotent (`hasTable` / `hasColumn` guarded) and additive-only, so it is
20
+ * safe to run against a database that already has some or all of the tables.
21
+ *
22
+ * `db` (the `Database` manager from `@adonisjs/lucid/services/db`) rather than `this.db`:
23
+ * `createDurableTables` needs `db.connection(name).schema`, and the `this.db` a migration gets is a
24
+ * `QueryClientContract` — one already-resolved client, with no `.connection()`. `this.db` still
25
+ * supplies the connection NAME, so `node ace migration:run --connection=x` provisions the tables on
26
+ * connection `x` and not on the default.
11
27
  */
12
28
  export default class extends BaseSchema {
13
- async up() {
14
- this.schema.createTable('durable_workflow_runs', (table) => {
15
- table.string('id').primary()
16
- table.string('workflow').notNullable()
17
- table.string('workflow_version').notNullable()
18
- table.string('status').notNullable()
19
- table.string('namespace').notNullable().defaultTo('default')
20
- table.text('input')
21
- table.text('output')
22
- table.text('error')
23
- table.bigInteger('wake_at')
24
- table.string('locked_by')
25
- table.bigInteger('locked_until')
26
- table.integer('recovery_attempts')
27
- table.text('tags')
28
- table.text('search_attributes')
29
- table.integer('priority')
30
- table.bigInteger('created_at').notNullable()
31
- table.bigInteger('updated_at').notNullable()
32
- table.index(['status'], 'durable_runs_status_idx')
33
- table.index(['status', 'wake_at'], 'durable_runs_due_idx')
34
- table.index(['namespace', 'status', 'created_at'], 'durable_runs_namespace_idx')
35
- })
36
-
37
- this.schema.createTable('durable_step_checkpoints', (table) => {
38
- table.string('run_id').notNullable()
39
- table.integer('seq').notNullable()
40
- table.string('name').notNullable()
41
- table.string('kind').notNullable()
42
- table.string('step_id').notNullable()
43
- table.string('status').notNullable()
44
- table.text('input')
45
- table.text('output')
46
- table.text('error')
47
- table.text('events')
48
- table.integer('attempts').notNullable()
49
- table.string('worker_group')
50
- table.bigInteger('wake_at')
51
- table.string('parallel_group')
52
- table.bigInteger('enqueued_at')
53
- table.bigInteger('started_at').notNullable()
54
- table.bigInteger('finished_at').notNullable()
55
- table.primary(['run_id', 'seq'])
56
- table.index(['run_id', 'name'], 'durable_checkpoints_name_idx')
57
- })
58
-
59
- this.schema.createTable('durable_run_attributes', (table) => {
60
- table.string('run_id').notNullable()
61
- table.string('key').notNullable()
62
- table.string('str_value')
63
- table.double('num_value')
64
- table.primary(['run_id', 'key'])
65
- table.index(['key', 'num_value'], 'durable_run_attributes_num_idx')
66
- table.index(['key', 'str_value'], 'durable_run_attributes_str_idx')
67
- })
29
+ /**
30
+ * REQUIRED, not a preference. The DDL below runs through the `Database` manager, which checks out
31
+ * its OWN connection from the pool — it is not part of the transaction the migrator would otherwise
32
+ * open for this migration. With `pool: { max: 1 }` (Adonis's own SQLite guidance) that transaction
33
+ * would hold the only connection while `createDurableTables` waited for a free one, and the
34
+ * migration would hang until the acquire timeout. Nothing is lost by opting out: the statements were
35
+ * never inside that transaction anyway, and re-running an interrupted `createDurableTables` is a
36
+ * no-op.
37
+ */
38
+ static disableTransactions = true
68
39
 
69
- this.schema.createTable('durable_signal_waiters', (table) => {
70
- table.string('token').primary()
71
- table.string('run_id').notNullable()
72
- table.integer('seq').notNullable()
73
- table.string('parallel_group')
74
- })
75
-
76
- this.schema.createTable('durable_buffered_signals', (table) => {
77
- table.increments('id').primary()
78
- table.string('token').notNullable()
79
- table.text('payload')
80
- table.index(['token'], 'durable_buffered_signals_token_idx')
81
- })
82
-
83
- // Reliable (buffered) events: a publish matching no live waiter keeps ONE copy here, consumed by
84
- // the first future matching `waitForEvent`. Keyed by `name` (many waiters share a name with
85
- // different `match`); `id` is the caller-minted PK; `published_at` is epoch-ms.
86
- this.schema.createTable('durable_buffered_events', (table) => {
87
- table.string('id').primary()
88
- table.string('name').notNullable()
89
- table.text('payload')
90
- table.bigInteger('published_at').notNullable()
91
- table.index(['name', 'published_at'], 'durable_buffered_events_name_idx')
92
- })
40
+ async up() {
41
+ await createDurableTables(db, this.db.connectionName)
93
42
  }
94
43
 
95
44
  async down() {
96
- this.schema.dropTableIfExists('durable_buffered_events')
97
- this.schema.dropTableIfExists('durable_buffered_signals')
98
- this.schema.dropTableIfExists('durable_signal_waiters')
99
- this.schema.dropTableIfExists('durable_run_attributes')
100
- this.schema.dropTableIfExists('durable_step_checkpoints')
101
- this.schema.dropTableIfExists('durable_workflow_runs')
45
+ await dropDurableTables(db, this.db.connectionName)
102
46
  }
103
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/durable",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Durable cross-app workflows for AdonisJS — deterministic replay engine, AdonisJS binding, ace commands, dashboard, OpenTelemetry, Telescope, testing harness and Redis admission, all in one package. Part of the Agora ecosystem.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -158,6 +158,7 @@
158
158
  "@opentelemetry/api": "^1.9.0",
159
159
  "@opentelemetry/context-async-hooks": "^1.30.0",
160
160
  "@opentelemetry/sdk-trace-base": "^1.30.0",
161
+ "@types/better-sqlite3": "^7.6.0",
161
162
  "@types/node": "^20.0.0",
162
163
  "better-sqlite3": "^11.0.0",
163
164
  "bullmq": "^5.0.0",
@@ -168,7 +169,7 @@
168
169
  "zod": "^3.25.0"
169
170
  },
170
171
  "engines": {
171
- "node": ">=20.6.0"
172
+ "node": ">=20.6.0 <23"
172
173
  },
173
174
  "keywords": [
174
175
  "adonisjs",
@@ -184,10 +185,12 @@
184
185
  "agora"
185
186
  ],
186
187
  "scripts": {
187
- "build": "tsc -p tsconfig.json && pnpm run copy:stubs",
188
+ "build": "pnpm run clean && tsc -p tsconfig.build.json && pnpm run copy:stubs && pnpm run check:dist",
189
+ "check:dist": "node ../../scripts/assert-build-output.mjs dist src/index.js",
190
+ "clean": "rm -rf dist",
188
191
  "copy:stubs": "mkdir -p dist/stubs/config dist/stubs/database/migrations dist/stubs/make/workflow dist/assets && cp stubs/config/durable.stub dist/stubs/config/durable.stub && cp stubs/config/durable_dashboard.stub dist/stubs/config/durable_dashboard.stub && cp -r stubs/database/migrations/. dist/stubs/database/migrations/ && cp stubs/make/workflow/main.stub dist/stubs/make/workflow/main.stub && cp assets/dashboard.html dist/assets/dashboard.html",
189
192
  "test": "vitest run --passWithNoTests",
190
193
  "test:watch": "vitest",
191
- "typecheck": "tsc -p tsconfig.json --noEmit"
194
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json"
192
195
  }
193
196
  }