@sanoma-ai/workflows 0.0.0-stage → 0.1.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/LICENSE +105 -0
- package/README.md +493 -2
- package/dist/approvals.d.ts +46 -0
- package/dist/approvals.js +111 -0
- package/dist/approvals.js.map +1 -0
- package/dist/ast.d.ts +57 -0
- package/dist/ast.js +81 -0
- package/dist/ast.js.map +1 -0
- package/dist/call.d.ts +33 -0
- package/dist/call.js +402 -0
- package/dist/call.js.map +1 -0
- package/dist/client.d.ts +167 -0
- package/dist/client.js +400 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +126 -0
- package/dist/config.js +247 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +30 -0
- package/dist/credentials.js +193 -0
- package/dist/credentials.js.map +1 -0
- package/dist/cucumber.d.ts +64 -0
- package/dist/cucumber.js +158 -0
- package/dist/cucumber.js.map +1 -0
- package/dist/datafile.d.ts +62 -0
- package/dist/datafile.js +326 -0
- package/dist/datafile.js.map +1 -0
- package/dist/db.d.ts +58 -0
- package/dist/db.js +179 -0
- package/dist/db.js.map +1 -0
- package/dist/define.d.ts +205 -0
- package/dist/define.js +103 -0
- package/dist/define.js.map +1 -0
- package/dist/describe.d.ts +135 -0
- package/dist/describe.js +127 -0
- package/dist/describe.js.map +1 -0
- package/dist/drift.d.ts +75 -0
- package/dist/drift.js +90 -0
- package/dist/drift.js.map +1 -0
- package/dist/env.d.ts +49 -0
- package/dist/env.js +83 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +88 -0
- package/dist/errors.js +148 -0
- package/dist/errors.js.map +1 -0
- package/dist/expectations.d.ts +26 -0
- package/dist/expectations.js +137 -0
- package/dist/expectations.js.map +1 -0
- package/dist/fake.d.ts +55 -0
- package/dist/fake.js +87 -0
- package/dist/fake.js.map +1 -0
- package/dist/fill.d.ts +9 -0
- package/dist/fill.js +17 -0
- package/dist/fill.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +217 -0
- package/dist/ledger.js +251 -0
- package/dist/ledger.js.map +1 -0
- package/dist/lint.d.ts +32 -0
- package/dist/lint.js +168 -0
- package/dist/lint.js.map +1 -0
- package/dist/log.d.ts +24 -0
- package/dist/log.js +83 -0
- package/dist/log.js.map +1 -0
- package/dist/op.d.ts +257 -0
- package/dist/op.js +188 -0
- package/dist/op.js.map +1 -0
- package/dist/outline.d.ts +179 -0
- package/dist/outline.js +506 -0
- package/dist/outline.js.map +1 -0
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +30 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugin.d.ts +28 -0
- package/dist/plugin.js +25 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +114 -0
- package/dist/policy.js +60 -0
- package/dist/policy.js.map +1 -0
- package/dist/resource.d.ts +212 -0
- package/dist/resource.js +285 -0
- package/dist/resource.js.map +1 -0
- package/dist/resources.d.ts +62 -0
- package/dist/resources.js +197 -0
- package/dist/resources.js.map +1 -0
- package/dist/run.d.ts +87 -0
- package/dist/run.js +2 -0
- package/dist/run.js.map +1 -0
- package/dist/sandbox.d.ts +33 -0
- package/dist/sandbox.js +123 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/scenario.d.ts +212 -0
- package/dist/scenario.js +1079 -0
- package/dist/scenario.js.map +1 -0
- package/dist/scenarios.d.ts +55 -0
- package/dist/scenarios.js +71 -0
- package/dist/scenarios.js.map +1 -0
- package/dist/shared.d.ts +61 -0
- package/dist/shared.js +77 -0
- package/dist/shared.js.map +1 -0
- package/dist/steps.d.ts +76 -0
- package/dist/steps.js +23 -0
- package/dist/steps.js.map +1 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.js +76 -0
- package/dist/version.js.map +1 -0
- package/dist/worker.d.ts +34 -0
- package/dist/worker.js +219 -0
- package/dist/worker.js.map +1 -0
- package/oxlint.json +128 -0
- package/package.json +91 -3
- package/src/approvals.ts +157 -0
- package/src/ast.ts +115 -0
- package/src/call.ts +468 -0
- package/src/client.ts +534 -0
- package/src/config.ts +381 -0
- package/src/credentials.ts +211 -0
- package/src/cucumber.ts +230 -0
- package/src/datafile.ts +409 -0
- package/src/db.ts +197 -0
- package/src/define.ts +285 -0
- package/src/describe.ts +262 -0
- package/src/drift.ts +149 -0
- package/src/env.ts +122 -0
- package/src/errors.ts +202 -0
- package/src/expectations.ts +165 -0
- package/src/fake.ts +156 -0
- package/src/fill.ts +24 -0
- package/src/index.ts +120 -0
- package/src/ledger.ts +419 -0
- package/src/lint.ts +208 -0
- package/src/log.ts +82 -0
- package/src/op.ts +399 -0
- package/src/outline.ts +620 -0
- package/src/paths.ts +32 -0
- package/src/plugin.ts +37 -0
- package/src/policy.ts +139 -0
- package/src/resource.ts +447 -0
- package/src/resources.ts +256 -0
- package/src/run.ts +91 -0
- package/src/sandbox.ts +157 -0
- package/src/scenario.ts +1328 -0
- package/src/scenarios.ts +111 -0
- package/src/shared.ts +116 -0
- package/src/steps.ts +90 -0
- package/src/version.ts +86 -0
- package/src/worker.ts +253 -0
package/dist/db.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { DBOS, DBOSClient } from "@dbos-inc/dbos-sdk";
|
|
2
|
+
import { Client, Pool } from "pg";
|
|
3
|
+
import { warn } from "./log.js";
|
|
4
|
+
import { errorMessage } from "./shared.js";
|
|
5
|
+
/** Where the credentials set from the app live. */
|
|
6
|
+
export const CREDENTIALS_TABLE = "sanoma_credentials";
|
|
7
|
+
/** Where the Postgres ledger keeps its records. */
|
|
8
|
+
export const LEDGER_TABLE = "sanoma_ledger";
|
|
9
|
+
/** The runtime's tables, each with the word the messages name it by. */
|
|
10
|
+
const TABLES = [
|
|
11
|
+
[
|
|
12
|
+
"credentials",
|
|
13
|
+
`CREATE TABLE IF NOT EXISTS ${CREDENTIALS_TABLE} (name text PRIMARY KEY, value text NOT NULL, ` +
|
|
14
|
+
"set_by text NOT NULL, set_at timestamptz NOT NULL DEFAULT now())",
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
"ledger",
|
|
18
|
+
// `n` is the write order, `id` the record's, which the first write of takes, and `seq` the
|
|
19
|
+
// record's place in its run: a run reads in `seq` order, then write order.
|
|
20
|
+
`CREATE TABLE IF NOT EXISTS ${LEDGER_TABLE} (n bigserial PRIMARY KEY, id text NOT NULL UNIQUE, ` +
|
|
21
|
+
`run_id text NOT NULL, seq integer NOT NULL, record jsonb NOT NULL); ` +
|
|
22
|
+
`CREATE INDEX IF NOT EXISTS ${LEDGER_TABLE}_run_seq ON ${LEDGER_TABLE} (run_id, seq, n)`,
|
|
23
|
+
],
|
|
24
|
+
];
|
|
25
|
+
/** The Postgres error code of `err`, or an empty string when it has none. */
|
|
26
|
+
export const pgCode = (err) => err?.code?.toString() ?? "";
|
|
27
|
+
/** Postgres's codes for a row type or a table another process made first. */
|
|
28
|
+
const MADE_ALREADY = new Set(["23505", "42P07"]);
|
|
29
|
+
/** True for the failure of a create that another process won: what was to be made is there. */
|
|
30
|
+
export const madeAlready = (err) => MADE_ALREADY.has(pgCode(err));
|
|
31
|
+
/**
|
|
32
|
+
* A pool on the config's database for `what` (a word for the messages). An idle connection that
|
|
33
|
+
* drops (Postgres restarted) is replaced on the next query; unheard, it would end the process.
|
|
34
|
+
*/
|
|
35
|
+
export function openPool(databaseUrl, what) {
|
|
36
|
+
const pool = new Pool({ connectionString: databaseUrl });
|
|
37
|
+
pool.on("error", (err) => warn(`a ${what} connection failed: ${errorMessage(err)}`));
|
|
38
|
+
return pool;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* What every DBOS handle on the config's database is given (`DBOS.setConfig`, `dbosClient`): DBOS
|
|
42
|
+
* is told not to set Postgres's `idle_in_transaction_session_timeout` on each connection of its
|
|
43
|
+
* pool. It sends that `SET` from the pool's `connect` event without awaiting it (dbos-sdk 5.2.11,
|
|
44
|
+
* dist/src/system_database.js:691), so the pool hands the connection out while it runs: two
|
|
45
|
+
* queries at once on one client, which pg deprecates. `prepareDatabase` sets the same 60 s guard
|
|
46
|
+
* on the database instead. Remove the switch when DBOS awaits its `SET`.
|
|
47
|
+
*/
|
|
48
|
+
export const DBOS_DATABASE_OPTIONS = { systemDatabaseIdleTransactionTimeoutMs: 0 };
|
|
49
|
+
/**
|
|
50
|
+
* A DBOS client of the app `applicationName` on the database at `databaseUrl`, as every client of
|
|
51
|
+
* the runtime, its tests and `@sanoma-ai/testing` make one (`DBOS_DATABASE_OPTIONS`).
|
|
52
|
+
*/
|
|
53
|
+
export const dbosClient = (databaseUrl, applicationName) => DBOSClient.create({
|
|
54
|
+
systemDatabaseUrl: databaseUrl,
|
|
55
|
+
...(applicationName !== undefined && { applicationName }),
|
|
56
|
+
...DBOS_DATABASE_OPTIONS,
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* The `idle_in_transaction_session_timeout` `prepareDatabase` sets: DBOS's own default
|
|
60
|
+
* (`DEFAULT_IDLE_TRANSACTION_TIMEOUT_MS`, 60_000, in dbos-sdk 5.2.11 dist/src/system_database.js),
|
|
61
|
+
* how long a frozen or unreachable client may hold the system database's locks.
|
|
62
|
+
*/
|
|
63
|
+
const IDLE_IN_TRANSACTION_TIMEOUT = "60s";
|
|
64
|
+
/** Postgres's code for a statement the role lacks the privilege for. */
|
|
65
|
+
const INSUFFICIENT_PRIVILEGE = "42501";
|
|
66
|
+
/**
|
|
67
|
+
* A pool for `what` on the database at `databaseUrl`, opened on the first `db()` once the
|
|
68
|
+
* database is prepared (`prepareDatabase`). A preparation that fails lets the pool go, so the
|
|
69
|
+
* next `db()` tries again. `close` ends the pool, for good: a `db()` after it is refused.
|
|
70
|
+
*/
|
|
71
|
+
export function lazyPool(databaseUrl, what) {
|
|
72
|
+
let pool;
|
|
73
|
+
let closed = false;
|
|
74
|
+
return {
|
|
75
|
+
async db() {
|
|
76
|
+
if (closed)
|
|
77
|
+
throw new Error(`the ${what} store is closed`);
|
|
78
|
+
const current = (pool ??= openPool(databaseUrl, what));
|
|
79
|
+
try {
|
|
80
|
+
await prepareDatabase(databaseUrl);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (pool === current)
|
|
84
|
+
pool = undefined;
|
|
85
|
+
await current.end().catch(() => undefined);
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
return current;
|
|
89
|
+
},
|
|
90
|
+
async close() {
|
|
91
|
+
closed = true;
|
|
92
|
+
const current = pool;
|
|
93
|
+
pool = undefined;
|
|
94
|
+
await current?.end();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Each database prepared, or being prepared, by its URL. A failed preparation is forgotten. */
|
|
99
|
+
const prepared = new Map();
|
|
100
|
+
/**
|
|
101
|
+
* Makes the database at `databaseUrl` ready for the runtime, once per process and URL: the
|
|
102
|
+
* database itself and DBOS's tables when they are not there yet (as a worker's launch makes
|
|
103
|
+
* them; credentials may be set, and a ledger read, before any worker has run), then the
|
|
104
|
+
* credentials and ledger tables, and its idle-transaction timeout (`guardIdleTransactions`).
|
|
105
|
+
* Two processes preparing one database both succeed. A
|
|
106
|
+
* preparation that fails is forgotten, so the next call tries again.
|
|
107
|
+
*/
|
|
108
|
+
export function prepareDatabase(databaseUrl) {
|
|
109
|
+
let preparing = prepared.get(databaseUrl);
|
|
110
|
+
if (!preparing) {
|
|
111
|
+
preparing = prepare(databaseUrl).catch((err) => {
|
|
112
|
+
if (prepared.get(databaseUrl) === preparing)
|
|
113
|
+
prepared.delete(databaseUrl);
|
|
114
|
+
throw err;
|
|
115
|
+
});
|
|
116
|
+
prepared.set(databaseUrl, preparing);
|
|
117
|
+
}
|
|
118
|
+
return preparing;
|
|
119
|
+
}
|
|
120
|
+
async function prepare(databaseUrl) {
|
|
121
|
+
try {
|
|
122
|
+
await DBOS.migrate(databaseUrl);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
// The database, or a DBOS table, made by another process meanwhile: it is there.
|
|
126
|
+
if (!madeAlready(err))
|
|
127
|
+
throw new Error(`could not prepare the database: ${errorMessage(err)}`, { cause: err });
|
|
128
|
+
}
|
|
129
|
+
const conn = new Client({ connectionString: databaseUrl });
|
|
130
|
+
await conn.connect();
|
|
131
|
+
try {
|
|
132
|
+
for (const [what, ddl] of TABLES)
|
|
133
|
+
await createTable(conn, ddl, what);
|
|
134
|
+
await guardIdleTransactions(conn);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await conn.end();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Sets `idle_in_transaction_session_timeout` on the database, for each session that starts after,
|
|
142
|
+
* as DBOS would per connection (`DBOS_DATABASE_OPTIONS`), unless the server, the role or the
|
|
143
|
+
* database sets it already. One process at a time (an advisory lock), so two preparing at once
|
|
144
|
+
* both succeed. A role that may not alter the database leaves it, quietly: its administrator decides.
|
|
145
|
+
*/
|
|
146
|
+
async function guardIdleTransactions(db) {
|
|
147
|
+
try {
|
|
148
|
+
await db.query(`DO $$ BEGIN
|
|
149
|
+
PERFORM pg_advisory_xact_lock(hashtext('sanoma.idle_in_transaction_session_timeout'));
|
|
150
|
+
IF current_setting('idle_in_transaction_session_timeout') = '0' AND NOT EXISTS (
|
|
151
|
+
SELECT FROM pg_db_role_setting s JOIN pg_database d ON d.oid = s.setdatabase, unnest(s.setconfig) c
|
|
152
|
+
WHERE d.datname = current_database() AND s.setrole = 0 AND c LIKE 'idle_in_transaction_session_timeout=%'
|
|
153
|
+
) THEN
|
|
154
|
+
EXECUTE format('ALTER DATABASE %I SET idle_in_transaction_session_timeout = %L', current_database(),
|
|
155
|
+
'${IDLE_IN_TRANSACTION_TIMEOUT}');
|
|
156
|
+
END IF;
|
|
157
|
+
END $$`);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
if (pgCode(err) !== INSUFFICIENT_PRIVILEGE) {
|
|
161
|
+
throw new Error(`could not prepare the database: ${errorMessage(err)}`, { cause: err });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Runs `ddl` (`CREATE TABLE IF NOT EXISTS`, and the like) on `db`. Two processes racing to create
|
|
167
|
+
* the same table both succeed; any other failure names the `what` table, with its cause.
|
|
168
|
+
*/
|
|
169
|
+
export async function createTable(db, ddl, what) {
|
|
170
|
+
try {
|
|
171
|
+
await db.query(ddl);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (madeAlready(err))
|
|
175
|
+
return;
|
|
176
|
+
throw new Error(`could not prepare the ${what} table: ${errorMessage(err)}`, { cause: err });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/define.d.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type Op } from "./op.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Who is acting: the person or service that starts a run or decides an approval. `id` is
|
|
5
|
+
* whatever the deployment identifies people by; `groups` are the roles it vouches for.
|
|
6
|
+
*/
|
|
7
|
+
export interface Principal {
|
|
8
|
+
id: string;
|
|
9
|
+
groups?: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const Principal: z.ZodType<Principal>;
|
|
12
|
+
export type Builtin = "approval" | "sleep" | "all";
|
|
13
|
+
export type Use = Op | Builtin;
|
|
14
|
+
/** Who may decide an approval: one person, by `Principal.id`, or anyone in a group, by `Principal.groups`. */
|
|
15
|
+
export type Approver = string | {
|
|
16
|
+
group: string;
|
|
17
|
+
};
|
|
18
|
+
export declare const Approver: z.ZodType<Approver>;
|
|
19
|
+
/** Why `by` may not decide the approval, naming who may. */
|
|
20
|
+
export declare const notApprover: ({ approver }: Pick<ApprovalState, "approver">, by: Principal) => string;
|
|
21
|
+
/** True when the two name the same approver: the same id, or the same group. */
|
|
22
|
+
export declare const sameApprover: (a: Approver, b: Approver) => boolean;
|
|
23
|
+
/** `covers` as given (operations), parsed to their ids. */
|
|
24
|
+
export declare const Covers: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
}, z.core.$strip>, z.ZodTransform<string, {
|
|
27
|
+
id: string;
|
|
28
|
+
}>>>>;
|
|
29
|
+
export interface ApprovalRequest {
|
|
30
|
+
/** Who must approve: a person's id, or `{ group }` for anyone in it. Phase 4 replaces this with a Cedar decision. */
|
|
31
|
+
approver: Approver;
|
|
32
|
+
/**
|
|
33
|
+
* The operations the approval stands for, so a policy can let their calls through once it
|
|
34
|
+
* is approved (`a.covers.includes(op.id)`). None unless given.
|
|
35
|
+
*/
|
|
36
|
+
covers?: Op[];
|
|
37
|
+
links?: string[];
|
|
38
|
+
details?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Checks a `ctx.approval` request as a workflow may give it, so the UI and the ledger get what
|
|
42
|
+
* the types say, and turns `covers` into op ids.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ApprovalRequestSchema: z.ZodObject<{
|
|
45
|
+
approver: z.ZodType<Approver, unknown, z.core.$ZodTypeInternals<Approver, unknown>>;
|
|
46
|
+
covers: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
47
|
+
id: z.ZodString;
|
|
48
|
+
}, z.core.$strip>, z.ZodTransform<string, {
|
|
49
|
+
id: string;
|
|
50
|
+
}>>>>;
|
|
51
|
+
links: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
52
|
+
details: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
/** One approval in a run, as the run sees it. Published as the run's "approvals" event. */
|
|
55
|
+
export interface ApprovalState extends Omit<ApprovalRequest, "covers"> {
|
|
56
|
+
id: string;
|
|
57
|
+
title: string;
|
|
58
|
+
/** Who asked: the workflow (`ctx.approval`), or the policy holding an operation call. */
|
|
59
|
+
requestedBy: "workflow" | "policy";
|
|
60
|
+
/**
|
|
61
|
+
* The operations the approval stands for, by id. A policy hold covers the call it held and
|
|
62
|
+
* any `covers` the policy added; a workflow's approval covers the `covers` it named, or none.
|
|
63
|
+
*/
|
|
64
|
+
covers: string[];
|
|
65
|
+
/** For a policy request: the operation call held, by op id. */
|
|
66
|
+
op?: string;
|
|
67
|
+
/** For a policy request: the ledger `seq` of the call held, which its `op.called` record has. */
|
|
68
|
+
opSeq?: number;
|
|
69
|
+
/** For a policy request: the held call's input. */
|
|
70
|
+
input?: unknown;
|
|
71
|
+
status: "pending" | "approved" | "rejected";
|
|
72
|
+
requestedAt: number;
|
|
73
|
+
decidedBy?: string;
|
|
74
|
+
decidedAt?: number;
|
|
75
|
+
note?: string;
|
|
76
|
+
/** The `id` of the decision message the run decided with, when it had one. */
|
|
77
|
+
decidedWith?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Messages that were ignored: decisions sent by someone who may not decide the approval,
|
|
80
|
+
* and messages that were not a decision. `by` is the sender, when the message named one, and
|
|
81
|
+
* `id` the decision message's own, when it had one.
|
|
82
|
+
*/
|
|
83
|
+
refused: {
|
|
84
|
+
by?: string;
|
|
85
|
+
id?: string;
|
|
86
|
+
at: number;
|
|
87
|
+
reason: string;
|
|
88
|
+
}[];
|
|
89
|
+
}
|
|
90
|
+
export interface ApprovalResult {
|
|
91
|
+
approvedBy: string;
|
|
92
|
+
at: number;
|
|
93
|
+
note?: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* How long `ctx.sleep` waits: until a time (an ISO 8601 date-time with an offset, such as
|
|
97
|
+
* `2026-10-07T09:00:00Z`, or epoch milliseconds), or for a duration whose parts add up. A time
|
|
98
|
+
* already past waits not at all.
|
|
99
|
+
*/
|
|
100
|
+
export type SleepRequest = {
|
|
101
|
+
until: string | number;
|
|
102
|
+
} | {
|
|
103
|
+
ms?: number;
|
|
104
|
+
seconds?: number;
|
|
105
|
+
minutes?: number;
|
|
106
|
+
hours?: number;
|
|
107
|
+
days?: number;
|
|
108
|
+
};
|
|
109
|
+
/** Checks a `ctx.sleep` request that names a time. */
|
|
110
|
+
export declare const SleepUntil: z.ZodObject<{
|
|
111
|
+
until: z.ZodUnion<readonly [z.ZodISODateTime, z.ZodNumber]>;
|
|
112
|
+
}, z.core.$strict>;
|
|
113
|
+
/** Checks a `ctx.sleep` request that names a duration. */
|
|
114
|
+
export declare const SleepFor: z.ZodObject<{
|
|
115
|
+
ms: z.ZodOptional<z.ZodNumber>;
|
|
116
|
+
seconds: z.ZodOptional<z.ZodNumber>;
|
|
117
|
+
minutes: z.ZodOptional<z.ZodNumber>;
|
|
118
|
+
hours: z.ZodOptional<z.ZodNumber>;
|
|
119
|
+
days: z.ZodOptional<z.ZodNumber>;
|
|
120
|
+
}, z.core.$strict>;
|
|
121
|
+
/** Checks a `ctx.all` argument: a list of functions, each one member. */
|
|
122
|
+
export declare const AllMembers: z.ZodArray<z.ZodCustom<() => unknown, () => unknown>>;
|
|
123
|
+
/**
|
|
124
|
+
* What a step is called, beside its operation: `ctx.ghost.post.create(input, { name: "Draft" })`,
|
|
125
|
+
* `ctx.sleep({ days: 1 }, { name: "Wait a day" })`. A string literal, or a top-level `const` of
|
|
126
|
+
* the workflow's file holding one, so the outline reads it. Without one, a call is named by its
|
|
127
|
+
* operation's id, a sleep `Sleep`; an approval by its title. Names are unique in a workflow, so a
|
|
128
|
+
* workflow that calls one operation twice names the calls.
|
|
129
|
+
*/
|
|
130
|
+
export interface StepOptions {
|
|
131
|
+
name?: string;
|
|
132
|
+
}
|
|
133
|
+
type OpsOf<U extends readonly Use[]> = Extract<U[number], Op>;
|
|
134
|
+
type CtxOps<O extends Op> = {
|
|
135
|
+
readonly [V in O["vendor"]]: {
|
|
136
|
+
readonly [R in Extract<O, {
|
|
137
|
+
vendor: V;
|
|
138
|
+
}>["resource"]]: {
|
|
139
|
+
readonly [N in Extract<O, {
|
|
140
|
+
vendor: V;
|
|
141
|
+
resource: R;
|
|
142
|
+
}>["name"]]: Extract<O, {
|
|
143
|
+
vendor: V;
|
|
144
|
+
resource: R;
|
|
145
|
+
name: N;
|
|
146
|
+
}> extends Op<any, any, any, infer I, infer Out> ? (input: I, options?: StepOptions) => Promise<Out> : never;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
interface ApprovalCtx {
|
|
151
|
+
/** Waits, durably, until the approver (or someone in the approver group) approves. Throws `RejectedError` on a rejection. */
|
|
152
|
+
approval(title: string, request: ApprovalRequest): Promise<ApprovalResult>;
|
|
153
|
+
}
|
|
154
|
+
interface SleepCtx {
|
|
155
|
+
/** Waits, durably, until a time or for a duration. Survives worker restarts. */
|
|
156
|
+
sleep(request: SleepRequest, options?: StepOptions): Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
interface AllCtx {
|
|
159
|
+
/**
|
|
160
|
+
* Runs the members one after another, in order, and returns their outputs in that order. It
|
|
161
|
+
* declares a fan-out for the ledger and the graph; it adds no concurrency. The first member
|
|
162
|
+
* that throws stops the group with its error, and later members never run. Not nestable.
|
|
163
|
+
*/
|
|
164
|
+
all<const T extends readonly (() => Promise<unknown>)[]>(members: T): Promise<{
|
|
165
|
+
-readonly [K in keyof T]: Awaited<ReturnType<T[K]>>;
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
interface BaseCtx {
|
|
169
|
+
/** The current time, recorded so replays see the same value. Use instead of `Date.now()`. */
|
|
170
|
+
now(): Promise<number>;
|
|
171
|
+
readonly runId: string;
|
|
172
|
+
}
|
|
173
|
+
export type Ctx<U extends readonly Use[]> = BaseCtx & CtxOps<OpsOf<U>> & ("approval" extends U[number] ? ApprovalCtx : unknown) & ("sleep" extends U[number] ? SleepCtx : unknown) & ("all" extends U[number] ? AllCtx : unknown);
|
|
174
|
+
/**
|
|
175
|
+
* A schema as JSON Schema (draft 2020-12). By default it describes what a caller sends
|
|
176
|
+
* (`io: "input"`: defaults stay optional); `io: "output"` describes what parsing returns.
|
|
177
|
+
* What JSON Schema can't express is left open. Throws when zod can't convert the schema at all.
|
|
178
|
+
*/
|
|
179
|
+
export declare function jsonSchemaOf(schema: z.ZodType, io?: "input" | "output", refs?: ReadonlyMap<unknown, string>): Record<string, unknown>;
|
|
180
|
+
export interface WorkflowDefinition<U extends readonly Use[] = readonly Use[], S extends z.ZodType = z.ZodType> {
|
|
181
|
+
readonly kind: "workflow";
|
|
182
|
+
readonly name: string;
|
|
183
|
+
readonly title?: string;
|
|
184
|
+
readonly trigger: "manual";
|
|
185
|
+
readonly input: S;
|
|
186
|
+
/** Every operation and built-in the workflow may call. `ctx` is built from this list and nothing else. */
|
|
187
|
+
readonly uses: U;
|
|
188
|
+
readonly run: (ctx: Ctx<U>, input: z.output<S>) => Promise<unknown>;
|
|
189
|
+
/**
|
|
190
|
+
* The file that defined it, absolute. Set by defineWorkflow from its call site: the outline is
|
|
191
|
+
* read from it, the worker holds each run to that outline, and the app shows it as the code view.
|
|
192
|
+
*/
|
|
193
|
+
readonly file: string;
|
|
194
|
+
}
|
|
195
|
+
/** The ids of the operations a workflow `uses`, in the order declared. */
|
|
196
|
+
export declare const opIdsOf: (wf: Pick<WorkflowDefinition, "uses">) => string[];
|
|
197
|
+
export declare function defineWorkflow<const U extends readonly Use[], S extends z.ZodType>(def: Omit<WorkflowDefinition<U, S>, "kind" | "file">): WorkflowDefinition<U, S>;
|
|
198
|
+
/**
|
|
199
|
+
* The file of the code that called the function calling this one (`defineWorkflow`'s caller),
|
|
200
|
+
* from V8's call sites, as a path; undefined where there is none.
|
|
201
|
+
*/
|
|
202
|
+
export declare function callerFile(): string | undefined;
|
|
203
|
+
/** A V8 script name (a `file:` URL for a module, a path for a script) as a path; undefined for none. */
|
|
204
|
+
export declare function scriptPath(script: string | undefined): string | undefined;
|
|
205
|
+
export {};
|
package/dist/define.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { getCallSites } from "node:util";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { isOp } from "./op.js";
|
|
5
|
+
import { approverLabel } from "./shared.js";
|
|
6
|
+
export const Principal = z.object({
|
|
7
|
+
id: z.string().regex(/\S/, "id must not be blank"),
|
|
8
|
+
groups: z.array(z.string()).optional(),
|
|
9
|
+
});
|
|
10
|
+
export const Approver = z.union([z.string().regex(/\S/), z.object({ group: z.string().regex(/\S/) })], { error: 'needs an approver: a name, or { group: "name" }' });
|
|
11
|
+
/** Why `by` may not decide the approval, naming who may. */
|
|
12
|
+
export const notApprover = ({ approver }, by) => typeof approver === "string"
|
|
13
|
+
? `${by.id} is not the approver; ${approverLabel(approver)} is`
|
|
14
|
+
: `${by.id} is not in ${approverLabel(approver)}`;
|
|
15
|
+
/** True when the two name the same approver: the same id, or the same group. */
|
|
16
|
+
export const sameApprover = (a, b) => typeof a === "string" || typeof b === "string" ? a === b : a.group === b.group;
|
|
17
|
+
const notOps = "covers must be a list of operations";
|
|
18
|
+
/** `covers` as given (operations), parsed to their ids. */
|
|
19
|
+
export const Covers = z
|
|
20
|
+
.array(z.object({ id: z.string() }, { error: notOps }).transform((op) => op.id), { error: notOps })
|
|
21
|
+
.optional();
|
|
22
|
+
/**
|
|
23
|
+
* Checks a `ctx.approval` request as a workflow may give it, so the UI and the ledger get what
|
|
24
|
+
* the types say, and turns `covers` into op ids.
|
|
25
|
+
*/
|
|
26
|
+
export const ApprovalRequestSchema = z.object({
|
|
27
|
+
approver: Approver,
|
|
28
|
+
covers: Covers,
|
|
29
|
+
links: z.array(z.string(), { error: "links must be a list of strings" }).optional(),
|
|
30
|
+
details: z.string({ error: "details must be a string" }).optional(),
|
|
31
|
+
}, { error: "needs a request: { approver, covers?, links?, details? }" });
|
|
32
|
+
const time = "must be an ISO 8601 date-time with an offset, such as 2026-10-07T09:00:00Z, or epoch milliseconds";
|
|
33
|
+
const duration = z.number({ error: "must be a number" }).nonnegative("must not be negative").optional();
|
|
34
|
+
/** Checks a `ctx.sleep` request that names a time. */
|
|
35
|
+
export const SleepUntil = z.strictObject({
|
|
36
|
+
until: z.union([z.iso.datetime({ offset: true, error: time }), z.number({ error: time })], { error: time }),
|
|
37
|
+
});
|
|
38
|
+
/** Checks a `ctx.sleep` request that names a duration. */
|
|
39
|
+
export const SleepFor = z.strictObject({
|
|
40
|
+
ms: duration,
|
|
41
|
+
seconds: duration,
|
|
42
|
+
minutes: duration,
|
|
43
|
+
hours: duration,
|
|
44
|
+
days: duration,
|
|
45
|
+
});
|
|
46
|
+
/** Checks a `ctx.all` argument: a list of functions, each one member. */
|
|
47
|
+
export const AllMembers = z.array(z.custom((m) => typeof m === "function", "each member must be a function, such as () => ctx.…"), { error: "needs a list of functions: ctx.all([() => …, () => …])" });
|
|
48
|
+
/**
|
|
49
|
+
* A schema as JSON Schema (draft 2020-12). By default it describes what a caller sends
|
|
50
|
+
* (`io: "input"`: defaults stay optional); `io: "output"` describes what parsing returns.
|
|
51
|
+
* What JSON Schema can't express is left open. Throws when zod can't convert the schema at all.
|
|
52
|
+
*/
|
|
53
|
+
export function jsonSchemaOf(schema, io = "input", refs) {
|
|
54
|
+
return z.toJSONSchema(schema, {
|
|
55
|
+
io,
|
|
56
|
+
target: "draft-2020-12",
|
|
57
|
+
unrepresentable: "any",
|
|
58
|
+
// A schema `refs` names, inside another, becomes a `$ref` to it (a resource type's state).
|
|
59
|
+
...(refs?.size && {
|
|
60
|
+
override: ({ zodSchema, jsonSchema, path }) => {
|
|
61
|
+
const ref = path.length > 0 ? refs.get(zodSchema) : undefined;
|
|
62
|
+
if (ref === undefined)
|
|
63
|
+
return;
|
|
64
|
+
for (const key of Object.keys(jsonSchema))
|
|
65
|
+
delete jsonSchema[key];
|
|
66
|
+
Object.assign(jsonSchema, { $ref: ref });
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** The ids of the operations a workflow `uses`, in the order declared. */
|
|
72
|
+
export const opIdsOf = (wf) => wf.uses.filter(isOp).map((op) => op.id);
|
|
73
|
+
export function defineWorkflow(def) {
|
|
74
|
+
if (!/^[a-z][a-z0-9-]*$/.test(def.name)) {
|
|
75
|
+
throw new Error(`Workflow name "${def.name}" must be lowercase letters, digits and dashes`);
|
|
76
|
+
}
|
|
77
|
+
const file = callerFile();
|
|
78
|
+
if (file === undefined) {
|
|
79
|
+
throw new Error(`Workflow "${def.name}" was defined from code with no file (an eval, or a bundle without one): ` +
|
|
80
|
+
"its outline is read from the file, and a run is held to the outline");
|
|
81
|
+
}
|
|
82
|
+
return Object.freeze({ kind: "workflow", ...def, file });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The file of the code that called the function calling this one (`defineWorkflow`'s caller),
|
|
86
|
+
* from V8's call sites, as a path; undefined where there is none.
|
|
87
|
+
*/
|
|
88
|
+
export function callerFile() {
|
|
89
|
+
// Frame 0 is this function, frame 1 its caller, frame 2 theirs: by index, since a bundle may hold all three.
|
|
90
|
+
return scriptPath(getCallSites(3)[2]?.scriptName);
|
|
91
|
+
}
|
|
92
|
+
/** A V8 script name (a `file:` URL for a module, a path for a script) as a path; undefined for none. */
|
|
93
|
+
export function scriptPath(script) {
|
|
94
|
+
if (!script?.startsWith("file:"))
|
|
95
|
+
return script || undefined;
|
|
96
|
+
try {
|
|
97
|
+
return fileURLToPath(script);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=define.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,IAAI,EAAW,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAW5C,MAAM,CAAC,MAAM,SAAS,GAAyB,CAAC,CAAC,MAAM,CAAC;IACtD,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAClD,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAQH,MAAM,CAAC,MAAM,QAAQ,GAAwB,CAAC,CAAC,KAAK,CAClD,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EACrE,EAAE,KAAK,EAAE,iDAAiD,EAAE,CAC7D,CAAC;AAEF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,QAAQ,EAAmC,EAAE,EAAa,EAAU,EAAE,CAClG,OAAO,QAAQ,KAAK,QAAQ;IAC1B,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,yBAAyB,aAAa,CAAC,QAAQ,CAAC,KAAK;IAC/D,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,cAAc,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;AAEtD,gFAAgF;AAChF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAW,EAAE,CAAW,EAAW,EAAE,CAChE,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC;AAEjF,MAAM,MAAM,GAAG,qCAAqC,CAAC;AAErD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC;KACpB,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EACxE,EAAE,KAAK,EAAE,MAAM,EAAE,CAClB;KACA,QAAQ,EAAE,CAAC;AAcd;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAC3C;IACE,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACnF,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC,QAAQ,EAAE;CACpE,EACD,EAAE,KAAK,EAAE,0DAA0D,EAAE,CACtE,CAAC;AAiDF,MAAM,IAAI,GAAG,mGAAmG,CAAC;AACjH,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE,CAAC;AAExG,sDAAsD;AACtD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;CAC5G,CAAmC,CAAC;AAErC,0DAA0D;AAC1D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,YAAY,CAAC;IACrC,EAAE,EAAE,QAAQ;IACZ,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE,QAAQ;IACjB,KAAK,EAAE,QAAQ;IACf,IAAI,EAAE,QAAQ;CACf,CAAmC,CAAC;AAErC,yEAAyE;AACzE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAC/B,CAAC,CAAC,MAAM,CAAgB,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE,qDAAqD,CAAC,EAC9G,EAAE,KAAK,EAAE,wDAAwD,EAAE,CACpE,CAAC;AA6DF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAiB,EACjB,EAAE,GAAuB,OAAO,EAChC,IAAmC;IAEnC,OAAO,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE;QAC5B,EAAE;QACF,MAAM,EAAE,eAAe;QACvB,eAAe,EAAE,KAAK;QACtB,2FAA2F;QAC3F,GAAG,CAAC,IAAI,EAAE,IAAI,IAAI;YAChB,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE;gBAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC9D,IAAI,GAAG,KAAK,SAAS;oBAAE,OAAO;gBAC9B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;oBAAE,OAAQ,UAAsC,CAAC,GAAG,CAAC,CAAC;gBAC/F,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YAC3C,CAAC;SACF,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAkBD,0EAA0E;AAC1E,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAoC,EAAY,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAEnH,MAAM,UAAU,cAAc,CAC5B,GAAoD;IAEpD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,IAAI,gDAAgD,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,aAAa,GAAG,CAAC,IAAI,2EAA2E;YAC9F,qEAAqE,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,6GAA6G;IAC7G,OAAO,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AACpD,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,UAAU,CAAC,MAA0B;IACnD,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,IAAI,SAAS,CAAC;IAC7D,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type LintProblem } from "./ast.ts";
|
|
2
|
+
import { type SanomaConfig } from "./config.ts";
|
|
3
|
+
import { type Builtin } from "./define.ts";
|
|
4
|
+
import { type Effect } from "./op.ts";
|
|
5
|
+
import { type Outline } from "./outline.ts";
|
|
6
|
+
import { type ResourceFields } from "./resource.ts";
|
|
7
|
+
import { type DeclaredResource, type ResourceProblem } from "./resources.ts";
|
|
8
|
+
export { outlineWorkflow, type Outline, type OutlineNode, type Span } from "./outline.ts";
|
|
9
|
+
export type { LintProblem } from "./ast.ts";
|
|
10
|
+
export { readDataFiles, readResources, type DataFiles, type DeclaredResource, type ResourceProblem, } from "./resources.ts";
|
|
11
|
+
/** What a workflow is, read from its definition: enough to draw a start form and show what it may call. */
|
|
12
|
+
export interface WorkflowEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
trigger: "manual";
|
|
16
|
+
/**
|
|
17
|
+
* Set for a workflow the runtime adds itself, `drift`, rather than the config: a label. It
|
|
18
|
+
* starts like any other.
|
|
19
|
+
*/
|
|
20
|
+
builtin?: true;
|
|
21
|
+
/** The input, as JSON Schema (draft 2020-12), from the zod schema. */
|
|
22
|
+
input: Record<string, unknown>;
|
|
23
|
+
/** Operation ids the workflow may call, in the order declared. */
|
|
24
|
+
ops: string[];
|
|
25
|
+
/** Built-ins the workflow may call. */
|
|
26
|
+
builtins: Builtin[];
|
|
27
|
+
/** The file that defined it, absolute. */
|
|
28
|
+
file: string;
|
|
29
|
+
/** What its `run` calls, in order, read from its file (`outlineWorkflow`), which the worker holds its runs to. */
|
|
30
|
+
outline: Outline;
|
|
31
|
+
/**
|
|
32
|
+
* What is wrong in its file, by position: what `lintWorkflow` finds (syntax, an import a
|
|
33
|
+
* workflow may not make, `instanceof` against the runtime's error classes, what the outline
|
|
34
|
+
* cannot draw in each `run`), read the one way the outline is, so a check sees what the
|
|
35
|
+
* worker refuses; and, at 1:1, an outline error that is the file's (it cannot be read, or holds
|
|
36
|
+
* no workflow of this name). A file defining several workflows gives each its problems. Empty
|
|
37
|
+
* for the built-in `drift`, which the package's tests hold to the lint.
|
|
38
|
+
*/
|
|
39
|
+
problems: LintProblem[];
|
|
40
|
+
/**
|
|
41
|
+
* The text the outline's spans index into: the workflow's file, with `\n` line endings.
|
|
42
|
+
* Absent only when the outline is `{ error }`. Whole files: a server may keep it from clients.
|
|
43
|
+
*/
|
|
44
|
+
source?: string;
|
|
45
|
+
}
|
|
46
|
+
/** What an operation is, read from its connector: enough to badge it and show its contract. */
|
|
47
|
+
export interface OpEntry {
|
|
48
|
+
id: string;
|
|
49
|
+
vendor: string;
|
|
50
|
+
resource: string;
|
|
51
|
+
name: string;
|
|
52
|
+
effect: Effect;
|
|
53
|
+
idempotent: boolean;
|
|
54
|
+
description?: string;
|
|
55
|
+
/** How a scenario's steps name it: Cucumber expressions over its input's fields. */
|
|
56
|
+
phrases?: {
|
|
57
|
+
given?: string;
|
|
58
|
+
expect?: string;
|
|
59
|
+
};
|
|
60
|
+
/** What a call sends, as JSON Schema (`io: "input"`: fields with defaults are optional). */
|
|
61
|
+
input: Record<string, unknown>;
|
|
62
|
+
/** What a call returns once its schema has parsed the vendor's reply (`io: "output"`). */
|
|
63
|
+
output: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
/** Who a vendor is, from its connector's `VendorInfo`: enough to name it, show its logo and link to its connector. */
|
|
66
|
+
export interface VendorEntry {
|
|
67
|
+
/** The connector's `title`, else the vendor's id. */
|
|
68
|
+
title: string;
|
|
69
|
+
/** The logo as `data:image/svg+xml` URLs, for an `<img>`: `dark` is for dark backgrounds. */
|
|
70
|
+
logo?: {
|
|
71
|
+
src: string;
|
|
72
|
+
dark?: string;
|
|
73
|
+
};
|
|
74
|
+
/** The connector's npm package name. */
|
|
75
|
+
package?: string;
|
|
76
|
+
/** The connector package's `homepage` from its package.json: where its code and README are, as an https URL. */
|
|
77
|
+
homepage?: string;
|
|
78
|
+
/** Whether the vendor's connector attaches a fake (`withSetup`), for sandbox runs. */
|
|
79
|
+
fake: boolean;
|
|
80
|
+
}
|
|
81
|
+
/** A resource type a connector declares with `defineResource`. */
|
|
82
|
+
export interface ResourceTypeEntry {
|
|
83
|
+
/** `<vendor>.<type>`, such as `github.repository`. */
|
|
84
|
+
id: string;
|
|
85
|
+
vendor: string;
|
|
86
|
+
type: string;
|
|
87
|
+
title: string;
|
|
88
|
+
/** How a declared resource's import id is made, such as `name` or `repository_id:pattern`. */
|
|
89
|
+
identity: string;
|
|
90
|
+
/** Its flagged fields, by dotted path. */
|
|
91
|
+
fields: ResourceFields;
|
|
92
|
+
/**
|
|
93
|
+
* Its state, as a read returns it, as JSON Schema (`io: "output"`), with `$id`
|
|
94
|
+
* `sanoma:resource-type/<id>`: the operations' contracts `$ref` it instead of repeating it.
|
|
95
|
+
*/
|
|
96
|
+
schema: Record<string, unknown>;
|
|
97
|
+
/** Its operations' ids: `<id>.import` and `<id>.read`. */
|
|
98
|
+
ops: string[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* A plain-JSON description of what a config can do: its workflows, the operations they call,
|
|
102
|
+
* and whether a policy gates them. The app server builds it from the config and sends it to the
|
|
103
|
+
* browser, so the UI shows exactly what the worker enforces. Never written by hand.
|
|
104
|
+
*/
|
|
105
|
+
export interface ConfigDescription {
|
|
106
|
+
appName: string;
|
|
107
|
+
/** The application version runs started now are stamped with. */
|
|
108
|
+
version: string;
|
|
109
|
+
workflows: WorkflowEntry[];
|
|
110
|
+
ops: OpEntry[];
|
|
111
|
+
/** Every vendor the operations are from, by its id (an `OpEntry`'s `vendor`). */
|
|
112
|
+
vendors: Record<string, VendorEntry>;
|
|
113
|
+
/** The resource types the connectors declare, by `id`. */
|
|
114
|
+
resourceTypes: ResourceTypeEntry[];
|
|
115
|
+
/**
|
|
116
|
+
* The resources the data files under `resources/` declare, by file and then in file order,
|
|
117
|
+
* less any with a problem and any that names one.
|
|
118
|
+
*/
|
|
119
|
+
resources: DeclaredResource[];
|
|
120
|
+
/** What is wrong in the data files (`readDataFiles`), each at its file, relative to the root, line and column. A workflow's are on its entry. */
|
|
121
|
+
problems: ResourceProblem[];
|
|
122
|
+
/** `defined` is false for `allowAll`. */
|
|
123
|
+
policy: {
|
|
124
|
+
defined: boolean;
|
|
125
|
+
version?: string;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Describes a config. Throws what `startWorker` would refuse (see `resolveConfig`), except missing
|
|
130
|
+
* credentials (`SanomaClient.credentials` gives their statuses) and what is wrong in a workflow's
|
|
131
|
+
* file, which the worker refuses when it reads the outline: that is data, on each workflow's
|
|
132
|
+
* entry, as the data files' problems are, `problems`, beside the resources read without any.
|
|
133
|
+
* Each workflow's file is read and parsed once, however many workflows it defines.
|
|
134
|
+
*/
|
|
135
|
+
export declare function describeConfig(config: SanomaConfig): ConfigDescription;
|