@sanoma-ai/workflows 0.7.0 → 0.8.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 (168) hide show
  1. package/README.md +43 -12
  2. package/dist/apps-list.d.ts +90 -0
  3. package/dist/apps-list.js +109 -0
  4. package/dist/apps-list.js.map +1 -0
  5. package/dist/apps.d.ts +42 -0
  6. package/dist/apps.js +137 -0
  7. package/dist/apps.js.map +1 -0
  8. package/dist/call-workflow.js +5 -6
  9. package/dist/call-workflow.js.map +1 -1
  10. package/dist/call.js +6 -3
  11. package/dist/call.js.map +1 -1
  12. package/dist/cedar.d.ts +1 -1
  13. package/dist/cedar.js +6 -4
  14. package/dist/cedar.js.map +1 -1
  15. package/dist/change-steps.d.ts +10 -0
  16. package/dist/change-steps.js +204 -0
  17. package/dist/change-steps.js.map +1 -0
  18. package/dist/change-workflow.d.ts +54 -0
  19. package/dist/change-workflow.js +161 -0
  20. package/dist/change-workflow.js.map +1 -0
  21. package/dist/change.d.ts +121 -24
  22. package/dist/change.js +196 -17
  23. package/dist/change.js.map +1 -1
  24. package/dist/claims.d.ts +58 -0
  25. package/dist/claims.js +27 -0
  26. package/dist/claims.js.map +1 -0
  27. package/dist/client.d.ts +92 -0
  28. package/dist/client.js +268 -6
  29. package/dist/client.js.map +1 -1
  30. package/dist/collection.d.ts +6 -1
  31. package/dist/collection.js +11 -3
  32. package/dist/collection.js.map +1 -1
  33. package/dist/config.d.ts +56 -8
  34. package/dist/config.js +71 -29
  35. package/dist/config.js.map +1 -1
  36. package/dist/constructors.d.ts +37 -1
  37. package/dist/constructors.js +6 -2
  38. package/dist/constructors.js.map +1 -1
  39. package/dist/db.d.ts +21 -21
  40. package/dist/db.js +31 -54
  41. package/dist/db.js.map +1 -1
  42. package/dist/deadlines.d.ts +18 -12
  43. package/dist/deadlines.js +18 -24
  44. package/dist/deadlines.js.map +1 -1
  45. package/dist/define.d.ts +30 -14
  46. package/dist/define.js +9 -1
  47. package/dist/define.js.map +1 -1
  48. package/dist/demo.d.ts +15 -4
  49. package/dist/demo.js +27 -12
  50. package/dist/demo.js.map +1 -1
  51. package/dist/describe.d.ts +16 -2
  52. package/dist/describe.js +39 -4
  53. package/dist/describe.js.map +1 -1
  54. package/dist/direct.d.ts +4 -3
  55. package/dist/direct.js +7 -4
  56. package/dist/direct.js.map +1 -1
  57. package/dist/errors.d.ts +2 -0
  58. package/dist/errors.js +2 -0
  59. package/dist/errors.js.map +1 -1
  60. package/dist/fake.d.ts +26 -0
  61. package/dist/fake.js +123 -0
  62. package/dist/fake.js.map +1 -1
  63. package/dist/host.d.ts +2 -3
  64. package/dist/host.js +1 -1
  65. package/dist/host.js.map +1 -1
  66. package/dist/index.d.ts +8 -7
  67. package/dist/index.js +7 -6
  68. package/dist/index.js.map +1 -1
  69. package/dist/lint.js +20 -6
  70. package/dist/lint.js.map +1 -1
  71. package/dist/op.d.ts +8 -1
  72. package/dist/op.js.map +1 -1
  73. package/dist/outline.js +13 -6
  74. package/dist/outline.js.map +1 -1
  75. package/dist/paths.d.ts +16 -0
  76. package/dist/paths.js +54 -2
  77. package/dist/paths.js.map +1 -1
  78. package/dist/person.d.ts +5 -0
  79. package/dist/person.js +3 -1
  80. package/dist/person.js.map +1 -1
  81. package/dist/resources.d.ts +7 -2
  82. package/dist/resources.js +27 -10
  83. package/dist/resources.js.map +1 -1
  84. package/dist/run.d.ts +7 -0
  85. package/dist/run.js.map +1 -1
  86. package/dist/scenario.d.ts +2 -2
  87. package/dist/scenario.js +10 -4
  88. package/dist/scenario.js.map +1 -1
  89. package/dist/scenarios.d.ts +1 -1
  90. package/dist/scenarios.js.map +1 -1
  91. package/dist/sdk.d.ts +1 -1
  92. package/dist/sdk.js +19 -5
  93. package/dist/sdk.js.map +1 -1
  94. package/dist/shared.d.ts +43 -2
  95. package/dist/shared.js +46 -4
  96. package/dist/shared.js.map +1 -1
  97. package/dist/sites.d.ts +26 -5
  98. package/dist/sites.js +56 -20
  99. package/dist/sites.js.map +1 -1
  100. package/dist/source.d.ts +179 -5
  101. package/dist/source.js +13 -0
  102. package/dist/source.js.map +1 -1
  103. package/dist/supervisor.d.ts +5 -4
  104. package/dist/supervisor.js +2 -11
  105. package/dist/supervisor.js.map +1 -1
  106. package/dist/version.d.ts +8 -0
  107. package/dist/version.js +21 -13
  108. package/dist/version.js.map +1 -1
  109. package/dist/wakeup-driver.d.ts +128 -0
  110. package/dist/wakeup-driver.js +216 -0
  111. package/dist/wakeup-driver.js.map +1 -0
  112. package/dist/wakeup-service.d.ts +4 -4
  113. package/dist/wakeup-service.js +19 -25
  114. package/dist/wakeup-service.js.map +1 -1
  115. package/dist/wakeup-type.d.ts +43 -0
  116. package/dist/wakeup-type.js +80 -0
  117. package/dist/wakeup-type.js.map +1 -0
  118. package/dist/wakeup.d.ts +1 -8
  119. package/dist/wakeup.js +2 -11
  120. package/dist/wakeup.js.map +1 -1
  121. package/dist/worker.d.ts +2 -2
  122. package/dist/worker.js +50 -7
  123. package/dist/worker.js.map +1 -1
  124. package/oxlint.json +3 -2
  125. package/package.json +3 -1
  126. package/src/apps-list.ts +119 -0
  127. package/src/apps.ts +162 -0
  128. package/src/call-workflow.ts +10 -2
  129. package/src/call.ts +6 -3
  130. package/src/cedar.ts +9 -7
  131. package/src/change-steps.ts +274 -0
  132. package/src/change-workflow.ts +198 -0
  133. package/src/change.ts +257 -27
  134. package/src/claims.ts +79 -0
  135. package/src/client.ts +320 -4
  136. package/src/collection.ts +22 -3
  137. package/src/config.ts +104 -35
  138. package/src/constructors.ts +8 -2
  139. package/src/db.ts +39 -66
  140. package/src/deadlines.ts +27 -30
  141. package/src/define.ts +31 -12
  142. package/src/demo.ts +55 -24
  143. package/src/describe.ts +67 -4
  144. package/src/direct.ts +9 -5
  145. package/src/errors.ts +3 -0
  146. package/src/fake.ts +137 -0
  147. package/src/host.ts +3 -4
  148. package/src/index.ts +22 -3
  149. package/src/lint.ts +21 -6
  150. package/src/op.ts +8 -1
  151. package/src/outline.ts +15 -6
  152. package/src/paths.ts +57 -2
  153. package/src/person.ts +4 -1
  154. package/src/resources.ts +31 -11
  155. package/src/run.ts +5 -0
  156. package/src/scenario.ts +14 -4
  157. package/src/scenarios.ts +1 -1
  158. package/src/sdk.ts +21 -7
  159. package/src/shared.ts +60 -5
  160. package/src/sites.ts +70 -24
  161. package/src/source.ts +168 -5
  162. package/src/supervisor.ts +7 -12
  163. package/src/version.ts +25 -16
  164. package/src/wakeup-driver.ts +270 -0
  165. package/src/wakeup-service.ts +21 -28
  166. package/src/wakeup-type.ts +90 -0
  167. package/src/wakeup.ts +6 -17
  168. package/src/worker.ts +48 -9
package/src/worker.ts CHANGED
@@ -1,23 +1,22 @@
1
1
  import { DBOS, type ScheduledWorkflowFn } from "@dbos-inc/dbos-sdk";
2
- import { randomUUID } from "node:crypto";
3
2
  import { awaitApproval } from "./approvals.ts";
4
3
  import { buildCtx, decideStart, isInfrastructureError, startHoldOf } from "./call.ts";
5
4
  import { dbosStatusesOf } from "./client.ts";
6
5
  import { declaredWakeup, type ResolvedConfig, resolveConfig, type SanomaConfig } from "./config.ts";
7
6
  import { storedSecrets, watchCredentials } from "./credentials.ts";
8
- import { claimWakeup, DBOS_DATABASE_OPTIONS, lazyPool, prepareDatabase } from "./db.ts";
7
+ import { type Db, DBOS_DATABASE_OPTIONS, lazyPool, prepareDatabase, stampVersion } from "./db.ts";
9
8
  import { deadlineRegistry, type Deadlines, idle, onOwnQueue, type SelfStop, stopWhenIdle } from "./deadlines.ts";
10
9
  import type { WorkflowDefinition } from "./define.ts";
11
10
  import { credentialsOf, unconfigured, unconfiguredSummary } from "./env.ts";
12
11
  import { errorInfo, parseOrThrow, PolicyDeniedError } from "./errors.ts";
13
12
  import { entry, ownedLedger, skipped, write, writeFailure } from "./ledger.ts";
14
- import { dbosLogger, quietIfSandbox, setLogLevel, warn } from "./log.ts";
13
+ import { dbosLogger, info, quietIfSandbox, setLogLevel, warn } from "./log.ts";
15
14
  import { lineStartsOf, readSource } from "./ast.ts";
16
15
  import { callsOf, outlineWithSource } from "./outline.ts";
17
16
  import { currentRun, type Run, type RunArgs, type WorkerState, type WorkflowOutline } from "./run.ts";
18
17
  import { applyScheduled, nextScheduledFire, type ScheduledStarter, scheduledStarter } from "./schedules.ts";
19
18
  import type { Source } from "./source.ts";
20
- import { approverLabel, errorMessage, WORKFLOW_ACTION } from "./shared.ts";
19
+ import { approverLabel, CHANGE_WORKFLOW, errorMessage, WORKFLOW_ACTION } from "./shared.ts";
21
20
 
22
21
  export interface Worker {
23
22
  /**
@@ -85,8 +84,8 @@ export interface WorkerOptions {
85
84
  /**
86
85
  * Given, and with a wakeup declared, the worker stops itself when idle (`stopCheckMs`); without
87
86
  * it, nobody would start another, so it never does. Called once the worker has stopped itself, with the deadline it registered with the
88
- * wakeup (null for none) and that registration's generation: a supervisor starts a fresh
89
- * worker on the next request or wake (`shouldWake`), since a stopped one cannot launch again.
87
+ * wakeup (null for none): a supervisor starts a fresh worker on the next write or wake, since a
88
+ * stopped one cannot launch again.
90
89
  */
91
90
  onStopped?: (stopped: SelfStop) => void;
92
91
  /**
@@ -145,6 +144,12 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
145
144
  secrets: storedSecrets(() => secrets.db(), "sanoma"),
146
145
  workflows: resolved.workflows,
147
146
  outlines: new Map([...resolved.workflows].map(([name, wf]) => [name, outlineOf(wf)])),
147
+ // `registered` is filled below, before any run starts: every name of this config's is registered then.
148
+ startRun: async (name, runId, args) => {
149
+ const fn = registered.get(name) as unknown as ((args: RunArgs) => Promise<unknown>) | undefined;
150
+ if (!fn) throw new Error(`The worker has no workflow "${name}" to start`);
151
+ return DBOS.startWorkflow(fn, { workflowID: runId })(args);
152
+ },
148
153
  stopped: false,
149
154
  deadlines: deadlineRegistry(),
150
155
  };
@@ -217,6 +222,8 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
217
222
  );
218
223
  }
219
224
  }
225
+ // Change runs move to the latest version only: a rollback's worker, unless promoted, leaves them be.
226
+ if (isLatest) await carryChangeRuns(resolved, () => secrets.db());
220
227
  await DBOS.registerQueue(resolved.queueName);
221
228
  // The schedules are the latest version's: an older worker leaves them as the newer set them.
222
229
  if (isLatest && options.schedules !== false) {
@@ -231,13 +238,10 @@ export async function startWorker(config: SanomaConfig, options: WorkerOptions =
231
238
  warn(`the policies do not hold, so every call and start is refused:\n ${resolved.policyProblems.join("\n ")}`);
232
239
  }
233
240
  if (wakeup && options.onStopped) {
234
- const owner = randomUUID();
235
- await claimWakeup(await secrets.db(), owner);
236
241
  const registerId = wakeup.type.wakeup!.register;
237
242
  stopRule = stopWhenIdle({
238
243
  // The row is one more of the runtime's tables: the secrets' pool, on the same database.
239
244
  db: () => secrets.db(),
240
- owner,
241
245
  wakeup: wakeup.binding ?? wakeup.id,
242
246
  // `resolveConfig` checked the operation is the config's (`checkWakeupOps`).
243
247
  register: { op: resolved.ops.get(registerId)!, fn: resolved.drivers.get(registerId) },
@@ -286,6 +290,41 @@ function outlineOf(wf: WorkflowDefinition<any, any>): WorkflowOutline {
286
290
  return { file: outline.file, lineStarts: lineStartsOf(source!), calls: callsOf(outline.nodes) };
287
291
  }
288
292
 
293
+ /**
294
+ * Takes over the unfinished change runs of other versions whose change code is this worker's,
295
+ * keeping each run's id, history and unread messages: a change's own deploy restarts the worker on
296
+ * the merged code, a new version, which DBOS would never give the run to. Each is stamped with this
297
+ * version (`stampVersion`), and a PENDING one queued again on the app's queue (`resumeWorkflows`);
298
+ * one stopped between the two is PENDING on this version, which DBOS's recovery takes. Not
299
+ * `rewindWorkflow`, which takes only a cancelled run and deletes its unread messages. It assumes one
300
+ * live worker per database, as a deploy does: an older one still running such a run runs it on
301
+ * until it stops. A failure is warned about, and the worker starts.
302
+ */
303
+ async function carryChangeRuns({ appName, version, changeCode, queueName }: ResolvedConfig, db: () => Promise<Db>) {
304
+ try {
305
+ const runs = await DBOS.listWorkflows({
306
+ workflowName: CHANGE_WORKFLOW,
307
+ status: ["PENDING", "ENQUEUED"],
308
+ applicationName: appName,
309
+ attributes: { changeCode },
310
+ loadInput: false,
311
+ loadOutput: false,
312
+ });
313
+ const theirs = runs
314
+ .filter((r) => r.applicationVersion && r.applicationVersion !== version)
315
+ .map((r) => r.workflowID);
316
+ if (!theirs.length) return;
317
+ const carried = await stampVersion(await db(), theirs, version);
318
+ const pending = carried.filter((r) => r.pending).map((r) => r.id);
319
+ if (pending.length) await DBOS.resumeWorkflows(pending, { queueName });
320
+ if (carried.length) {
321
+ info(`carried ${carried.length} change run(s) onto version ${version}: ${carried.map((r) => r.id).join(", ")}`);
322
+ }
323
+ } catch (err) {
324
+ warn(`carrying the change runs of other versions onto ${version} failed, so they stay there: ${errorMessage(err)}`);
325
+ }
326
+ }
327
+
289
328
  /**
290
329
  * Warns about unfinished runs this worker will never pick up: started on another version of
291
330
  * the app, or queued on another queue (such as the single "sanoma" queue before queues were