@sanoma-ai/workflows 0.1.0 → 0.2.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 (99) hide show
  1. package/README.md +72 -21
  2. package/dist/apply-steps.d.ts +6 -0
  3. package/dist/apply-steps.js +93 -0
  4. package/dist/apply-steps.js.map +1 -0
  5. package/dist/apply.d.ts +197 -0
  6. package/dist/apply.js +470 -0
  7. package/dist/apply.js.map +1 -0
  8. package/dist/call.d.ts +4 -0
  9. package/dist/call.js +19 -4
  10. package/dist/call.js.map +1 -1
  11. package/dist/client.d.ts +38 -2
  12. package/dist/client.js +67 -10
  13. package/dist/client.js.map +1 -1
  14. package/dist/config.d.ts +3 -3
  15. package/dist/config.js +33 -16
  16. package/dist/config.js.map +1 -1
  17. package/dist/credentials.d.ts +25 -0
  18. package/dist/credentials.js +73 -5
  19. package/dist/credentials.js.map +1 -1
  20. package/dist/datafile.d.ts +11 -2
  21. package/dist/datafile.js +56 -2
  22. package/dist/datafile.js.map +1 -1
  23. package/dist/describe.d.ts +11 -5
  24. package/dist/describe.js +13 -5
  25. package/dist/describe.js.map +1 -1
  26. package/dist/drift.d.ts +64 -12
  27. package/dist/drift.js +118 -41
  28. package/dist/drift.js.map +1 -1
  29. package/dist/expectations.d.ts +2 -2
  30. package/dist/expectations.js +28 -3
  31. package/dist/expectations.js.map +1 -1
  32. package/dist/host.d.ts +56 -0
  33. package/dist/host.js +45 -0
  34. package/dist/host.js.map +1 -0
  35. package/dist/index.d.ts +6 -2
  36. package/dist/index.js +5 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/lockfile.d.ts +36 -0
  39. package/dist/lockfile.js +69 -0
  40. package/dist/lockfile.js.map +1 -0
  41. package/dist/op.d.ts +3 -0
  42. package/dist/op.js.map +1 -1
  43. package/dist/resource.d.ts +188 -22
  44. package/dist/resource.js +173 -28
  45. package/dist/resource.js.map +1 -1
  46. package/dist/resources.d.ts +18 -3
  47. package/dist/resources.js +53 -6
  48. package/dist/resources.js.map +1 -1
  49. package/dist/run.d.ts +14 -0
  50. package/dist/run.js +6 -1
  51. package/dist/run.js.map +1 -1
  52. package/dist/sandbox.js +5 -1
  53. package/dist/sandbox.js.map +1 -1
  54. package/dist/scenario.d.ts +12 -3
  55. package/dist/scenario.js +33 -8
  56. package/dist/scenario.js.map +1 -1
  57. package/dist/secret.d.ts +42 -0
  58. package/dist/secret.js +58 -0
  59. package/dist/secret.js.map +1 -0
  60. package/dist/shared.d.ts +8 -0
  61. package/dist/shared.js +7 -0
  62. package/dist/shared.js.map +1 -1
  63. package/dist/source.d.ts +58 -0
  64. package/dist/source.js +54 -0
  65. package/dist/source.js.map +1 -0
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/dist/worker.d.ts +3 -0
  69. package/dist/worker.js +13 -4
  70. package/dist/worker.js.map +1 -1
  71. package/dist/writer.d.ts +35 -0
  72. package/dist/writer.js +201 -0
  73. package/dist/writer.js.map +1 -0
  74. package/package.json +6 -1
  75. package/src/apply-steps.ts +110 -0
  76. package/src/apply.ts +626 -0
  77. package/src/call.ts +19 -4
  78. package/src/client.ts +83 -13
  79. package/src/config.ts +48 -25
  80. package/src/credentials.ts +90 -5
  81. package/src/datafile.ts +76 -4
  82. package/src/describe.ts +36 -16
  83. package/src/drift.ts +155 -45
  84. package/src/expectations.ts +30 -4
  85. package/src/host.ts +96 -0
  86. package/src/index.ts +24 -1
  87. package/src/lockfile.ts +95 -0
  88. package/src/op.ts +3 -0
  89. package/src/resource.ts +270 -47
  90. package/src/resources.ts +63 -8
  91. package/src/run.ts +15 -0
  92. package/src/sandbox.ts +5 -1
  93. package/src/scenario.ts +50 -13
  94. package/src/secret.ts +92 -0
  95. package/src/shared.ts +10 -0
  96. package/src/source.ts +82 -0
  97. package/src/version.ts +1 -1
  98. package/src/worker.ts +16 -5
  99. package/src/writer.ts +225 -0
@@ -0,0 +1,110 @@
1
+ import { join, relative, sep } from "node:path";
2
+ import { DBOS } from "@dbos-inc/dbos-sdk";
3
+ import type { ApplyDeclared, ApplySteps, PlannedCall } from "./apply.ts";
4
+ import { decide } from "./call.ts";
5
+ import type { Approver } from "./define.ts";
6
+ import { type Binding, type Bindings, diffLockfiles, LOCKFILE, readLockfile, writeLockfile } from "./lockfile.ts";
7
+ import { currentRun, type Run } from "./run.ts";
8
+ import { APPLY_WORKFLOW } from "./shared.ts";
9
+
10
+ // The apply workflow's steps that call no vendor: reading the data files, asking the policy
11
+ // ahead, and writing the lockfile. Here, not in apply.ts, which is held to the workflow lint and
12
+ // imports no DBOS; each reads its run from `currentRun`.
13
+
14
+ /** The name of the step that writes the lockfile and commits it: its output has the bindings it wrote. */
15
+ const WRITE_STEP = "apply:lockfile";
16
+
17
+ /** How many of the latest apply runs are searched for the lockfile one last wrote. */
18
+ const RUNS_SEARCHED = 100;
19
+
20
+ const runNow = (): Run => {
21
+ const run = currentRun.getStore();
22
+ if (!run) throw new Error("An apply step ran outside its run");
23
+ return run;
24
+ };
25
+
26
+ /**
27
+ * The bindings the latest apply run that wrote the lockfile wrote last, from its steps; undefined
28
+ * when none of the latest `RUNS_SEARCHED` did. A sandbox run writes none.
29
+ */
30
+ async function lastWritten(appName: string): Promise<Bindings | undefined> {
31
+ const runs = await DBOS.listWorkflows({
32
+ workflowName: APPLY_WORKFLOW,
33
+ applicationName: appName,
34
+ sortDesc: true,
35
+ limit: RUNS_SEARCHED,
36
+ loadInput: false,
37
+ loadOutput: false,
38
+ });
39
+ for (const { workflowID } of runs) {
40
+ const steps = await DBOS.listWorkflowSteps(workflowID);
41
+ const last = steps?.findLast((step) => step.name === WRITE_STEP && !step.error);
42
+ if (last) return (last.output as { bindings: Bindings }).bindings;
43
+ }
44
+ return undefined;
45
+ }
46
+
47
+ /**
48
+ * The apply workflow's steps for a config: `read`, the data files and the lockfile as drift reads
49
+ * them, under `root`; `appName` scopes the search for the lockfile apply last wrote.
50
+ */
51
+ export function applySteps(
52
+ appName: string,
53
+ root: string | undefined,
54
+ read: () => Promise<Omit<ApplyDeclared, "rebinds">>,
55
+ ): ApplySteps {
56
+ return {
57
+ // The data files, and the lockfile against the one apply last wrote: a change someone else made.
58
+ declared: () =>
59
+ DBOS.runStep(
60
+ async () => {
61
+ const declared = await read();
62
+ const before = await lastWritten(appName);
63
+ return { ...declared, rebinds: before ? diffLockfiles(before, declared.bindings) : [] };
64
+ },
65
+ { name: "apply:data-files" },
66
+ ),
67
+ // The policy asked as each call would ask it, now, so one approval can cover them all.
68
+ holds: (calls: PlannedCall[]) =>
69
+ DBOS.runStep(
70
+ async (): Promise<Approver | undefined> => {
71
+ const run = runNow();
72
+ for (const { op: id, input } of calls) {
73
+ const op = run.state.ops.get(id)!;
74
+ // Parsed as the call will be, so the policy sees the same input with its defaults.
75
+ const parsed = op.input.safeParse(input);
76
+ const decision = await decide(run, op, parsed.success ? parsed.data : input);
77
+ if (decision.kind === "approve") return decision.approver;
78
+ }
79
+ return undefined;
80
+ },
81
+ { name: "apply:policy" },
82
+ ),
83
+ // The one binding changed on the lockfile as it is now, written whole, then committed, in one
84
+ // step whose output is what it wrote. A sandbox run keeps its lockfile in memory: the run's
85
+ // report has it.
86
+ write: async (key: string, binding: Binding | undefined, message: string) => {
87
+ const run = runNow();
88
+ if (run.sandbox !== undefined || root === undefined) return { written: false };
89
+ const { source } = run.state;
90
+ const { revision } = await DBOS.runStep(
91
+ async (): Promise<{ bindings: Bindings; revision?: string }> => {
92
+ const now = readLockfile(root);
93
+ if (now.problems.length) throw new Error(`${LOCKFILE} cannot be updated: ${now.problems[0]!.message}`);
94
+ const { [key]: _, ...others } = now.bindings;
95
+ const bindings = binding ? { ...others, [key]: binding } : others;
96
+ writeLockfile(root, bindings);
97
+ const paths = [
98
+ relative(source?.root ?? root, join(root, LOCKFILE))
99
+ .split(sep)
100
+ .join("/"),
101
+ ];
102
+ const committed = await source?.commit({ paths, message, author: run.actor.id });
103
+ return { bindings, ...(committed && { revision: committed.revision }) };
104
+ },
105
+ { name: WRITE_STEP },
106
+ );
107
+ return { written: true, ...(revision !== undefined && { revision }) };
108
+ },
109
+ };
110
+ }