@plitzi/sdk-shared 0.35.4 → 0.35.6
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/CHANGELOG.md +23 -0
- package/dist/actions/failureHandler.d.ts +8 -0
- package/dist/actions/failureHandler.mjs +4 -0
- package/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.mjs +6 -4
- package/dist/actions/smtp.d.ts +34 -0
- package/dist/actions/smtp.mjs +51 -0
- package/dist/actions/validateDocument.mjs +119 -87
- package/dist/network/spaceEvents.d.ts +6 -6
- package/dist/store/index.d.ts +45 -45
- package/dist/style/styleConstants.d.ts +2 -2
- package/dist/types/ActionTypes.d.ts +37 -9
- package/dist/types/InteractionTypes.d.ts +6 -0
- package/dist/types/ServerTypes.d.ts +5 -5
- package/dist/types/SpaceTypes.d.ts +1 -1
- package/package.json +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @plitzi/sdk-shared
|
|
2
2
|
|
|
3
|
+
## 0.35.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- A failed server action can give back what it already did.
|
|
8
|
+
|
|
9
|
+
- **`flow.onFailure` ("On Failure") marks where the undo begins.** A run that reaches it has succeeded and ends there.
|
|
10
|
+
A run whose step failed jumps to it and runs the steps after it, in order, each still asking its own `when` — the
|
|
11
|
+
failure may have come before the thing to undo was ever done — with `{{ failure.step }}` and `{{ failure.message }}`
|
|
12
|
+
in scope. The run still ends failed, with the failure it had; an undo step that fails too is added to it.
|
|
13
|
+
- **The undo runs on its own clock and budget:** 5 seconds (or the run's own timeout if shorter) and its own outbound
|
|
14
|
+
request budget, because the run's may be exactly what ran out, and a caller closing the connection does not stop it.
|
|
15
|
+
A run that hit its deadline is undone too.
|
|
16
|
+
- **`defineAction` writes it from `onFailure: [...]`**, after the answer. `validateActionDocument` refuses an output
|
|
17
|
+
step or a second handler after one, warns about a handler with nothing to undo or nothing that undoes, and reserves
|
|
18
|
+
`failure` as a step id. `FAILURE_HANDLER_TASK` is exported from `@plitzi/sdk-shared/actions`.
|
|
19
|
+
|
|
20
|
+
## 0.35.5
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- v0.35.5
|
|
25
|
+
|
|
3
26
|
## 0.35.4
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step that marks where a server action's undo begins.
|
|
3
|
+
*
|
|
4
|
+
* Steps after it run only when a step before it failed, so an action can give back what it already did — seats it took,
|
|
5
|
+
* a row it wrote — instead of leaving it behind a failure. Named once because four places read it: the runner that jumps
|
|
6
|
+
* to it, the validator that checks where it sits, the authoring API that writes it and the task catalog that offers it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const FAILURE_HANDLER_TASK = "flow.onFailure";
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { cronMatches, isKnownTimeZone, parseCron, zonedClock } from './cron';
|
|
2
|
+
export { FAILURE_HANDLER_TASK } from './failureHandler';
|
|
2
3
|
export { triggerAccess, triggerCacheMs, triggerHasStaleVerify, triggerInput, triggerVerify } from './triggerParams';
|
|
3
4
|
export { actionName, actionTriggers, isActionEnabled } from './triggers';
|
|
4
5
|
export { validateActionDocument } from './validateDocument';
|
|
6
|
+
export { isSingleEmailAddress, readSmtpCredential, SMTP_CREDENTIAL_KEYS } from './smtp';
|
|
5
7
|
export type { CronExpression } from './cron';
|
|
8
|
+
export type { SmtpCredentialProblem, SmtpCredentialReading, SmtpSecurity, SmtpSettings } from './smtp';
|
|
6
9
|
export type { ActionDocumentIssue, ActionDocumentReport } from './validateDocument';
|
package/dist/actions/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cronMatches as e, isKnownTimeZone as t, parseCron as n, zonedClock as r } from "./cron.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import { FAILURE_HANDLER_TASK as i } from "./failureHandler.mjs";
|
|
3
|
+
import { triggerAccess as a, triggerCacheMs as o, triggerHasStaleVerify as s, triggerInput as c, triggerVerify as l } from "./triggerParams.mjs";
|
|
4
|
+
import { actionName as u, actionTriggers as d, isActionEnabled as f } from "./triggers.mjs";
|
|
5
|
+
import { validateActionDocument as p } from "./validateDocument.mjs";
|
|
6
|
+
import { SMTP_CREDENTIAL_KEYS as m, isSingleEmailAddress as h, readSmtpCredential as g } from "./smtp.mjs";
|
|
7
|
+
export { i as FAILURE_HANDLER_TASK, m as SMTP_CREDENTIAL_KEYS, u as actionName, d as actionTriggers, e as cronMatches, f as isActionEnabled, t as isKnownTimeZone, h as isSingleEmailAddress, n as parseCron, g as readSmtpCredential, a as triggerAccess, o as triggerCacheMs, s as triggerHasStaleVerify, c as triggerInput, l as triggerVerify, p as validateActionDocument, r as zonedClock };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SMTP server a space sends its flows' mail through, as its credential stores it — one rule for everybody who
|
|
3
|
+
* reads one: the editor saving it, the API accepting it, the check reporting on it and the `email.send` task using it.
|
|
4
|
+
*
|
|
5
|
+
* A credential is a bag of strings, so every value arrives as text and is judged here: a port that is not a port, a
|
|
6
|
+
* sender that is not one address, a password with no user to go with it. Each problem names the key it is about, so a
|
|
7
|
+
* form can put the sentence under the field.
|
|
8
|
+
*/
|
|
9
|
+
export type SmtpSecurity = 'tls' | 'starttls' | 'none';
|
|
10
|
+
export type SmtpSettings = {
|
|
11
|
+
host: string;
|
|
12
|
+
port: number;
|
|
13
|
+
/** `tls` connects encrypted (usually 465); `starttls` upgrades a plain connection (usually 587); `none` never does. */
|
|
14
|
+
security: SmtpSecurity;
|
|
15
|
+
/** Empty with an empty password: a relay that authenticates by where the connection comes from. */
|
|
16
|
+
username: string;
|
|
17
|
+
password: string;
|
|
18
|
+
fromEmail: string;
|
|
19
|
+
fromName: string;
|
|
20
|
+
};
|
|
21
|
+
export type SmtpCredentialProblem = {
|
|
22
|
+
key: keyof SmtpSettings;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
export type SmtpCredentialReading = {
|
|
26
|
+
settings: SmtpSettings;
|
|
27
|
+
problems: [];
|
|
28
|
+
} | {
|
|
29
|
+
problems: SmtpCredentialProblem[];
|
|
30
|
+
};
|
|
31
|
+
/** The keys an SMTP credential holds, in the order an editor asks for them. */
|
|
32
|
+
export declare const SMTP_CREDENTIAL_KEYS: readonly (keyof SmtpSettings)[];
|
|
33
|
+
export declare const isSingleEmailAddress: (value: string) => boolean;
|
|
34
|
+
export declare const readSmtpCredential: (data: Record<string, unknown>) => SmtpCredentialReading;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/actions/smtp.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"host",
|
|
4
|
+
"port",
|
|
5
|
+
"security",
|
|
6
|
+
"username",
|
|
7
|
+
"password",
|
|
8
|
+
"fromEmail",
|
|
9
|
+
"fromName"
|
|
10
|
+
], t = [
|
|
11
|
+
"tls",
|
|
12
|
+
"starttls",
|
|
13
|
+
"none"
|
|
14
|
+
], n = /^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/, r = /^[^\s@,;<>"]+@[^\s@,;<>"]+\.[^\s@,;<>"]+$/, i = (e) => r.test(e), a = (e) => t.some((t) => t === e), o = (e, t) => {
|
|
15
|
+
let n = e[t];
|
|
16
|
+
return typeof n == "number" ? String(n) : typeof n == "string" ? n.trim() : "";
|
|
17
|
+
}, s = (e) => {
|
|
18
|
+
let t = o(e, "host"), r = Number(o(e, "port")), s = o(e, "security"), c = o(e, "username"), l = typeof e.password == "string" ? e.password : "", u = o(e, "fromEmail"), d = o(e, "fromName"), f = [];
|
|
19
|
+
return n.test(t) || f.push({
|
|
20
|
+
key: "host",
|
|
21
|
+
message: "A host name like smtp.example.com, with no scheme and no port"
|
|
22
|
+
}), (!Number.isInteger(r) || r < 1 || r > 65535) && f.push({
|
|
23
|
+
key: "port",
|
|
24
|
+
message: "A port number, usually 587 or 465"
|
|
25
|
+
}), a(s) || f.push({
|
|
26
|
+
key: "security",
|
|
27
|
+
message: "One of tls, starttls or none"
|
|
28
|
+
}), l !== "" && c === "" && f.push({
|
|
29
|
+
key: "username",
|
|
30
|
+
message: "A password needs the user it belongs to"
|
|
31
|
+
}), i(u) || f.push({
|
|
32
|
+
key: "fromEmail",
|
|
33
|
+
message: "The one address this server sends as"
|
|
34
|
+
}), (/[\r\n]/.test(d) || d.length > 100) && f.push({
|
|
35
|
+
key: "fromName",
|
|
36
|
+
message: "One line of at most 100 characters"
|
|
37
|
+
}), f.length > 0 || !a(s) ? { problems: f } : {
|
|
38
|
+
settings: {
|
|
39
|
+
host: t,
|
|
40
|
+
port: r,
|
|
41
|
+
security: s,
|
|
42
|
+
username: c,
|
|
43
|
+
password: l,
|
|
44
|
+
fromEmail: u,
|
|
45
|
+
fromName: d
|
|
46
|
+
},
|
|
47
|
+
problems: []
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
export { e as SMTP_CREDENTIAL_KEYS, i as isSingleEmailAddress, s as readSmtpCredential };
|
|
@@ -1,41 +1,43 @@
|
|
|
1
1
|
import { isKnownTimeZone as e, parseCron as t } from "./cron.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { FAILURE_HANDLER_TASK as n } from "./failureHandler.mjs";
|
|
3
|
+
import { triggerAccess as r, triggerHasStaleVerify as i, triggerVerify as a } from "./triggerParams.mjs";
|
|
3
4
|
//#region src/actions/validateDocument.ts
|
|
4
|
-
var
|
|
5
|
+
var o = [
|
|
5
6
|
"text",
|
|
6
7
|
"number",
|
|
7
8
|
"boolean",
|
|
8
9
|
"date",
|
|
9
10
|
"json",
|
|
10
11
|
"file"
|
|
11
|
-
],
|
|
12
|
+
], s = [
|
|
12
13
|
"public",
|
|
13
14
|
"session",
|
|
14
15
|
"role"
|
|
15
|
-
],
|
|
16
|
+
], c = [
|
|
16
17
|
"call",
|
|
17
18
|
"webhook",
|
|
18
19
|
"schedule",
|
|
19
20
|
"render",
|
|
20
21
|
"custom"
|
|
21
|
-
],
|
|
22
|
+
], l = ["sha256", "sha1"], u = /* @__PURE__ */ new Set([
|
|
22
23
|
"input",
|
|
23
24
|
"user",
|
|
24
25
|
"spaceId",
|
|
25
26
|
"environment",
|
|
26
27
|
"trigger",
|
|
27
28
|
"runId",
|
|
28
|
-
"now"
|
|
29
|
-
|
|
29
|
+
"now",
|
|
30
|
+
"failure"
|
|
31
|
+
]), d = /^[a-z][a-zA-Z0-9]*\.[a-z][a-zA-Z0-9]*$/, f = (e) => typeof e == "object" && !!e && !Array.isArray(e), p = (e) => typeof e == "string" && e.trim() !== "", m = (e) => {
|
|
30
32
|
try {
|
|
31
33
|
let t = JSON.parse(e);
|
|
32
|
-
return
|
|
34
|
+
return f(t) ? t : void 0;
|
|
33
35
|
} catch {
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
36
|
-
},
|
|
38
|
+
}, h = (e, t, n) => {
|
|
37
39
|
if (e !== void 0) {
|
|
38
|
-
if (!
|
|
40
|
+
if (!f(e)) {
|
|
39
41
|
n.push({
|
|
40
42
|
path: t,
|
|
41
43
|
message: "must be an object of field definitions"
|
|
@@ -43,215 +45,245 @@ var a = [
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
Object.entries(e).forEach(([e, r]) => {
|
|
46
|
-
if (!
|
|
48
|
+
if (!f(r) || !p(r.type)) {
|
|
47
49
|
n.push({
|
|
48
50
|
path: `${t}.${e}`,
|
|
49
51
|
message: "must declare a type"
|
|
50
52
|
});
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
|
-
|
|
55
|
+
o.includes(r.type) || n.push({
|
|
54
56
|
path: `${t}.${e}.type`,
|
|
55
57
|
message: `unknown field type "${r.type}"`,
|
|
56
|
-
hint: `one of ${
|
|
58
|
+
hint: `one of ${o.join(", ")}`
|
|
57
59
|
});
|
|
58
60
|
});
|
|
59
61
|
}
|
|
60
|
-
},
|
|
61
|
-
let g = `nodes.${
|
|
62
|
-
if (!
|
|
62
|
+
}, g = (n, o, u, d) => {
|
|
63
|
+
let g = `nodes.${n}`, _ = o.action;
|
|
64
|
+
if (!p(_) || !c.includes(_)) {
|
|
63
65
|
u.push({
|
|
64
66
|
path: `${g}.action`,
|
|
65
67
|
message: `"${String(_)}" is not a way an action can be started`,
|
|
66
|
-
hint: `one of ${
|
|
68
|
+
hint: `one of ${c.join(", ")}`
|
|
67
69
|
});
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
|
-
let v =
|
|
71
|
-
if (
|
|
72
|
-
let e =
|
|
72
|
+
let v = f(o.params) ? o.params : {};
|
|
73
|
+
if (p(v.input)) {
|
|
74
|
+
let e = m(v.input);
|
|
73
75
|
e === void 0 ? u.push({
|
|
74
76
|
path: `${g}.params.input`,
|
|
75
77
|
message: "is not valid JSON"
|
|
76
|
-
}) :
|
|
78
|
+
}) : h(e, `${g}.params.input`, u);
|
|
77
79
|
}
|
|
78
80
|
if (_ !== "schedule") {
|
|
79
|
-
if (!
|
|
81
|
+
if (!p(v.access) || !s.includes(v.access)) u.push({
|
|
80
82
|
path: `${g}.params.access`,
|
|
81
83
|
message: "must say who may start a run this way",
|
|
82
|
-
hint: `one of ${
|
|
84
|
+
hint: `one of ${s.join(", ")}`
|
|
83
85
|
});
|
|
84
86
|
else if (v.access === "role") {
|
|
85
|
-
let e =
|
|
87
|
+
let e = r(v);
|
|
86
88
|
e?.mode === "role" && e.permissions.length === 0 && u.push({
|
|
87
89
|
path: `${g}.params.permissions`,
|
|
88
90
|
message: "role access must name at least one permission"
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
|
-
if (_ === "schedule" && (
|
|
94
|
+
if (_ === "schedule" && (p(v.cron) ? t(v.cron) || u.push({
|
|
93
95
|
path: `${g}.params.cron`,
|
|
94
96
|
message: `"${v.cron}" is not an expression this server can read, so it would never fire`,
|
|
95
97
|
hint: "five fields — minute hour day-of-month month day-of-week — with *, lists, ranges and steps"
|
|
96
98
|
}) : u.push({
|
|
97
99
|
path: `${g}.params.cron`,
|
|
98
100
|
message: "a scheduled trigger needs a cron expression"
|
|
99
|
-
}),
|
|
101
|
+
}), p(v.timezone) && !e(v.timezone) && u.push({
|
|
100
102
|
path: `${g}.params.timezone`,
|
|
101
103
|
message: `"${v.timezone}" is not a time zone this server knows, so the schedule would never fire`,
|
|
102
104
|
hint: "an IANA name like America/Santiago or Europe/Madrid — leave it empty for UTC"
|
|
103
|
-
})),
|
|
105
|
+
})), p(v.cacheSeconds)) {
|
|
104
106
|
let e = Number.parseFloat(v.cacheSeconds);
|
|
105
107
|
!Number.isFinite(e) || e < 0 ? u.push({
|
|
106
108
|
path: `${g}.params.cacheSeconds`,
|
|
107
109
|
message: "must be a number of seconds, or left empty"
|
|
108
|
-
}) : _ !== "render" &&
|
|
110
|
+
}) : _ !== "render" && d.push({
|
|
109
111
|
path: `${g}.params.cacheSeconds`,
|
|
110
112
|
message: `a "${_}" trigger is never answered from a cache`,
|
|
111
113
|
hint: "reuse only applies to a render, where the same page is built for one visitor after another"
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
|
-
_ === "custom" && !
|
|
116
|
+
_ === "custom" && !p(v.name) && u.push({
|
|
115
117
|
path: `${g}.params.name`,
|
|
116
118
|
message: "a custom trigger needs a name to be mounted under"
|
|
117
|
-
}), _ === "webhook" && (
|
|
119
|
+
}), _ === "webhook" && (i(v) && u.push({
|
|
118
120
|
path: `${g}.params.signatureCredential`,
|
|
119
121
|
message: "this signature check is in a format nothing reads any more, so the webhook is unverified",
|
|
120
122
|
hint: "name the credential holding the signing secret in \"Signing secret\", and its header beside it"
|
|
121
|
-
}),
|
|
123
|
+
}), a(v) || d.push({
|
|
122
124
|
path: `${g}.params.signatureCredential`,
|
|
123
125
|
message: "this webhook accepts unsigned requests",
|
|
124
126
|
hint: "name the credential holding the signing secret, and only the sender you expect can start a run"
|
|
125
|
-
}),
|
|
127
|
+
}), p(v.signatureAlgorithm) && !l.includes(v.signatureAlgorithm) && d.push({
|
|
126
128
|
path: `${g}.params.signatureAlgorithm`,
|
|
127
129
|
message: `"${v.signatureAlgorithm}" is not an algorithm this server signs with, so sha256 is used`,
|
|
128
|
-
hint: `one of ${
|
|
129
|
-
}),
|
|
130
|
+
hint: `one of ${l.join(", ")}`
|
|
131
|
+
}), p(v.signatureToleranceSeconds) && !p(v.signatureTimestampHeader) && d.push({
|
|
130
132
|
path: `${g}.params.signatureToleranceSeconds`,
|
|
131
133
|
message: "a tolerance with no timestamp header expires nothing",
|
|
132
134
|
hint: "name the header the sender puts the signing time in, or drop the tolerance"
|
|
133
|
-
})), _ === "call" && v.access === "public" &&
|
|
135
|
+
})), _ === "call" && v.access === "public" && d.push({
|
|
134
136
|
path: `${g}.params.access`,
|
|
135
137
|
message: "anyone can run this action, including signed-out visitors",
|
|
136
138
|
hint: "use session access unless the page needs it before anyone signs in"
|
|
137
139
|
});
|
|
138
|
-
},
|
|
139
|
-
if (!
|
|
140
|
-
|
|
140
|
+
}, _ = (e, t, r, i) => {
|
|
141
|
+
if (!f(e)) {
|
|
142
|
+
r.push({
|
|
141
143
|
path: "nodes",
|
|
142
144
|
message: "must be an object of steps"
|
|
143
145
|
});
|
|
144
146
|
return;
|
|
145
147
|
}
|
|
146
|
-
let
|
|
147
|
-
|
|
148
|
+
let a = Object.entries(e), o = a.filter(([, e]) => f(e) && e.type === "trigger");
|
|
149
|
+
o.length === 0 && r.push({
|
|
148
150
|
path: "nodes",
|
|
149
151
|
message: "this action has no way in",
|
|
150
152
|
hint: "add a trigger step — call, webhook, schedule, render or custom"
|
|
151
153
|
});
|
|
152
|
-
let
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}),
|
|
156
|
-
e.length > 1 &&
|
|
154
|
+
let s = /* @__PURE__ */ new Map();
|
|
155
|
+
o.forEach(([e, t]) => {
|
|
156
|
+
p(t.action) && s.set(t.action, [...s.get(t.action) ?? [], e]);
|
|
157
|
+
}), s.forEach((e, t) => {
|
|
158
|
+
e.length > 1 && r.push({
|
|
157
159
|
path: `nodes.${e[1]}`,
|
|
158
160
|
message: `this action has ${e.length} "${t}" triggers`,
|
|
159
161
|
hint: "one step per way in — a second flow starts from a different kind of trigger"
|
|
160
162
|
});
|
|
161
|
-
}),
|
|
163
|
+
}), a.forEach(([t, n]) => {
|
|
162
164
|
let a = `nodes.${t}`;
|
|
163
|
-
if (!
|
|
164
|
-
|
|
165
|
+
if (!f(n)) {
|
|
166
|
+
r.push({
|
|
165
167
|
path: a,
|
|
166
168
|
message: "must be an object"
|
|
167
169
|
});
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
+
if (n.type === "trigger") {
|
|
173
|
+
g(t, n, r, i);
|
|
172
174
|
return;
|
|
173
175
|
}
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
+
if (n.type !== "task") {
|
|
177
|
+
r.push({
|
|
176
178
|
path: `${a}.type`,
|
|
177
|
-
message: `"${String(
|
|
179
|
+
message: `"${String(n.type)}" steps run in the browser and cannot run on the server`,
|
|
178
180
|
hint: "a server action is made of task steps"
|
|
179
181
|
});
|
|
180
182
|
return;
|
|
181
183
|
}
|
|
182
|
-
if (!
|
|
183
|
-
|
|
184
|
+
if (!p(n.action) || !d.test(n.action)) {
|
|
185
|
+
r.push({
|
|
184
186
|
path: `${a}.action`,
|
|
185
187
|
message: "must name a task as <namespace>.<action>",
|
|
186
188
|
hint: "for example flow.output or http.request"
|
|
187
189
|
});
|
|
188
190
|
return;
|
|
189
191
|
}
|
|
190
|
-
|
|
192
|
+
p(n.afterNode) && !Object.hasOwn(e, n.afterNode) && r.push({
|
|
191
193
|
path: `${a}.afterNode`,
|
|
192
|
-
message: `points at "${
|
|
194
|
+
message: `points at "${n.afterNode}", which is not a step here`
|
|
193
195
|
});
|
|
194
|
-
}),
|
|
195
|
-
|
|
196
|
+
}), a.forEach(([e]) => {
|
|
197
|
+
u.has(e) && r.push({
|
|
196
198
|
path: `nodes.${e}`,
|
|
197
199
|
message: `"${e}" is what the run itself publishes in the flow scope, so a step may not take the name`,
|
|
198
200
|
hint: `rename the step — every later step reads {{ ${e} }} expecting the run's own value`
|
|
199
201
|
});
|
|
200
202
|
});
|
|
201
|
-
let
|
|
202
|
-
|
|
203
|
-
let n = /* @__PURE__ */ new Set(), r = t;
|
|
204
|
-
for (;
|
|
205
|
-
if (n.has(
|
|
206
|
-
|
|
203
|
+
let c = /* @__PURE__ */ new Set(), l = /* @__PURE__ */ new Set(), m = [];
|
|
204
|
+
o.forEach(([t]) => {
|
|
205
|
+
let n = /* @__PURE__ */ new Set(), r = [], i = t;
|
|
206
|
+
for (; i;) {
|
|
207
|
+
if (n.has(i)) {
|
|
208
|
+
l.add(i);
|
|
207
209
|
break;
|
|
208
210
|
}
|
|
209
|
-
n.add(r),
|
|
210
|
-
let t = e[
|
|
211
|
-
|
|
211
|
+
n.add(i), r.push(i), c.add(i);
|
|
212
|
+
let t = e[i];
|
|
213
|
+
i = p(t?.afterNode) ? t.afterNode : "";
|
|
212
214
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
m.push(r);
|
|
216
|
+
}), l.forEach((e) => {
|
|
217
|
+
r.push({
|
|
215
218
|
path: `nodes.${e}`,
|
|
216
219
|
message: "this step is reached again by its own chain, so the flow never ends",
|
|
217
220
|
hint: "break the `afterNode` chain that comes back to it"
|
|
218
221
|
});
|
|
219
|
-
}),
|
|
220
|
-
|
|
222
|
+
}), a.forEach(([e]) => {
|
|
223
|
+
c.has(e) || i.push({
|
|
221
224
|
path: `nodes.${e}`,
|
|
222
225
|
message: "no trigger reaches this step, so it never runs",
|
|
223
226
|
hint: "chain it with `afterNode` from a trigger, or delete it"
|
|
224
227
|
});
|
|
225
228
|
});
|
|
226
|
-
let
|
|
227
|
-
for (let [t, n] of
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
let h = (t) => e[t]?.action, _ = a.filter(([, e]) => f(e) && e.action === "flow.output");
|
|
230
|
+
for (let [t, n] of _) {
|
|
231
|
+
let r = p(n.afterNode) && h(n.afterNode) === "flow.onFailure";
|
|
232
|
+
p(n.afterNode) && Object.hasOwn(e, n.afterNode) && !r && i.push({
|
|
233
|
+
path: `nodes.${t}.afterNode`,
|
|
234
|
+
message: "the output step is not the last one, so the steps after it run for nothing",
|
|
235
|
+
hint: "move flow.output to the end of the flow"
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
let v = /* @__PURE__ */ new Set(), y = (e, t) => {
|
|
239
|
+
v.has(`${t.path}|${t.message}`) || (v.add(`${t.path}|${t.message}`), e.push(t));
|
|
240
|
+
};
|
|
241
|
+
m.forEach((e) => {
|
|
242
|
+
let t = e.findIndex((e) => h(e) === n);
|
|
243
|
+
if (t === -1) return;
|
|
244
|
+
let a = e[t];
|
|
245
|
+
t === 1 && y(i, {
|
|
246
|
+
path: `nodes.${a}`,
|
|
247
|
+
message: "nothing runs before this step, so nothing can fail and the steps after it never run",
|
|
248
|
+
hint: "put On Failure after the steps whose work it undoes"
|
|
249
|
+
}), t === e.length - 1 && y(i, {
|
|
250
|
+
path: `nodes.${a}`,
|
|
251
|
+
message: "nothing follows this step, so a failed run undoes nothing",
|
|
252
|
+
hint: "add the steps that give back what the flow already did"
|
|
253
|
+
}), e.slice(t + 1).forEach((e) => {
|
|
254
|
+
h(e) === "flow.onFailure" && y(r, {
|
|
255
|
+
path: `nodes.${e}`,
|
|
256
|
+
message: "a flow has one place its undo begins, and this is a second one",
|
|
257
|
+
hint: "keep one On Failure step and put every undo step after it"
|
|
258
|
+
}), h(e) === "flow.output" && y(r, {
|
|
259
|
+
path: `nodes.${e}`,
|
|
260
|
+
message: "an output step after On Failure answers a run that has already failed",
|
|
261
|
+
hint: "move flow.output before On Failure"
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}), _.length === 0 && f(t) && Object.keys(t).length > 0 && i.push({
|
|
233
265
|
path: "nodes",
|
|
234
266
|
message: "this action is expected to answer values but no step names any",
|
|
235
267
|
hint: "add a flow.output step at the end naming what the caller should get"
|
|
236
268
|
});
|
|
237
|
-
},
|
|
269
|
+
}, v = /\{\{\s*credential\./, y = (e, t) => {
|
|
238
270
|
Object.entries(e).forEach(([e, n]) => {
|
|
239
|
-
if (!
|
|
240
|
-
let r =
|
|
271
|
+
if (!f(n) || !f(n.params)) return;
|
|
272
|
+
let r = p(n.params.credential) ? n.params.credential : "";
|
|
241
273
|
Object.entries(n.params).forEach(([n, i]) => {
|
|
242
|
-
typeof i == "string" &&
|
|
274
|
+
typeof i == "string" && v.test(i) && r === "" && t.push({
|
|
243
275
|
path: `nodes.${e}.params.${n}`,
|
|
244
276
|
message: "this step reads a credential it did not ask for, so the token resolves to nothing",
|
|
245
277
|
hint: "set the step’s `credential` to the identifier it should use"
|
|
246
278
|
});
|
|
247
279
|
});
|
|
248
280
|
});
|
|
249
|
-
},
|
|
281
|
+
}, b = (e) => {
|
|
250
282
|
let t = [], n = [];
|
|
251
|
-
return
|
|
283
|
+
return f(e) ? (p(e.name) || t.push({
|
|
252
284
|
path: "name",
|
|
253
285
|
message: "must have a name"
|
|
254
|
-
}),
|
|
286
|
+
}), h(e.output, "output", t), _(e.nodes, e.output, t, n), y(f(e.nodes) ? e.nodes : {}, n), {
|
|
255
287
|
valid: t.length === 0,
|
|
256
288
|
errors: t,
|
|
257
289
|
warnings: n
|
|
@@ -265,4 +297,4 @@ var a = [
|
|
|
265
297
|
};
|
|
266
298
|
};
|
|
267
299
|
//#endregion
|
|
268
|
-
export {
|
|
300
|
+
export { b as validateActionDocument };
|
|
@@ -93,19 +93,19 @@ export declare const spaceEventSchemas: {
|
|
|
93
93
|
selector: z.ZodString;
|
|
94
94
|
path: z.ZodOptional<z.ZodCustom<StyleCategory, StyleCategory>>;
|
|
95
95
|
type: z.ZodEnum<{
|
|
96
|
+
id: "id";
|
|
96
97
|
class: "class";
|
|
97
98
|
element: "element";
|
|
98
|
-
id: "id";
|
|
99
99
|
}>;
|
|
100
100
|
style: z.ZodOptional<z.ZodCustom<import('..').StyleAttributes, import('..').StyleAttributes>>;
|
|
101
101
|
params: z.ZodObject<{
|
|
102
102
|
componentType: z.ZodOptional<z.ZodString>;
|
|
103
103
|
styleSelector: z.ZodOptional<z.ZodString>;
|
|
104
104
|
styleState: z.ZodOptional<z.ZodEnum<{
|
|
105
|
-
disabled: "disabled";
|
|
106
105
|
hover: "hover";
|
|
107
106
|
active: "active";
|
|
108
107
|
focus: "focus";
|
|
108
|
+
disabled: "disabled";
|
|
109
109
|
checked: "checked";
|
|
110
110
|
visited: "visited";
|
|
111
111
|
}>>;
|
|
@@ -124,10 +124,10 @@ export declare const spaceEventSchemas: {
|
|
|
124
124
|
params: z.ZodObject<{
|
|
125
125
|
componentType: z.ZodOptional<z.ZodString>;
|
|
126
126
|
styleState: z.ZodOptional<z.ZodEnum<{
|
|
127
|
-
disabled: "disabled";
|
|
128
127
|
hover: "hover";
|
|
129
128
|
active: "active";
|
|
130
129
|
focus: "focus";
|
|
130
|
+
disabled: "disabled";
|
|
131
131
|
checked: "checked";
|
|
132
132
|
visited: "visited";
|
|
133
133
|
}>>;
|
|
@@ -281,9 +281,9 @@ export declare const spaceEventSchemas: {
|
|
|
281
281
|
}>;
|
|
282
282
|
selector: z.ZodString;
|
|
283
283
|
type: z.ZodEnum<{
|
|
284
|
+
id: "id";
|
|
284
285
|
class: "class";
|
|
285
286
|
element: "element";
|
|
286
|
-
id: "id";
|
|
287
287
|
}>;
|
|
288
288
|
path: z.ZodOptional<z.ZodCustom<StyleCategory, StyleCategory>>;
|
|
289
289
|
style: z.ZodOptional<z.ZodCustom<import('..').StyleAttributes, import('..').StyleAttributes>>;
|
|
@@ -291,10 +291,10 @@ export declare const spaceEventSchemas: {
|
|
|
291
291
|
componentType: z.ZodOptional<z.ZodString>;
|
|
292
292
|
styleSelector: z.ZodOptional<z.ZodString>;
|
|
293
293
|
styleState: z.ZodOptional<z.ZodEnum<{
|
|
294
|
-
disabled: "disabled";
|
|
295
294
|
hover: "hover";
|
|
296
295
|
active: "active";
|
|
297
296
|
focus: "focus";
|
|
297
|
+
disabled: "disabled";
|
|
298
298
|
checked: "checked";
|
|
299
299
|
visited: "visited";
|
|
300
300
|
}>>;
|
|
@@ -314,10 +314,10 @@ export declare const spaceEventSchemas: {
|
|
|
314
314
|
params: z.ZodObject<{
|
|
315
315
|
componentType: z.ZodOptional<z.ZodString>;
|
|
316
316
|
styleState: z.ZodOptional<z.ZodEnum<{
|
|
317
|
-
disabled: "disabled";
|
|
318
317
|
hover: "hover";
|
|
319
318
|
active: "active";
|
|
320
319
|
focus: "focus";
|
|
320
|
+
disabled: "disabled";
|
|
321
321
|
checked: "checked";
|
|
322
322
|
visited: "visited";
|
|
323
323
|
}>>;
|