@swell/cli 2.9.2 → 2.9.3
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/dist/commands/app/dev.js +48 -0
- package/dist/commands/create/function.d.ts +3 -0
- package/dist/commands/create/function.js +80 -27
- package/dist/commands/inspect/functions.d.ts +4 -1
- package/dist/commands/inspect/functions.js +11 -0
- package/dist/commands/inspect/index.js +1 -0
- package/dist/commands/inspect/workflow-runs.d.ts +34 -0
- package/dist/commands/inspect/workflow-runs.js +319 -0
- package/dist/commands/inspect/workflows.d.ts +33 -0
- package/dist/commands/inspect/workflows.js +99 -0
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.js +20 -2
- package/dist/commands/schema.js +21 -2
- package/dist/create-app-command.js +1 -1
- package/dist/inspect-resource-command.d.ts +4 -1
- package/dist/inspect-resource-command.js +15 -7
- package/dist/lib/api.js +12 -0
- package/dist/lib/apps/app-config.js +25 -2
- package/dist/lib/bundle.d.ts +2 -0
- package/dist/lib/bundle.js +312 -0
- package/dist/lib/create/function.d.ts +2 -2
- package/dist/lib/create/function.js +45 -1
- package/dist/lib/function-source-analysis.d.ts +16 -0
- package/dist/lib/function-source-analysis.js +311 -0
- package/dist/lib/logs/index.d.ts +2 -0
- package/dist/lib/logs/index.js +27 -2
- package/dist/lib/swell-function-wrapper.d.ts +10 -0
- package/dist/lib/swell-function-wrapper.js +99 -0
- package/dist/lib/workflows/operations.d.ts +68 -0
- package/dist/lib/workflows/operations.js +162 -0
- package/oclif.manifest.json +186 -8
- package/package.json +3 -3
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const ts = require('typescript');
|
|
5
|
+
const ORDINARY_TRIGGER_KEYS = ['route', 'model', 'cron'];
|
|
6
|
+
const WORKFLOW_FORBIDDEN_KEYS = [
|
|
7
|
+
'route',
|
|
8
|
+
'model',
|
|
9
|
+
'cron',
|
|
10
|
+
'extension',
|
|
11
|
+
'timeout',
|
|
12
|
+
];
|
|
13
|
+
export async function analyzeFunctionSource(filePath) {
|
|
14
|
+
const sourceText = await fs.promises.readFile(filePath, 'utf8');
|
|
15
|
+
return analyzeFunctionSourceText(sourceText, filePath);
|
|
16
|
+
}
|
|
17
|
+
export function analyzeFunctionSourceText(sourceText, filePath = 'function.ts') {
|
|
18
|
+
const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') || filePath.endsWith('.ts')
|
|
19
|
+
? ts.ScriptKind.TS
|
|
20
|
+
: ts.ScriptKind.JS);
|
|
21
|
+
const diagnostics = [];
|
|
22
|
+
const parseDiagnostics = sourceFile
|
|
23
|
+
.parseDiagnostics ?? [];
|
|
24
|
+
diagnostics.push(...parseDiagnostics.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')));
|
|
25
|
+
const configExport = findConfigExport(sourceFile);
|
|
26
|
+
let config;
|
|
27
|
+
let kind = 'unknown';
|
|
28
|
+
if (configExport?.initializer) {
|
|
29
|
+
const initializer = unwrapExpression(configExport.initializer);
|
|
30
|
+
const kindValue = readConfigKind(initializer);
|
|
31
|
+
if (kindValue === 'dynamic') {
|
|
32
|
+
diagnostics.push('config.kind must be a static string literal.');
|
|
33
|
+
}
|
|
34
|
+
const evalResult = evaluateStatic(initializer);
|
|
35
|
+
if (evalResult.errors.length > 0) {
|
|
36
|
+
if (kindValue === 'workflow') {
|
|
37
|
+
diagnostics.push(...evalResult.errors);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (isRecord(evalResult.value)) {
|
|
41
|
+
config = evalResult.value;
|
|
42
|
+
}
|
|
43
|
+
const configKind = config?.kind ?? kindValue;
|
|
44
|
+
if (configKind === undefined) {
|
|
45
|
+
kind = config ? 'function' : 'unknown';
|
|
46
|
+
}
|
|
47
|
+
else if (configKind === 'function' || configKind === 'workflow') {
|
|
48
|
+
kind = configKind;
|
|
49
|
+
}
|
|
50
|
+
else if (typeof configKind === 'string') {
|
|
51
|
+
diagnostics.push(`Unsupported config.kind "${configKind}".`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const analysis = {
|
|
55
|
+
config,
|
|
56
|
+
kind,
|
|
57
|
+
diagnostics,
|
|
58
|
+
};
|
|
59
|
+
if (kind === 'workflow') {
|
|
60
|
+
analysis.defaultExport = validateWorkflowDefaultExport(sourceFile);
|
|
61
|
+
analysis.diagnostics.push(...validateWorkflowConfig(config));
|
|
62
|
+
if (!analysis.defaultExport.validWorkflowClass) {
|
|
63
|
+
analysis.diagnostics.push(analysis.defaultExport.reason ||
|
|
64
|
+
'Workflow default export must be a same-file class with a run method.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return analysis;
|
|
68
|
+
}
|
|
69
|
+
export function getFunctionTriggers(config) {
|
|
70
|
+
return ORDINARY_TRIGGER_KEYS.filter((key) => Boolean(config[key]));
|
|
71
|
+
}
|
|
72
|
+
export function hasKindDiagnostic(analysis) {
|
|
73
|
+
return analysis.diagnostics.some((diagnostic) => diagnostic === 'config.kind must be a static string literal.' ||
|
|
74
|
+
diagnostic.startsWith('Unsupported config.kind '));
|
|
75
|
+
}
|
|
76
|
+
export function workflowStaticConfigError() {
|
|
77
|
+
return new Error('Workflow configs must export a static object literal so the CLI can choose the workflow bundling path before compiling.');
|
|
78
|
+
}
|
|
79
|
+
function findConfigExport(sourceFile) {
|
|
80
|
+
const localVariables = new Map();
|
|
81
|
+
let exportedConfigLocalName;
|
|
82
|
+
let directExport;
|
|
83
|
+
for (const statement of sourceFile.statements) {
|
|
84
|
+
if (ts.isVariableStatement(statement)) {
|
|
85
|
+
const isExported = hasModifier(statement, ts.SyntaxKind.ExportKeyword);
|
|
86
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
87
|
+
if (!ts.isIdentifier(declaration.name)) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
localVariables.set(declaration.name.text, declaration.initializer);
|
|
91
|
+
if (isExported && declaration.name.text === 'config') {
|
|
92
|
+
directExport = { initializer: declaration.initializer };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (ts.isExportDeclaration(statement) &&
|
|
97
|
+
statement.exportClause &&
|
|
98
|
+
ts.isNamedExports(statement.exportClause)) {
|
|
99
|
+
for (const element of statement.exportClause.elements) {
|
|
100
|
+
const exportedName = element.name.text;
|
|
101
|
+
if (exportedName === 'config') {
|
|
102
|
+
exportedConfigLocalName = (element.propertyName || element.name).text;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (directExport) {
|
|
108
|
+
return directExport;
|
|
109
|
+
}
|
|
110
|
+
if (exportedConfigLocalName) {
|
|
111
|
+
return { initializer: localVariables.get(exportedConfigLocalName) };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function readConfigKind(expression) {
|
|
115
|
+
const unwrapped = unwrapExpression(expression);
|
|
116
|
+
if (!ts.isObjectLiteralExpression(unwrapped)) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
for (const property of unwrapped.properties) {
|
|
120
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const name = staticPropertyName(property.name);
|
|
124
|
+
if (name !== 'kind') {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const value = unwrapExpression(property.initializer);
|
|
128
|
+
return ts.isStringLiteral(value) ? value.text : 'dynamic';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function evaluateStatic(expression) {
|
|
132
|
+
const unwrapped = unwrapExpression(expression);
|
|
133
|
+
if (ts.isObjectLiteralExpression(unwrapped)) {
|
|
134
|
+
const value = {};
|
|
135
|
+
const errors = [];
|
|
136
|
+
for (const property of unwrapped.properties) {
|
|
137
|
+
if (ts.isSpreadAssignment(property)) {
|
|
138
|
+
errors.push('config must not use object spreads.');
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
142
|
+
errors.push('config must contain only static property assignments.');
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const name = staticPropertyName(property.name);
|
|
146
|
+
if (!name) {
|
|
147
|
+
errors.push('config must not use computed property keys.');
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const result = evaluateStatic(property.initializer);
|
|
151
|
+
if (result.errors.length > 0) {
|
|
152
|
+
errors.push(...result.errors.map((error) => `${name}: ${error}`));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
value[name] = result.value;
|
|
156
|
+
}
|
|
157
|
+
return { errors, value };
|
|
158
|
+
}
|
|
159
|
+
if (ts.isArrayLiteralExpression(unwrapped)) {
|
|
160
|
+
const value = [];
|
|
161
|
+
const errors = [];
|
|
162
|
+
for (const element of unwrapped.elements) {
|
|
163
|
+
if (ts.isSpreadElement(element)) {
|
|
164
|
+
errors.push('arrays must not use spreads.');
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const result = evaluateStatic(element);
|
|
168
|
+
if (result.errors.length > 0) {
|
|
169
|
+
errors.push(...result.errors);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
value.push(result.value);
|
|
173
|
+
}
|
|
174
|
+
return { errors, value };
|
|
175
|
+
}
|
|
176
|
+
if (ts.isStringLiteral(unwrapped) ||
|
|
177
|
+
ts.isNoSubstitutionTemplateLiteral(unwrapped)) {
|
|
178
|
+
return { errors: [], value: unwrapped.text };
|
|
179
|
+
}
|
|
180
|
+
if (ts.isNumericLiteral(unwrapped)) {
|
|
181
|
+
return { errors: [], value: Number(unwrapped.text) };
|
|
182
|
+
}
|
|
183
|
+
if (unwrapped.kind === ts.SyntaxKind.TrueKeyword) {
|
|
184
|
+
return { errors: [], value: true };
|
|
185
|
+
}
|
|
186
|
+
if (unwrapped.kind === ts.SyntaxKind.FalseKeyword) {
|
|
187
|
+
return { errors: [], value: false };
|
|
188
|
+
}
|
|
189
|
+
if (unwrapped.kind === ts.SyntaxKind.NullKeyword) {
|
|
190
|
+
return { errors: [], value: null };
|
|
191
|
+
}
|
|
192
|
+
if (ts.isPrefixUnaryExpression(unwrapped) &&
|
|
193
|
+
ts.isNumericLiteral(unwrapped.operand) &&
|
|
194
|
+
(unwrapped.operator === ts.SyntaxKind.MinusToken ||
|
|
195
|
+
unwrapped.operator === ts.SyntaxKind.PlusToken)) {
|
|
196
|
+
const value = Number(unwrapped.operand.text);
|
|
197
|
+
return {
|
|
198
|
+
errors: [],
|
|
199
|
+
value: unwrapped.operator === ts.SyntaxKind.MinusToken ? -value : value,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { errors: ['value must be static JSON-like metadata.'] };
|
|
203
|
+
}
|
|
204
|
+
function validateWorkflowConfig(config) {
|
|
205
|
+
if (!config) {
|
|
206
|
+
return ['Workflow config must be a static object literal.'];
|
|
207
|
+
}
|
|
208
|
+
const diagnostics = [];
|
|
209
|
+
for (const key of WORKFLOW_FORBIDDEN_KEYS) {
|
|
210
|
+
if (key in config) {
|
|
211
|
+
diagnostics.push(`Workflow config must not specify ${key}.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return diagnostics;
|
|
215
|
+
}
|
|
216
|
+
function validateWorkflowDefaultExport(sourceFile) {
|
|
217
|
+
const classes = new Map();
|
|
218
|
+
const defaultExports = [];
|
|
219
|
+
for (const statement of sourceFile.statements) {
|
|
220
|
+
if (ts.isClassDeclaration(statement) && statement.name) {
|
|
221
|
+
classes.set(statement.name.text, statement);
|
|
222
|
+
}
|
|
223
|
+
if (ts.isClassDeclaration(statement) &&
|
|
224
|
+
hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
225
|
+
defaultExports.push(statement);
|
|
226
|
+
}
|
|
227
|
+
else if (ts.isFunctionDeclaration(statement) &&
|
|
228
|
+
hasModifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
229
|
+
defaultExports.push(statement);
|
|
230
|
+
}
|
|
231
|
+
else if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
|
|
232
|
+
defaultExports.push(statement);
|
|
233
|
+
}
|
|
234
|
+
else if (ts.isExportDeclaration(statement) &&
|
|
235
|
+
statement.exportClause &&
|
|
236
|
+
ts.isNamedExports(statement.exportClause)) {
|
|
237
|
+
for (const element of statement.exportClause.elements) {
|
|
238
|
+
if (element.name.text === 'default') {
|
|
239
|
+
defaultExports.push(statement);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (defaultExports.length !== 1) {
|
|
245
|
+
return {
|
|
246
|
+
validWorkflowClass: false,
|
|
247
|
+
reason: 'Workflow files must have exactly one default export.',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const defaultExport = defaultExports[0];
|
|
251
|
+
let workflowClass;
|
|
252
|
+
if (ts.isClassDeclaration(defaultExport)) {
|
|
253
|
+
workflowClass = defaultExport;
|
|
254
|
+
}
|
|
255
|
+
else if (ts.isExportAssignment(defaultExport)) {
|
|
256
|
+
const expression = unwrapExpression(defaultExport.expression);
|
|
257
|
+
if (ts.isIdentifier(expression)) {
|
|
258
|
+
workflowClass = classes.get(expression.text);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (!workflowClass) {
|
|
262
|
+
return {
|
|
263
|
+
validWorkflowClass: false,
|
|
264
|
+
reason: 'Workflow default export must resolve to a class declared in the same file.',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (!hasInstanceRunMethod(workflowClass)) {
|
|
268
|
+
return {
|
|
269
|
+
validWorkflowClass: false,
|
|
270
|
+
name: workflowClass.name?.text,
|
|
271
|
+
reason: 'Workflow default export class must define an instance run method.',
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
validWorkflowClass: true,
|
|
276
|
+
name: workflowClass.name?.text,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function hasInstanceRunMethod(classDeclaration) {
|
|
280
|
+
return classDeclaration.members.some((member) => {
|
|
281
|
+
if (!ts.isMethodDeclaration(member) ||
|
|
282
|
+
hasModifier(member, ts.SyntaxKind.StaticKeyword)) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
return staticPropertyName(member.name) === 'run';
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function unwrapExpression(expression) {
|
|
289
|
+
let current = expression;
|
|
290
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
291
|
+
ts.isAsExpression(current) ||
|
|
292
|
+
ts.isTypeAssertionExpression(current) ||
|
|
293
|
+
ts.isSatisfiesExpression(current)) {
|
|
294
|
+
current = current.expression;
|
|
295
|
+
}
|
|
296
|
+
return current;
|
|
297
|
+
}
|
|
298
|
+
function staticPropertyName(name) {
|
|
299
|
+
if (ts.isIdentifier(name) ||
|
|
300
|
+
ts.isStringLiteral(name) ||
|
|
301
|
+
ts.isNumericLiteral(name)) {
|
|
302
|
+
return name.text;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function hasModifier(node, kind) {
|
|
306
|
+
return Boolean(ts.canHaveModifiers(node) &&
|
|
307
|
+
ts.getModifiers(node)?.some((modifier) => modifier.kind === kind));
|
|
308
|
+
}
|
|
309
|
+
function isRecord(value) {
|
|
310
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
311
|
+
}
|
package/dist/lib/logs/index.d.ts
CHANGED
package/dist/lib/logs/index.js
CHANGED
|
@@ -42,6 +42,10 @@ export class LoggedItem {
|
|
|
42
42
|
request = this.prepareRequestFunction(logMessage);
|
|
43
43
|
break;
|
|
44
44
|
}
|
|
45
|
+
case 'workflow': {
|
|
46
|
+
request = this.prepareRequestWorkflow(logMessage);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
45
49
|
case 'webhook': {
|
|
46
50
|
const { event } = logMessage;
|
|
47
51
|
request = `Webhook ${event.type} > ${style.logMethod('POST')} ${url}`;
|
|
@@ -61,10 +65,32 @@ export class LoggedItem {
|
|
|
61
65
|
}
|
|
62
66
|
return request;
|
|
63
67
|
}
|
|
68
|
+
prepareRequestWorkflow(logMessage) {
|
|
69
|
+
const { error, logs, phase, status, step_name: stepName, workflow_instance_id: instanceId, workflow_name: workflowName, } = logMessage;
|
|
70
|
+
const parts = ['Workflow', workflowName || '-', instanceId || '-'];
|
|
71
|
+
if (phase) {
|
|
72
|
+
parts.push(phase);
|
|
73
|
+
}
|
|
74
|
+
if (stepName) {
|
|
75
|
+
parts.push(`"${stepName}"`);
|
|
76
|
+
}
|
|
77
|
+
if (status) {
|
|
78
|
+
parts.push(String(status));
|
|
79
|
+
}
|
|
80
|
+
if (error) {
|
|
81
|
+
parts.push(style.funcError(String(error)));
|
|
82
|
+
}
|
|
83
|
+
const logLines = this.prepareConsoleLines(logs);
|
|
84
|
+
return `${parts.join(' ')}${logLines ? `\n${logLines}` : ''}`;
|
|
85
|
+
}
|
|
64
86
|
prepareRequestFunction(logMessage) {
|
|
65
87
|
const { event } = logMessage;
|
|
66
88
|
const { logs, method, name } = logMessage;
|
|
67
|
-
const logLines = logs
|
|
89
|
+
const logLines = this.prepareConsoleLines(logs);
|
|
90
|
+
return `Function ${event ? `${event.hook ? `${event.hook}:` : ''}${event.type} >` : ''} ${style.logMethod(method.toUpperCase())} /${name} ${logLines ? `\n${logLines}` : ''}`;
|
|
91
|
+
}
|
|
92
|
+
prepareConsoleLines(logs) {
|
|
93
|
+
return logs
|
|
68
94
|
?.map((l) => {
|
|
69
95
|
const line = l.line?.join('\n');
|
|
70
96
|
switch (l.level) {
|
|
@@ -83,6 +109,5 @@ export class LoggedItem {
|
|
|
83
109
|
}
|
|
84
110
|
})
|
|
85
111
|
.join('\n');
|
|
86
|
-
return `Function ${event ? `${event.hook ? `${event.hook}:` : ''}${event.type} >` : ''} ${style.logMethod(method.toUpperCase())} /${name} ${logLines ? `\n${logLines}` : ''}`;
|
|
87
112
|
}
|
|
88
113
|
}
|
|
@@ -48,6 +48,11 @@ declare function executeModuleHandler(req: SwellRequest, context: Event): Promis
|
|
|
48
48
|
* @returns {boolean}
|
|
49
49
|
*/
|
|
50
50
|
declare function isOrdinaryObject(val: any): boolean;
|
|
51
|
+
declare function validateWorkflowParams(params: any): any;
|
|
52
|
+
declare function validateWorkflowParamValue(value: any, seen?: WeakSet<object>): void;
|
|
53
|
+
declare function validateWorkflowParamArray(value: any, seen: any): void;
|
|
54
|
+
declare function validateWorkflowParamObject(value: any, seen: any): void;
|
|
55
|
+
declare function createWorkflowParamsError(code: any, message: any): SwellError;
|
|
51
56
|
declare namespace originalConsole {
|
|
52
57
|
let log: {
|
|
53
58
|
(...data: any[]): void;
|
|
@@ -70,6 +75,7 @@ declare namespace originalConsole {
|
|
|
70
75
|
(message?: any, ...optionalParams: any[]): void;
|
|
71
76
|
};
|
|
72
77
|
}
|
|
78
|
+
declare const WORKFLOW_PARAMS_MAX_BYTES: number;
|
|
73
79
|
/**
|
|
74
80
|
* Class representing a Swell request.
|
|
75
81
|
*/
|
|
@@ -121,6 +127,9 @@ declare class SwellAPI {
|
|
|
121
127
|
baseUrl: any;
|
|
122
128
|
basicAuth: string;
|
|
123
129
|
context: any;
|
|
130
|
+
workflows: {
|
|
131
|
+
create: (name: any, params: any) => Promise<any>;
|
|
132
|
+
};
|
|
124
133
|
toBase64(inputString: any): string;
|
|
125
134
|
stringifyQuery(queryObject: any, prefix: any): any;
|
|
126
135
|
makeRequest(method: any, url: any, data: any): Promise<any>;
|
|
@@ -129,6 +138,7 @@ declare class SwellAPI {
|
|
|
129
138
|
post(url: any, data: any): Promise<any>;
|
|
130
139
|
delete(url: any, data: any): Promise<any>;
|
|
131
140
|
settings(id?: any): Promise<any>;
|
|
141
|
+
createWorkflow(name: any, params: any): Promise<any>;
|
|
132
142
|
/**
|
|
133
143
|
* Atomic multi-op write. Throws SwellError with a stable `error.code`
|
|
134
144
|
* (transaction_conflict | transaction_timeout | transaction_throttled
|
|
@@ -6,6 +6,7 @@ const originalConsole = {
|
|
|
6
6
|
warn: console.warn,
|
|
7
7
|
error: console.error,
|
|
8
8
|
};
|
|
9
|
+
const WORKFLOW_PARAMS_MAX_BYTES = 128 * 1024;
|
|
9
10
|
addEventListener('fetch', (event) => {
|
|
10
11
|
event.respondWith(request(event.request, event.env, event));
|
|
11
12
|
});
|
|
@@ -240,6 +241,9 @@ class SwellAPI {
|
|
|
240
241
|
this.baseUrl = req.apiHost;
|
|
241
242
|
this.basicAuth = `${req.storeId}:${req.accessToken}`;
|
|
242
243
|
this.context = context;
|
|
244
|
+
this.workflows = {
|
|
245
|
+
create: (name, params) => this.createWorkflow(name, params),
|
|
246
|
+
};
|
|
243
247
|
}
|
|
244
248
|
toBase64(inputString) {
|
|
245
249
|
const utf8Bytes = new TextEncoder().encode(inputString);
|
|
@@ -324,6 +328,15 @@ class SwellAPI {
|
|
|
324
328
|
async settings(id = this.request.appId) {
|
|
325
329
|
return this.makeRequest('GET', `/settings/${id}`);
|
|
326
330
|
}
|
|
331
|
+
async createWorkflow(name, params) {
|
|
332
|
+
const data = {
|
|
333
|
+
workflow_name: name,
|
|
334
|
+
};
|
|
335
|
+
if (params !== undefined) {
|
|
336
|
+
data.params = validateWorkflowParams(params);
|
|
337
|
+
}
|
|
338
|
+
return this.makeRequest('POST', '/:workflows/instances', data);
|
|
339
|
+
}
|
|
327
340
|
/**
|
|
328
341
|
* Atomic multi-op write. Throws SwellError with a stable `error.code`
|
|
329
342
|
* (transaction_conflict | transaction_timeout | transaction_throttled
|
|
@@ -499,3 +512,89 @@ function isOrdinaryObject(val) {
|
|
|
499
512
|
val !== null &&
|
|
500
513
|
Object.getPrototypeOf(val) === Object.prototype);
|
|
501
514
|
}
|
|
515
|
+
function validateWorkflowParams(params) {
|
|
516
|
+
validateWorkflowParamValue(params);
|
|
517
|
+
const serialized = JSON.stringify(params);
|
|
518
|
+
const size = new TextEncoder().encode(serialized).length;
|
|
519
|
+
if (size > WORKFLOW_PARAMS_MAX_BYTES) {
|
|
520
|
+
throw createWorkflowParamsError('workflow_params_too_large', 'Workflow params are too large; pass identifiers and re-fetch data inside the workflow');
|
|
521
|
+
}
|
|
522
|
+
return params;
|
|
523
|
+
}
|
|
524
|
+
function validateWorkflowParamValue(value, seen = new WeakSet()) {
|
|
525
|
+
if (value === null) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
switch (typeof value) {
|
|
529
|
+
case 'string':
|
|
530
|
+
case 'boolean':
|
|
531
|
+
return;
|
|
532
|
+
case 'number':
|
|
533
|
+
if (Number.isFinite(value)) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
case 'object': {
|
|
538
|
+
if (Array.isArray(value)) {
|
|
539
|
+
validateWorkflowParamArray(value, seen);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const prototype = Object.getPrototypeOf(value);
|
|
543
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
validateWorkflowParamObject(value, seen);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
default:
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
553
|
+
}
|
|
554
|
+
function validateWorkflowParamArray(value, seen) {
|
|
555
|
+
if (seen.has(value)) {
|
|
556
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
557
|
+
}
|
|
558
|
+
seen.add(value);
|
|
559
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
560
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
561
|
+
}
|
|
562
|
+
for (const key of Object.keys(value)) {
|
|
563
|
+
if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
|
|
564
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
568
|
+
if (!Object.prototype.hasOwnProperty.call(value, i)) {
|
|
569
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
570
|
+
}
|
|
571
|
+
validateWorkflowParamValue(value[i], seen);
|
|
572
|
+
}
|
|
573
|
+
seen.delete(value);
|
|
574
|
+
}
|
|
575
|
+
function validateWorkflowParamObject(value, seen) {
|
|
576
|
+
if (seen.has(value)) {
|
|
577
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
578
|
+
}
|
|
579
|
+
seen.add(value);
|
|
580
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
581
|
+
throw createWorkflowParamsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
|
|
582
|
+
}
|
|
583
|
+
for (const item of Object.values(value)) {
|
|
584
|
+
validateWorkflowParamValue(item, seen);
|
|
585
|
+
}
|
|
586
|
+
seen.delete(value);
|
|
587
|
+
}
|
|
588
|
+
function createWorkflowParamsError(code, message) {
|
|
589
|
+
return new SwellError({
|
|
590
|
+
error: {
|
|
591
|
+
code,
|
|
592
|
+
message,
|
|
593
|
+
status: 400,
|
|
594
|
+
retryable: false,
|
|
595
|
+
},
|
|
596
|
+
}, {
|
|
597
|
+
code,
|
|
598
|
+
status: 400,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Api from '../api.js';
|
|
2
|
+
import { InspectScope } from '../apps/inspect-scope.js';
|
|
3
|
+
export declare const WORKFLOW_STATUSES: string[];
|
|
4
|
+
export declare const WORKFLOW_INSTANCE_ID_PATTERN: RegExp;
|
|
5
|
+
export interface WorkflowManifestRecord {
|
|
6
|
+
app_id?: string;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
id?: string;
|
|
9
|
+
kind?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
summary?: WorkflowSummary;
|
|
12
|
+
trigger?: string;
|
|
13
|
+
workflow_name?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WorkflowSummary {
|
|
16
|
+
active_instances?: number;
|
|
17
|
+
completed_recent?: number;
|
|
18
|
+
failed_instances?: number;
|
|
19
|
+
last_failure?: WorkflowFailure;
|
|
20
|
+
last_create_failure?: WorkflowFailure;
|
|
21
|
+
last_runtime_failure?: WorkflowFailure;
|
|
22
|
+
terminated_recent?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkflowFailure {
|
|
25
|
+
code?: string;
|
|
26
|
+
date?: string;
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WorkflowRunRecord {
|
|
30
|
+
app_id?: string;
|
|
31
|
+
cf_instance_id?: string;
|
|
32
|
+
date_completed?: string;
|
|
33
|
+
date_created?: string;
|
|
34
|
+
date_failed?: string;
|
|
35
|
+
date_started?: string;
|
|
36
|
+
date_terminated?: string;
|
|
37
|
+
error?: {
|
|
38
|
+
code?: string;
|
|
39
|
+
message?: string;
|
|
40
|
+
};
|
|
41
|
+
failure_phase?: string;
|
|
42
|
+
status?: string;
|
|
43
|
+
trigger?: string;
|
|
44
|
+
workflow_id?: string;
|
|
45
|
+
workflow_instance_id?: string;
|
|
46
|
+
workflow_name?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface WorkflowOperationRef {
|
|
49
|
+
appId: string;
|
|
50
|
+
appSlug?: string;
|
|
51
|
+
query: {
|
|
52
|
+
workflow_id?: string;
|
|
53
|
+
workflow_name?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export declare function resolveWorkflowOperationRef(api: Api, workflow: string, flags: {
|
|
57
|
+
app?: string;
|
|
58
|
+
}): Promise<WorkflowOperationRef>;
|
|
59
|
+
export declare function resolveWorkflowScope(api: Api, flags: {
|
|
60
|
+
app?: string;
|
|
61
|
+
}): Promise<InspectScope>;
|
|
62
|
+
export declare function isWorkflowInstanceIdentifier(value: string): boolean;
|
|
63
|
+
export declare function workflowDisplayName(record: WorkflowManifestRecord): string;
|
|
64
|
+
export declare function workflowListMeta(record: WorkflowManifestRecord): string;
|
|
65
|
+
export declare function workflowRunDate(run: WorkflowRunRecord): string | undefined;
|
|
66
|
+
export declare function workflowRunMeta(run: WorkflowRunRecord): string;
|
|
67
|
+
export declare function formatWorkflowRelativeTime(value: string, now?: number): string;
|
|
68
|
+
export declare function redactWorkflowPayload(value: unknown): unknown;
|