@sanoma-ai/workflows 0.0.0-stage → 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 (176) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +544 -2
  3. package/dist/apply-steps.d.ts +6 -0
  4. package/dist/apply-steps.js +93 -0
  5. package/dist/apply-steps.js.map +1 -0
  6. package/dist/apply.d.ts +197 -0
  7. package/dist/apply.js +470 -0
  8. package/dist/apply.js.map +1 -0
  9. package/dist/approvals.d.ts +46 -0
  10. package/dist/approvals.js +111 -0
  11. package/dist/approvals.js.map +1 -0
  12. package/dist/ast.d.ts +57 -0
  13. package/dist/ast.js +81 -0
  14. package/dist/ast.js.map +1 -0
  15. package/dist/call.d.ts +37 -0
  16. package/dist/call.js +417 -0
  17. package/dist/call.js.map +1 -0
  18. package/dist/client.d.ts +203 -0
  19. package/dist/client.js +457 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/config.d.ts +126 -0
  22. package/dist/config.js +264 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/credentials.d.ts +55 -0
  25. package/dist/credentials.js +261 -0
  26. package/dist/credentials.js.map +1 -0
  27. package/dist/cucumber.d.ts +64 -0
  28. package/dist/cucumber.js +158 -0
  29. package/dist/cucumber.js.map +1 -0
  30. package/dist/datafile.d.ts +71 -0
  31. package/dist/datafile.js +380 -0
  32. package/dist/datafile.js.map +1 -0
  33. package/dist/db.d.ts +58 -0
  34. package/dist/db.js +179 -0
  35. package/dist/db.js.map +1 -0
  36. package/dist/define.d.ts +205 -0
  37. package/dist/define.js +103 -0
  38. package/dist/define.js.map +1 -0
  39. package/dist/describe.d.ts +141 -0
  40. package/dist/describe.js +135 -0
  41. package/dist/describe.js.map +1 -0
  42. package/dist/drift.d.ts +127 -0
  43. package/dist/drift.js +167 -0
  44. package/dist/drift.js.map +1 -0
  45. package/dist/env.d.ts +49 -0
  46. package/dist/env.js +83 -0
  47. package/dist/env.js.map +1 -0
  48. package/dist/errors.d.ts +88 -0
  49. package/dist/errors.js +148 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/expectations.d.ts +26 -0
  52. package/dist/expectations.js +162 -0
  53. package/dist/expectations.js.map +1 -0
  54. package/dist/fake.d.ts +55 -0
  55. package/dist/fake.js +87 -0
  56. package/dist/fake.js.map +1 -0
  57. package/dist/fill.d.ts +9 -0
  58. package/dist/fill.js +17 -0
  59. package/dist/fill.js.map +1 -0
  60. package/dist/host.d.ts +56 -0
  61. package/dist/host.js +45 -0
  62. package/dist/host.js.map +1 -0
  63. package/dist/index.d.ts +23 -0
  64. package/dist/index.js +29 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/ledger.d.ts +217 -0
  67. package/dist/ledger.js +251 -0
  68. package/dist/ledger.js.map +1 -0
  69. package/dist/lint.d.ts +32 -0
  70. package/dist/lint.js +168 -0
  71. package/dist/lint.js.map +1 -0
  72. package/dist/lockfile.d.ts +36 -0
  73. package/dist/lockfile.js +69 -0
  74. package/dist/lockfile.js.map +1 -0
  75. package/dist/log.d.ts +24 -0
  76. package/dist/log.js +83 -0
  77. package/dist/log.js.map +1 -0
  78. package/dist/op.d.ts +260 -0
  79. package/dist/op.js +188 -0
  80. package/dist/op.js.map +1 -0
  81. package/dist/outline.d.ts +179 -0
  82. package/dist/outline.js +506 -0
  83. package/dist/outline.js.map +1 -0
  84. package/dist/paths.d.ts +11 -0
  85. package/dist/paths.js +30 -0
  86. package/dist/paths.js.map +1 -0
  87. package/dist/plugin.d.ts +28 -0
  88. package/dist/plugin.js +25 -0
  89. package/dist/plugin.js.map +1 -0
  90. package/dist/policy.d.ts +114 -0
  91. package/dist/policy.js +60 -0
  92. package/dist/policy.js.map +1 -0
  93. package/dist/resource.d.ts +378 -0
  94. package/dist/resource.js +430 -0
  95. package/dist/resource.js.map +1 -0
  96. package/dist/resources.d.ts +77 -0
  97. package/dist/resources.js +244 -0
  98. package/dist/resources.js.map +1 -0
  99. package/dist/run.d.ts +101 -0
  100. package/dist/run.js +7 -0
  101. package/dist/run.js.map +1 -0
  102. package/dist/sandbox.d.ts +33 -0
  103. package/dist/sandbox.js +127 -0
  104. package/dist/sandbox.js.map +1 -0
  105. package/dist/scenario.d.ts +221 -0
  106. package/dist/scenario.js +1104 -0
  107. package/dist/scenario.js.map +1 -0
  108. package/dist/scenarios.d.ts +55 -0
  109. package/dist/scenarios.js +71 -0
  110. package/dist/scenarios.js.map +1 -0
  111. package/dist/secret.d.ts +42 -0
  112. package/dist/secret.js +58 -0
  113. package/dist/secret.js.map +1 -0
  114. package/dist/shared.d.ts +69 -0
  115. package/dist/shared.js +84 -0
  116. package/dist/shared.js.map +1 -0
  117. package/dist/source.d.ts +58 -0
  118. package/dist/source.js +54 -0
  119. package/dist/source.js.map +1 -0
  120. package/dist/steps.d.ts +76 -0
  121. package/dist/steps.js +23 -0
  122. package/dist/steps.js.map +1 -0
  123. package/dist/version.d.ts +29 -0
  124. package/dist/version.js +76 -0
  125. package/dist/version.js.map +1 -0
  126. package/dist/worker.d.ts +37 -0
  127. package/dist/worker.js +228 -0
  128. package/dist/worker.js.map +1 -0
  129. package/dist/writer.d.ts +35 -0
  130. package/dist/writer.js +201 -0
  131. package/dist/writer.js.map +1 -0
  132. package/oxlint.json +128 -0
  133. package/package.json +96 -3
  134. package/src/apply-steps.ts +110 -0
  135. package/src/apply.ts +626 -0
  136. package/src/approvals.ts +157 -0
  137. package/src/ast.ts +115 -0
  138. package/src/call.ts +483 -0
  139. package/src/client.ts +604 -0
  140. package/src/config.ts +404 -0
  141. package/src/credentials.ts +296 -0
  142. package/src/cucumber.ts +230 -0
  143. package/src/datafile.ts +481 -0
  144. package/src/db.ts +197 -0
  145. package/src/define.ts +285 -0
  146. package/src/describe.ts +282 -0
  147. package/src/drift.ts +259 -0
  148. package/src/env.ts +122 -0
  149. package/src/errors.ts +202 -0
  150. package/src/expectations.ts +191 -0
  151. package/src/fake.ts +156 -0
  152. package/src/fill.ts +24 -0
  153. package/src/host.ts +96 -0
  154. package/src/index.ts +143 -0
  155. package/src/ledger.ts +419 -0
  156. package/src/lint.ts +208 -0
  157. package/src/lockfile.ts +95 -0
  158. package/src/log.ts +82 -0
  159. package/src/op.ts +402 -0
  160. package/src/outline.ts +620 -0
  161. package/src/paths.ts +32 -0
  162. package/src/plugin.ts +37 -0
  163. package/src/policy.ts +139 -0
  164. package/src/resource.ts +670 -0
  165. package/src/resources.ts +311 -0
  166. package/src/run.ts +106 -0
  167. package/src/sandbox.ts +161 -0
  168. package/src/scenario.ts +1365 -0
  169. package/src/scenarios.ts +111 -0
  170. package/src/secret.ts +92 -0
  171. package/src/shared.ts +126 -0
  172. package/src/source.ts +82 -0
  173. package/src/steps.ts +90 -0
  174. package/src/version.ts +86 -0
  175. package/src/worker.ts +264 -0
  176. package/src/writer.ts +225 -0
package/dist/op.d.ts ADDED
@@ -0,0 +1,260 @@
1
+ import type { z } from "zod";
2
+ import type { Fake } from "./fake.ts";
3
+ import type { Secrets } from "./secret.ts";
4
+ import type { Steps } from "./steps.ts";
5
+ /**
6
+ * What an operation does to the outside world. It sets the badge a reviewer
7
+ * sees and, later, the Cedar action the call is checked against.
8
+ */
9
+ export type Effect = "read" | "write" | "publish" | "send" | "money" | "access";
10
+ export interface OpSpec<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType> {
11
+ effect: Effect;
12
+ input: I;
13
+ output: O;
14
+ /** Safe to retry: the vendor dedupes repeated calls. Otherwise a failed call stops the run. */
15
+ idempotent?: boolean;
16
+ description?: string;
17
+ /**
18
+ * The resource instance a call acts on, from its parsed input, such as a post's id. The
19
+ * policy sees it as `target`, so it can decide per instance. Must be deterministic.
20
+ *
21
+ * Its input is typed `any` inside `defineConnector`: TypeScript gives an inline function the
22
+ * type `Specs` declares, not the one its own spec's schema implies. Annotate it to have it
23
+ * checked: `target: ({ id }: { id: string }) => \`post/${id}\``.
24
+ */
25
+ target?: (input: any) => string;
26
+ /**
27
+ * How a scenario says this operation was set up or called, in Gherkin steps: `given` seeds
28
+ * the fake through it, `expect` expects a call to it. Cucumber expressions whose parameters are
29
+ * fields of the input, such as `{ given: "a post titled {title} exists" }`.
30
+ */
31
+ phrases?: {
32
+ given?: string;
33
+ expect?: string;
34
+ };
35
+ /**
36
+ * Top-level fields of its input and output that are the driver's own data, not the vendor's
37
+ * (a resource type's `handle`): the ledger records each as `"<name>"`, never its value.
38
+ */
39
+ opaque?: readonly string[];
40
+ }
41
+ /** One vendor operation, such as `ghost.post.publish`. Declares the contract only; drivers implement it. */
42
+ export interface Op<V extends string = string, R extends string = string, N extends string = string, I = any, O = any> {
43
+ readonly kind: "op";
44
+ readonly id: `${V}.${R}.${N}`;
45
+ readonly vendor: V;
46
+ readonly resource: R;
47
+ readonly name: N;
48
+ readonly effect: Effect;
49
+ readonly idempotent: boolean;
50
+ readonly description?: string;
51
+ /** From the spec: the resource instance a call acts on. */
52
+ readonly target?: (input: any) => string;
53
+ /** From the spec: how a scenario's steps name it. */
54
+ readonly phrases?: {
55
+ readonly given?: string;
56
+ readonly expect?: string;
57
+ };
58
+ /** From the spec: fields the ledger records by name only. */
59
+ readonly opaque?: readonly string[];
60
+ readonly input: z.ZodType<any, I>;
61
+ readonly output: z.ZodType<O>;
62
+ }
63
+ /**
64
+ * Where a resource type keeps its brand. A symbol, so `defineConnector` tells a resource type
65
+ * (`defineResource`) from a group of operations; a registered one (`Symbol.for`), so a copy of
66
+ * this package reads what another copy made, as `VENDOR`.
67
+ */
68
+ export declare const RESOURCE: unique symbol;
69
+ /** What `defineConnector` reads of a resource type; `defineResource` makes them. */
70
+ export interface ResourceGroup {
71
+ readonly [RESOURCE]: true;
72
+ readonly vendor: string;
73
+ readonly type: string;
74
+ readonly ops: Readonly<Record<string, OpSpec>>;
75
+ }
76
+ /** A connector's groups: operations by name, or a resource type, whose operations are `read` and `import`. */
77
+ export type Specs = Record<string, Record<string, OpSpec> | ResourceGroup>;
78
+ /** The operations of one group of `Specs`. */
79
+ export type GroupOps<G> = G extends ResourceGroup ? G["ops"] : G;
80
+ type InputOf<T> = T extends OpSpec<infer I, any> ? I : never;
81
+ type OutputOf<T> = T extends OpSpec<any, infer O> ? O : never;
82
+ /**
83
+ * Where a connector keeps its vendor. A symbol, so it names no resource: `Object.values` and
84
+ * `keyof` over a connector see its resources only. A registered one (`Symbol.for`): a process
85
+ * may load two copies of this package (a connector's peer linked into a sanoma checkout, the
86
+ * app's from its tarball), and each must read the connectors the other made.
87
+ */
88
+ export declare const VENDOR: unique symbol;
89
+ export type Connector<V extends string, S extends Specs> = {
90
+ readonly [R in keyof S & string]: {
91
+ readonly [N in keyof GroupOps<S[R]> & string]: Op<V, R, N, z.input<InputOf<GroupOps<S[R]>[N]>>, z.output<OutputOf<GroupOps<S[R]>[N]>>>;
92
+ };
93
+ } & {
94
+ readonly [VENDOR]: ConnectorVendor<V>;
95
+ };
96
+ /**
97
+ * The shape of a connector's `[VENDOR]` record. A process may load two builds of this package,
98
+ * and `resolveConfig` reads only a record of its own shape (`checkConnector`): change it with
99
+ * the record, so a build refuses a connector another build made in another shape.
100
+ */
101
+ export declare const CONNECTOR_FORMAT = 1;
102
+ /**
103
+ * A connector's vendor: its id, who it is (`defineConnector`'s third argument, for a UI), its
104
+ * resource types, and the driver and fake its package attaches (`withSetup`).
105
+ */
106
+ export interface ConnectorVendor<V extends string = string> {
107
+ /** The record's shape (`CONNECTOR_FORMAT`). */
108
+ readonly format: typeof CONNECTOR_FORMAT;
109
+ readonly id: V;
110
+ readonly info?: VendorInfo;
111
+ /** The resource types among its groups, in the order given. */
112
+ readonly resources: readonly ResourceGroup[];
113
+ /** What `withSetup` attached: how the config's resolver makes its driver and its fake. */
114
+ readonly setup?: ConnectorSetup;
115
+ }
116
+ /**
117
+ * Who a connector's vendor is, and where the connector lives, for a UI to show beside its
118
+ * operations. `logo` is the vendor's mark as inline SVG markup (one `<svg>…</svg>` element), and
119
+ * `dark` its variant for dark backgrounds. The app shows a logo only as an image (a `data:` URL
120
+ * in an `<img>`), where an SVG's scripts and external references never run or load.
121
+ */
122
+ export interface VendorInfo {
123
+ /** The vendor's name as it writes it, such as "Resend". */
124
+ title?: string;
125
+ logo?: {
126
+ svg: string;
127
+ dark?: string;
128
+ };
129
+ /** The connector's npm package name, such as "@sanoma-ai/connector-resend". */
130
+ package?: string;
131
+ /** The connector package's `homepage` from its package.json: where its code and README are, as an https URL. */
132
+ homepage?: string;
133
+ }
134
+ /**
135
+ * Declares a vendor's operations, grouped by resource: `defineConnector("ghost", { post: { create: {...} } })`.
136
+ * A group may be a resource type from `defineResource`, under its own `type`, for its `read` and
137
+ * `import`: `defineConnector("github", { repository }, { package: "@sanoma-ai/connector-github" })`;
138
+ * it must be of the same vendor, and the connector needs `info.package`, whose `/resources`
139
+ * entry exports the types for data files.
140
+ * `info` says who the vendor is and where the connector lives, for a UI:
141
+ * `{ title: "Resend", logo: { svg }, package: "@sanoma-ai/connector-resend", homepage: "https://…" }`.
142
+ */
143
+ export declare function defineConnector<const V extends string, const S extends Specs>(vendor: V, specs: S, info?: VendorInfo): Connector<V, S>;
144
+ /**
145
+ * What the runtime tells a driver about the call it is making. `idempotencyKey` is the same
146
+ * on every replay and retry of one call (`<runId>:<seq>`), so a driver can hand it to the
147
+ * vendor, or dedupe on it, and a crash between the vendor's reply and the checkpoint does
148
+ * not repeat the side effect.
149
+ */
150
+ export interface CallContext {
151
+ idempotencyKey: string;
152
+ runId: string;
153
+ opId: string;
154
+ /** 1 on the first try; retries count up (idempotent operations only). */
155
+ attempt: number;
156
+ /** The secrets store, for the values `secret()` markers name: `resolveSecrets(input, call.secrets)`. In memory for a fake. */
157
+ secrets: Secrets;
158
+ }
159
+ export type DriverFn<I = any, O = unknown> = (input: I, call: CallContext) => Promise<O>;
160
+ /**
161
+ * A vendor's answer that the driver understood. `retryable` tells the runtime whether to try
162
+ * again (an idempotent operation on a timeout or a 5xx) or to stop (a 4xx). `status` and
163
+ * `vendorCode` keep what the vendor said, so the ledger and the UI can show it.
164
+ */
165
+ export declare class DriverError extends Error {
166
+ readonly code: "driver_failed";
167
+ readonly retryable: boolean;
168
+ readonly status?: number;
169
+ readonly vendorCode?: string;
170
+ constructor(message: string, options: {
171
+ retryable: boolean;
172
+ status?: number;
173
+ vendorCode?: string;
174
+ cause?: unknown;
175
+ });
176
+ }
177
+ /**
178
+ * Whether a vendor's HTTP status is worth retrying: a request timeout (408), a rate limit (429)
179
+ * or a server error (5xx). It is the runtime's default rule; a driver adds its vendor's
180
+ * exceptions around it, such as a quota that does not lift in the seconds a retry waits.
181
+ */
182
+ export declare function retryableStatus(status: number): boolean;
183
+ /**
184
+ * The environment variables a driver reads, as a Zod object of string schemas keyed by variable
185
+ * name, such as `z.object({ ACME_API_KEY: z.string().describe("an API key, from Settings, API keys") })`:
186
+ * each `.describe()` is the variable's one-line manual (what to put there, where to get it),
187
+ * `.optional()` marks one that may be unset. `resolveConfig` checks it against `process.env` and
188
+ * never keeps a value. The driver still reads the raw environment when it is called, so a
189
+ * `.default()` or `.transform()` is not applied to what it reads (use `.optional()` and name the
190
+ * default in `.describe()`), and a `.refine()` or `.superRefine()` on the object is not evaluated,
191
+ * only its fields.
192
+ */
193
+ export type DriverEnv = z.ZodObject<Record<string, z.ZodType<string | undefined, string | undefined>>>;
194
+ /**
195
+ * Implements a vendor's operations, keyed `"resource.name"`. A driver reads its credentials
196
+ * from the environment when it is called, never from the config file; `env` declares them.
197
+ */
198
+ export interface Driver {
199
+ readonly vendor: string;
200
+ readonly ops: Readonly<Record<string, DriverFn>>;
201
+ readonly env?: DriverEnv;
202
+ }
203
+ /**
204
+ * What a connector package attaches to its connector (`withSetup`), for the config's resolver
205
+ * to call: the config then names the connector alone, and never a driver, a fake or steps. Any may
206
+ * be missing: a workflow using an operation of a connector with no driver is refused, and a
207
+ * connector with no fake has none for sandbox runs.
208
+ */
209
+ export interface ConnectorSetup {
210
+ /** Makes the live driver, which reads its credentials from the environment when called: once per resolved config. */
211
+ driver?: () => Driver;
212
+ /** Makes a fake of the vendor (`defineFake`): once per sandbox run, its own. */
213
+ fake?: () => Fake<any, any>;
214
+ /** The connector's steps for Gherkin tests (`defineSteps`): a config's scenarios and the connector's own features can use them. */
215
+ steps?: Steps;
216
+ }
217
+ /**
218
+ * The connector with `setup` laid over what it already carries, as a connector package exports
219
+ * it: `export const ghost = withSetup(ghostOps, { driver: ghostDriver, fake: fakeGhost, steps: ghostSteps })`. A
220
+ * config that needs a driver made otherwise (another bridge, other options) swaps that one and
221
+ * keeps the fake: `withSetup(github, { driver: () => githubDriver({ bridge }) })`. The operations
222
+ * are the same objects, and the type is the connector's. The setup is kept on the connector's
223
+ * vendor record (`[VENDOR]`), so a spread copy of the connector keeps it.
224
+ */
225
+ export declare function withSetup<C extends Connector<any, any>>(connector: C, setup: Partial<ConnectorSetup>): C;
226
+ /**
227
+ * Throws unless `value` is a connector this package can read: one `defineConnector` made, in this
228
+ * copy of the package or another of the same `CONNECTOR_FORMAT`. One an older build made (its
229
+ * `VENDOR` an unregistered symbol, or its record without a format) is refused as such.
230
+ */
231
+ export declare function checkConnector(value: unknown): asserts value is Connector<any, any>;
232
+ /** What `withSetup` attached to a connector: nothing when it attached nothing. */
233
+ export declare const setupOf: (connector: Connector<any, any>) => ConnectorSetup;
234
+ export declare function isOp(x: unknown): x is Op;
235
+ /**
236
+ * What a driver for a connector must implement: every operation, by resource and name. Each
237
+ * function gets the input as the operation's schema parses it and returns what its output
238
+ * schema accepts.
239
+ */
240
+ export type DriverImpl<S extends Specs> = {
241
+ [R in keyof S & string]: {
242
+ [N in keyof GroupOps<S[R]> & string]: DriverFn<z.output<InputOf<GroupOps<S[R]>[N]>>, z.input<OutputOf<GroupOps<S[R]>[N]>>>;
243
+ };
244
+ };
245
+ /** The ids of a connector's operations, such as `"ghost.post.create" | "ghost.post.publish"`. */
246
+ export type OpIdOf<V extends string, S extends Specs> = {
247
+ [R in keyof S & string]: {
248
+ [N in keyof GroupOps<S[R]> & string]: `${V}.${R}.${N}`;
249
+ }[keyof GroupOps<S[R]> & string];
250
+ }[keyof S & string];
251
+ /**
252
+ * Implements a connector's operations, typed by its schemas. A driver must be complete: an
253
+ * operation the connector declares that `impl` leaves out, or one `impl` adds that the
254
+ * connector doesn't declare, is refused here rather than when a run calls it. `options.env`
255
+ * declares the environment variables it reads (see `DriverEnv`).
256
+ */
257
+ export declare function defineDriver<V extends string, S extends Specs>(connector: Connector<V, S>, impl: DriverImpl<S>, options?: {
258
+ env?: DriverEnv;
259
+ }): Driver;
260
+ export {};
package/dist/op.js ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Where a resource type keeps its brand. A symbol, so `defineConnector` tells a resource type
3
+ * (`defineResource`) from a group of operations; a registered one (`Symbol.for`), so a copy of
4
+ * this package reads what another copy made, as `VENDOR`.
5
+ */
6
+ export const RESOURCE = Symbol.for("sanoma.resource");
7
+ /**
8
+ * Where a connector keeps its vendor. A symbol, so it names no resource: `Object.values` and
9
+ * `keyof` over a connector see its resources only. A registered one (`Symbol.for`): a process
10
+ * may load two copies of this package (a connector's peer linked into a sanoma checkout, the
11
+ * app's from its tarball), and each must read the connectors the other made.
12
+ */
13
+ export const VENDOR = Symbol.for("sanoma.vendor");
14
+ /**
15
+ * The shape of a connector's `[VENDOR]` record. A process may load two builds of this package,
16
+ * and `resolveConfig` reads only a record of its own shape (`checkConnector`): change it with
17
+ * the record, so a build refuses a connector another build made in another shape.
18
+ */
19
+ export const CONNECTOR_FORMAT = 1;
20
+ /** One `<svg>` element, with nothing but whitespace around it. */
21
+ const SVG_ELEMENT = /^\s*<svg[\s>][\s\S]*<\/svg>\s*$/;
22
+ const isResourceGroup = (group) => (typeof group === "function" || typeof group === "object") &&
23
+ group !== null &&
24
+ group[RESOURCE] === true;
25
+ /**
26
+ * Declares a vendor's operations, grouped by resource: `defineConnector("ghost", { post: { create: {...} } })`.
27
+ * A group may be a resource type from `defineResource`, under its own `type`, for its `read` and
28
+ * `import`: `defineConnector("github", { repository }, { package: "@sanoma-ai/connector-github" })`;
29
+ * it must be of the same vendor, and the connector needs `info.package`, whose `/resources`
30
+ * entry exports the types for data files.
31
+ * `info` says who the vendor is and where the connector lives, for a UI:
32
+ * `{ title: "Resend", logo: { svg }, package: "@sanoma-ai/connector-resend", homepage: "https://…" }`.
33
+ */
34
+ export function defineConnector(vendor, specs, info) {
35
+ // A copy, frozen once checked: changing the caller's object later cannot slip a logo past the check.
36
+ const logo = info?.logo && Object.freeze(Object.fromEntries(Object.entries(info.logo).filter(([, v]) => v !== undefined)));
37
+ for (const [variant, svg] of Object.entries(logo ?? {})) {
38
+ if (typeof svg !== "string" || !SVG_ELEMENT.test(svg)) {
39
+ throw new Error(`defineConnector("${vendor}"): logo.${variant} must be inline SVG markup, one <svg>…</svg> element`);
40
+ }
41
+ }
42
+ const vendorInfo = info && Object.freeze({ ...info, ...(logo && { logo: logo }) });
43
+ // It becomes a link's href: a whole https URL, so never a `javascript:` one. Checked on the copy kept.
44
+ const homepage = vendorInfo?.homepage;
45
+ if (homepage !== undefined && (typeof homepage !== "string" || URL.parse(homepage)?.protocol !== "https:"))
46
+ throw new Error(`defineConnector("${vendor}"): homepage must be an https URL`);
47
+ const out = {};
48
+ const resources = [];
49
+ for (const [resource, group] of Object.entries(specs)) {
50
+ let ops = group;
51
+ if (isResourceGroup(group)) {
52
+ if (group.vendor !== vendor) {
53
+ throw new Error(`defineConnector("${vendor}"): ${resource} is a resource type of ${group.vendor}`);
54
+ }
55
+ if (group.type !== resource) {
56
+ throw new Error(`defineConnector("${vendor}"): the resource type ${group.type} is given as ${resource}`);
57
+ }
58
+ resources.push(group);
59
+ ops = group.ops;
60
+ }
61
+ out[resource] = {};
62
+ for (const [name, spec] of Object.entries(ops)) {
63
+ out[resource][name] = Object.freeze({
64
+ kind: "op",
65
+ id: `${vendor}.${resource}.${name}`,
66
+ vendor,
67
+ resource,
68
+ name,
69
+ effect: spec.effect,
70
+ idempotent: spec.idempotent ?? false,
71
+ description: spec.description,
72
+ target: spec.target,
73
+ phrases: spec.phrases,
74
+ ...(spec.opaque?.length ? { opaque: spec.opaque } : {}),
75
+ input: spec.input,
76
+ output: spec.output,
77
+ });
78
+ }
79
+ }
80
+ // A data file imports the resource types from `<package>/resources`, which the reader matches exactly.
81
+ if (resources.length && !vendorInfo?.package) {
82
+ throw new Error(`defineConnector("${vendor}"): a connector with resource types needs info.package, the npm package data files import them from (\`<package>/resources\`)`);
83
+ }
84
+ const owner = Object.freeze({
85
+ format: CONNECTOR_FORMAT,
86
+ id: vendor,
87
+ info: vendorInfo,
88
+ resources: Object.freeze(resources),
89
+ });
90
+ return { ...out, [VENDOR]: owner };
91
+ }
92
+ /**
93
+ * A vendor's answer that the driver understood. `retryable` tells the runtime whether to try
94
+ * again (an idempotent operation on a timeout or a 5xx) or to stop (a 4xx). `status` and
95
+ * `vendorCode` keep what the vendor said, so the ledger and the UI can show it.
96
+ */
97
+ export class DriverError extends Error {
98
+ code = "driver_failed";
99
+ retryable;
100
+ status;
101
+ vendorCode;
102
+ constructor(message, options) {
103
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
104
+ this.name = "DriverError";
105
+ this.retryable = options.retryable;
106
+ if (options.status !== undefined)
107
+ this.status = options.status;
108
+ if (options.vendorCode !== undefined)
109
+ this.vendorCode = options.vendorCode;
110
+ }
111
+ }
112
+ /**
113
+ * Whether a vendor's HTTP status is worth retrying: a request timeout (408), a rate limit (429)
114
+ * or a server error (5xx). It is the runtime's default rule; a driver adds its vendor's
115
+ * exceptions around it, such as a quota that does not lift in the seconds a retry waits.
116
+ */
117
+ export function retryableStatus(status) {
118
+ return status === 408 || status === 429 || status >= 500;
119
+ }
120
+ /**
121
+ * The connector with `setup` laid over what it already carries, as a connector package exports
122
+ * it: `export const ghost = withSetup(ghostOps, { driver: ghostDriver, fake: fakeGhost, steps: ghostSteps })`. A
123
+ * config that needs a driver made otherwise (another bridge, other options) swaps that one and
124
+ * keeps the fake: `withSetup(github, { driver: () => githubDriver({ bridge }) })`. The operations
125
+ * are the same objects, and the type is the connector's. The setup is kept on the connector's
126
+ * vendor record (`[VENDOR]`), so a spread copy of the connector keeps it.
127
+ */
128
+ export function withSetup(connector, setup) {
129
+ const owner = connector[VENDOR];
130
+ return {
131
+ ...connector,
132
+ [VENDOR]: Object.freeze({ ...owner, setup: Object.freeze({ ...owner.setup, ...setup }) }),
133
+ };
134
+ }
135
+ /**
136
+ * Throws unless `value` is a connector this package can read: one `defineConnector` made, in this
137
+ * copy of the package or another of the same `CONNECTOR_FORMAT`. One an older build made (its
138
+ * `VENDOR` an unregistered symbol, or its record without a format) is refused as such.
139
+ */
140
+ export function checkConnector(value) {
141
+ const record = typeof value === "object" && value !== null ? value[VENDOR] : undefined;
142
+ if (record?.format === CONNECTOR_FORMAT)
143
+ return;
144
+ const build = "install the sanoma packages and connectors from one build";
145
+ throw new Error(record === undefined
146
+ ? `\`connectors\` holds something that is not a connector: list what the connector packages export (or one from an older build: ${build})`
147
+ : `The "${String(record.id)}" connector was made by another build of @sanoma-ai/workflows: ${build}`);
148
+ }
149
+ /** What `withSetup` attached to a connector: nothing when it attached nothing. */
150
+ export const setupOf = (connector) => connector[VENDOR].setup ?? {};
151
+ export function isOp(x) {
152
+ return typeof x === "object" && x !== null && x.kind === "op";
153
+ }
154
+ /**
155
+ * Implements a connector's operations, typed by its schemas. A driver must be complete: an
156
+ * operation the connector declares that `impl` leaves out, or one `impl` adds that the
157
+ * connector doesn't declare, is refused here rather than when a run calls it. `options.env`
158
+ * declares the environment variables it reads (see `DriverEnv`).
159
+ */
160
+ export function defineDriver(connector, impl, options = {}) {
161
+ const declared = new Map();
162
+ for (const resource of Object.values(connector)) {
163
+ for (const op of Object.values(resource))
164
+ if (isOp(op))
165
+ declared.set(`${op.resource}.${op.name}`, op);
166
+ }
167
+ const vendor = connector[VENDOR].id;
168
+ const ops = {};
169
+ const extra = [];
170
+ for (const [resource, fns] of Object.entries(impl)) {
171
+ for (const [name, fn] of Object.entries(fns ?? {})) {
172
+ const key = `${resource}.${name}`;
173
+ if (!declared.has(key))
174
+ extra.push(`${vendor}.${key}`);
175
+ else if (typeof fn === "function")
176
+ ops[key] = fn;
177
+ }
178
+ }
179
+ const missing = [...declared.keys()].filter((key) => !Object.hasOwn(ops, key)).map((key) => `${vendor}.${key}`);
180
+ const problems = [
181
+ ...(missing.length ? [`it does not implement ${missing.join(", ")}`] : []),
182
+ ...(extra.length ? [`it implements ${extra.join(", ")}, which the connector does not declare`] : []),
183
+ ];
184
+ if (problems.length)
185
+ throw new Error(`defineDriver("${vendor}"): ${problems.join("; ")}`);
186
+ return Object.freeze({ vendor, ops: Object.freeze(ops), ...(options.env && { env: options.env }) });
187
+ }
188
+ //# sourceMappingURL=op.js.map
package/dist/op.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op.js","sourceRoot":"","sources":["../src/op.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ import { type Node, type SourceFile } from "./ast.ts";
2
+ import { type WorkflowDefinition } from "./define.ts";
3
+ /**
4
+ * What a workflow's `run` calls through `ctx`, in the order it makes the calls, with the loops,
5
+ * branches, `try` blocks and `ctx.all` groups around them. Read from the file that defined the
6
+ * workflow, and a contract: `run` may write only what the outline can draw (`outlineBody`'s
7
+ * problems; the worker refuses a workflow with any, the lint reports them), and the worker
8
+ * places every `ctx` call a run makes at its node (`callAt`), refusing one made from code the
9
+ * outline has no node for (`call_not_in_outline`). Every record a call writes names its node
10
+ * (`node`), so a run's steps are the outline's calls and nothing else.
11
+ */
12
+ export type Outline =
13
+ /** Read from the file that defined the workflow (`wf.file`): the spans index into its text. */
14
+ {
15
+ nodes: OutlineNode[];
16
+ file: string;
17
+ } | {
18
+ error: string;
19
+ };
20
+ /** Where a node is in the text the outline was read from: UTF-16 offsets, as a string index or CodeMirror counts them. */
21
+ export type Span = readonly [start: number, end: number];
22
+ /**
23
+ * Each node's `path` is its place in the outline: its index among its siblings, under its
24
+ * parent's path and the index of the branch, case or body it is in (`"2"`, `"2.1.0"`), so the
25
+ * same tree has the same paths however the code is laid out; a ledger record's `node` is one.
26
+ * Each node's `span` is the code it stands for: the call for `op`, `approval` and `sleep`, the
27
+ * `ctx.all(...)` call for `all` and `each`, the loop or the iterating call (`items.map(cb)`) for
28
+ * `repeat`, the whole `if` / `else if` chain, `switch`, `?:` or `&&` for `branch`, and the `try`
29
+ * statement for `try`.
30
+ *
31
+ * A call's `name` is its step's name, what a test and the graph call it: the one the call gives
32
+ * (`{ name: "…" }`, on `op` and `sleep`), else its operation's id, or `Sleep`
33
+ * (`DEFAULT_SLEEP_NAME`); an approval's is its `title`. A computed call without a name of its
34
+ * own has none here: the operation it calls, known only in a run, names it. A name given, and an
35
+ * approval's title, is one step's only; an unnamed call's may be several's. A loop's `label`
36
+ * (`repeat`, and `each`) is read from its code, "for each of subscribers" for
37
+ * `for (const s of subscribers)` or `subscribers.map(…)`, "repeat while queue.length" for a
38
+ * `while`: no step's name.
39
+ */
40
+ export type OutlineNode = {
41
+ path: string;
42
+ span: Span;
43
+ } &
44
+ /**
45
+ * `ctx.<vendor>.<resource>.<name>(...)`, a computed segment shown as `*`. An id with one has
46
+ * `ops`: the ids in the workflow's `uses` it fits (`fitsOp`), sorted, which is what the call
47
+ * can reach; an id with every segment literal has none.
48
+ */
49
+ /** `name`: the one the call gives (`ctx.x.y.z(input, { name: "…" })`), else the id when no segment is computed. */
50
+ ({
51
+ kind: "op";
52
+ id: string;
53
+ ops?: string[];
54
+ name?: string;
55
+ }
56
+ /**
57
+ * `ctx.approval(...)`, with its title, and the title as its `name`, when the first argument is
58
+ * a string literal or a top-level `const` holding one.
59
+ */
60
+ | {
61
+ kind: "approval";
62
+ title?: string;
63
+ name?: string;
64
+ }
65
+ /** `name`: the one the call gives (`ctx.sleep(request, { name: "…" })`), else `DEFAULT_SLEEP_NAME`. */
66
+ | {
67
+ kind: "sleep";
68
+ name: string;
69
+ }
70
+ /** `ctx.all([...])` over an array literal: one branch per element. */
71
+ | {
72
+ kind: "all";
73
+ branches: OutlineNode[][];
74
+ }
75
+ /** `ctx.all(items.map(cb))`: what the callback's member does, labelled from the code (`for each of items`). */
76
+ | {
77
+ kind: "each";
78
+ label: string;
79
+ body: OutlineNode[];
80
+ }
81
+ /**
82
+ * A loop, or a `.map` / `.forEach` / `.reduce` callback, whose body makes ctx calls, labelled
83
+ * from the code: `for each of items`, `for each key of record`, `repeat while cond`, `repeat`.
84
+ */
85
+ | {
86
+ kind: "repeat";
87
+ label: string;
88
+ body: OutlineNode[];
89
+ }
90
+ /**
91
+ * `if`, `switch`, `?:`, `&&`, `||` or `??`: one case per arm that makes ctx calls, and one empty
92
+ * case for the way past them when there is one (an arm without calls, an `if` without `else`,
93
+ * a `switch` without `default`, the right side of `&&` not run).
94
+ */
95
+ | {
96
+ kind: "branch";
97
+ cases: OutlineNode[][];
98
+ }
99
+ /** `try { body } catch { handler }`: the handler runs when the body fails. A `finally` makes no calls. */
100
+ | {
101
+ kind: "try";
102
+ body: OutlineNode[];
103
+ handler: OutlineNode[];
104
+ });
105
+ /** The node kinds a `ctx` call is: what a call site resolves to (`callAt`). */
106
+ export type CallNode = Extract<OutlineNode, {
107
+ kind: "op" | "approval" | "sleep" | "all" | "each";
108
+ }>;
109
+ /** Something in `run` the outline cannot draw, at an offset into the file, and what to write instead. */
110
+ export interface OutlineProblem {
111
+ start: number;
112
+ message: string;
113
+ }
114
+ /**
115
+ * Outlines a workflow from the file that defined it (`wf.file`): TypeScript or, once built,
116
+ * JavaScript. Counts only calls on `run`'s first parameter, whatever it is named. Returns
117
+ * `{ error }` when the file cannot be read or parsed, holds no workflow of that name, `run`
118
+ * takes no `ctx` by name, or `run` holds what the outline cannot draw (each problem at
119
+ * `file:line:column`).
120
+ */
121
+ export declare const outlineWorkflow: (wf: WorkflowDefinition<any, any>) => Outline;
122
+ /**
123
+ * `outlineWorkflow` over `file`, the workflow's file as `readSource` read it, with the text its
124
+ * spans index into (none with an `{ error }`). `found` is whether the file was parsed and holds
125
+ * the workflow's `run`: an `{ error }` with it found is what `run` holds that the outline cannot
126
+ * draw, which the lint reports too; one without is the file's (unread, unparsed, or no workflow
127
+ * of that name).
128
+ */
129
+ export declare function outlineWithSource(wf: WorkflowDefinition<any, any>, file: SourceFile | string): {
130
+ outline: Outline;
131
+ source?: string;
132
+ found: boolean;
133
+ };
134
+ /** The file's top-level string constants, `export const NAME = "drift"`, by name: what a name may be besides a literal. */
135
+ export declare function constsOf(program: Node): ReadonlyMap<string, string>;
136
+ /**
137
+ * The nodes of `fn`'s body (`run`'s, or a callback's the outline reads), each with an empty `path`
138
+ * for `withPaths` to fill, and the problems: what the body does that the graph could not show.
139
+ * A call's `{ name }`, and an approval's title, is a string literal or one of `consts`
140
+ * (`constsOf` the file); a name given is not an operation's id or `Sleep`, and no two steps are
141
+ * given one name (`twiceNamed`). A loop's label quotes its code from `source`, the file's text.
142
+ * `ctx` is `fn`'s first parameter, taken by one name (destructured is a problem; none, and the
143
+ * body makes no calls). `ctx` is only called, directly, as an operation or a built-in, or read
144
+ * for `ctx.runId`; a function defined inside that uses `ctx` is read only where the outline
145
+ * reads callbacks (`ctx.all`'s members, `.map`, `.forEach` and `.reduce`), elsewhere it is a
146
+ * problem; a `finally` may not use `ctx`; and in a body the outline reads, `return` comes last,
147
+ * `throw` only in a `catch`, `break` leaves only a `switch`, and there is no `continue`. A
148
+ * callback without `ctx` computes, and is not read.
149
+ */
150
+ export declare function outlineBody(fn: Node, { consts, source }: {
151
+ consts: ReadonlyMap<string, string>;
152
+ source: string;
153
+ }): {
154
+ nodes: OutlineNode[];
155
+ problems: OutlineProblem[];
156
+ };
157
+ /** The outline's call nodes, wherever they are in it, in order: what `callAt` searches. */
158
+ export declare const callsOf: (nodes: readonly OutlineNode[]) => CallNode[];
159
+ /** What a call node is called as on `ctx`: the operation's id (`*` for a computed segment), or `all`, `approval`, `sleep`. */
160
+ export declare const callName: (node: CallNode) => string;
161
+ /**
162
+ * The call a position in the source belongs to: the innermost of `calls` (`callsOf`) whose span
163
+ * holds the offset, as a stack frame places a call at its callee (`ctx.ghost.post.create(…)` at
164
+ * `create`). A call inside another's arguments is the inner one. None when no call's code holds
165
+ * it: a call made from a helper, or from a function defined inside `run`, which the outline does
166
+ * not read.
167
+ */
168
+ export declare function callAt(calls: readonly CallNode[], offset: number): CallNode | undefined;
169
+ /** The node under the wrappers that leave its value as it is, and a default's pattern. */
170
+ export declare function unwrap(node: Node): Node;
171
+ /**
172
+ * Every workflow literal in a parsed file: the object literal `defineWorkflow({ name, …, run })`
173
+ * (or `sanoma.defineWorkflow(…)`) is called with, when it has a `name` and a `run` function. For
174
+ * the outline, which picks the one by its name, and the lint, which checks each `run`.
175
+ */
176
+ export declare function workflowLiterals(program: Node): {
177
+ name: Node;
178
+ run: Node;
179
+ }[];