@teamkeel/testing-runtime 0.415.3 → 0.416.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/package.json +1 -1
- package/src/Executor.mjs +1 -116
- package/src/FlowExecutor.mjs +173 -0
- package/src/index.d.ts +192 -0
- package/src/index.mjs +1 -0
- package/src/parsing.mjs +116 -0
package/package.json
CHANGED
package/src/Executor.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
|
-
import {
|
|
2
|
+
import { parseInputs, parseOutputs, reviver } from "./parsing.mjs";
|
|
3
3
|
|
|
4
4
|
export class Executor {
|
|
5
5
|
constructor(props) {
|
|
@@ -123,118 +123,3 @@ export class Executor {
|
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
|
|
127
|
-
async function parseInputs(inputs) {
|
|
128
|
-
if (inputs != null && typeof inputs === "object") {
|
|
129
|
-
for (const keys of Object.keys(inputs)) {
|
|
130
|
-
if (inputs[keys] !== null && typeof inputs[keys] === "object") {
|
|
131
|
-
if (isDuration(inputs[keys])) {
|
|
132
|
-
inputs[keys] = inputs[keys].toISOString();
|
|
133
|
-
} else if (isInlineFileOrFile(inputs[keys])) {
|
|
134
|
-
const contents = await inputs[keys].read();
|
|
135
|
-
inputs[keys] = `data:${inputs[keys].contentType};name=${
|
|
136
|
-
inputs[keys].filename
|
|
137
|
-
};base64,${contents.toString("base64")}`;
|
|
138
|
-
} else {
|
|
139
|
-
inputs[keys] = await parseInputs(inputs[keys]);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return inputs;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function isInlineFileOrFile(obj) {
|
|
148
|
-
return (
|
|
149
|
-
obj &&
|
|
150
|
-
typeof obj === "object" &&
|
|
151
|
-
(obj.constructor.name === "InlineFile" || obj.constructor.name === "File")
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function isDuration(obj) {
|
|
156
|
-
return obj && typeof obj === "object" && obj.constructor.name === "Duration";
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function parseOutputs(data) {
|
|
160
|
-
if (!data) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (!isPlainObject(data)) {
|
|
165
|
-
return data;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const keys = data ? Object.keys(data) : [];
|
|
169
|
-
const row = {};
|
|
170
|
-
|
|
171
|
-
for (const key of keys) {
|
|
172
|
-
const value = data[key];
|
|
173
|
-
|
|
174
|
-
if (isPlainObject(value)) {
|
|
175
|
-
if (value.key && value.size && value.filename && value.contentType) {
|
|
176
|
-
row[key] = File.fromDbRecord(value);
|
|
177
|
-
} else {
|
|
178
|
-
row[key] = parseOutputs(value);
|
|
179
|
-
}
|
|
180
|
-
} else if (
|
|
181
|
-
Array.isArray(value) &&
|
|
182
|
-
value.every((item) => typeof item === "object" && item !== null)
|
|
183
|
-
) {
|
|
184
|
-
const arr = [];
|
|
185
|
-
for (let item of value) {
|
|
186
|
-
if (item.key && item.size && item.filename && item.contentType) {
|
|
187
|
-
arr.push(File.fromDbRecord(item));
|
|
188
|
-
} else {
|
|
189
|
-
arr.push(parseOutputs(item));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
row[key] = arr;
|
|
193
|
-
} else {
|
|
194
|
-
row[key] = value;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return row;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function isPlainObject(obj) {
|
|
201
|
-
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const dateFormat =
|
|
205
|
-
/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
206
|
-
|
|
207
|
-
function reviver(key, value) {
|
|
208
|
-
// Handle date strings
|
|
209
|
-
if (typeof value === "string") {
|
|
210
|
-
if (dateFormat.test(value)) {
|
|
211
|
-
return new Date(value);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Handle nested objects
|
|
216
|
-
if (value !== null && typeof value === "object") {
|
|
217
|
-
// Handle arrays
|
|
218
|
-
if (Array.isArray(value)) {
|
|
219
|
-
return value.map((item) => {
|
|
220
|
-
if (typeof item === "string") {
|
|
221
|
-
if (dateFormat.test(item)) {
|
|
222
|
-
return new Date(item);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return item;
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Handle plain objects
|
|
230
|
-
for (const k in value) {
|
|
231
|
-
if (typeof value[k] === "string") {
|
|
232
|
-
if (dateFormat.test(value[k])) {
|
|
233
|
-
value[k] = new Date(value[k]);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return value;
|
|
240
|
-
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { parseInputs, parseOutputs, reviver } from "./parsing.mjs";
|
|
2
|
+
|
|
3
|
+
export class FlowExecutor {
|
|
4
|
+
constructor(props) {
|
|
5
|
+
this._flowUrl = process.env.KEEL_TESTING_FLOWS_API_URL + "/" + props.name;
|
|
6
|
+
this._name = props.name;
|
|
7
|
+
this._identity = props.identity || null;
|
|
8
|
+
this._authToken = props.authToken || null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
withIdentity(i) {
|
|
12
|
+
return new FlowExecutor({
|
|
13
|
+
name: this._name,
|
|
14
|
+
identity: i,
|
|
15
|
+
apiBaseUrl: this._apiBaseUrl,
|
|
16
|
+
parseJsonResult: this._parseJsonResult,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
withAuthToken(t) {
|
|
20
|
+
return new FlowExecutor({
|
|
21
|
+
name: this._name,
|
|
22
|
+
authToken: t,
|
|
23
|
+
apiBaseUrl: this._apiBaseUrl,
|
|
24
|
+
parseJsonResult: this._parseJsonResult,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
headers() {
|
|
29
|
+
const headers = { "Content-Type": "application/json" };
|
|
30
|
+
|
|
31
|
+
// An Identity instance is provided make a JWT
|
|
32
|
+
if (this._identity !== null) {
|
|
33
|
+
const base64pk = process.env.KEEL_DEFAULT_PK;
|
|
34
|
+
let privateKey = undefined;
|
|
35
|
+
|
|
36
|
+
if (base64pk) {
|
|
37
|
+
privateKey = Buffer.from(base64pk, "base64").toString("utf8");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
headers["Authorization"] =
|
|
41
|
+
"Bearer " +
|
|
42
|
+
jwt.sign({}, privateKey, {
|
|
43
|
+
algorithm: privateKey ? "RS256" : "none",
|
|
44
|
+
expiresIn: 60 * 60 * 24,
|
|
45
|
+
subject: this._identity.id,
|
|
46
|
+
issuer: "https://keel.so",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If an auth token is provided that can be sent as-is
|
|
51
|
+
if (this._authToken !== null) {
|
|
52
|
+
headers["Authorization"] = "Bearer " + this._authToken;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return headers;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async start(inputs) {
|
|
59
|
+
return parseInputs(inputs).then((parsed) => {
|
|
60
|
+
// Use the HTTP JSON API as that returns more friendly errors than
|
|
61
|
+
// the JSON-RPC API.
|
|
62
|
+
return fetch(this._flowUrl, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: JSON.stringify(parsed),
|
|
65
|
+
headers: this.headers(),
|
|
66
|
+
}).then(handleResponse);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async get(id) {
|
|
71
|
+
return fetch(this._flowUrl + "/" + id, {
|
|
72
|
+
method: "GET",
|
|
73
|
+
headers: this.headers(),
|
|
74
|
+
}).then(handleResponse);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async cancel(id) {
|
|
78
|
+
return fetch(this._flowUrl + "/" + id + "/cancel", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: this.headers(),
|
|
81
|
+
}).then(handleResponse);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async putStepValues(id, stepId, values, action) {
|
|
85
|
+
let url = this._flowUrl + "/" + id + "/" + stepId;
|
|
86
|
+
|
|
87
|
+
if (action) {
|
|
88
|
+
// If an action is provided then we need to add it to the URL
|
|
89
|
+
const queryString = new URLSearchParams({ action }).toString();
|
|
90
|
+
url = `${url}?${queryString}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return await fetch(url, {
|
|
94
|
+
method: "PUT",
|
|
95
|
+
body: JSON.stringify(values),
|
|
96
|
+
headers: this.headers(),
|
|
97
|
+
}).then(handleResponse);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async untilFinished(id, timeout = 5000) {
|
|
101
|
+
const startTime = Date.now();
|
|
102
|
+
|
|
103
|
+
while (true) {
|
|
104
|
+
if (Date.now() - startTime > timeout) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`timed out waiting for flow run to reach a completed state after ${timeout}ms`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const flow = await this.get(id);
|
|
111
|
+
|
|
112
|
+
if (flow.status === "COMPLETED" || flow.status === "FAILED") {
|
|
113
|
+
return flow;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async untilAwaitingInput(id, timeout = 5000) {
|
|
121
|
+
const startTime = Date.now();
|
|
122
|
+
|
|
123
|
+
while (true) {
|
|
124
|
+
if (Date.now() - startTime > timeout) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`timed out waiting for flow run to reach a completed state after ${timeout}ms`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const flow = await this.get(id);
|
|
131
|
+
|
|
132
|
+
if (flow.status === "AWAITING_INPUT") {
|
|
133
|
+
return flow;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function handleResponse(r) {
|
|
142
|
+
if (r.status !== 200) {
|
|
143
|
+
// For non-200 first read the response as text
|
|
144
|
+
return r.text().then((t) => {
|
|
145
|
+
let d;
|
|
146
|
+
try {
|
|
147
|
+
d = JSON.parse(t);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
if ("DEBUG" in process.env) {
|
|
150
|
+
console.log(e);
|
|
151
|
+
}
|
|
152
|
+
// If JSON parsing fails then throw an error with the
|
|
153
|
+
// response text as the message
|
|
154
|
+
throw new Error(t);
|
|
155
|
+
}
|
|
156
|
+
// Otherwise throw the parsed JSON error response
|
|
157
|
+
// We override toString as otherwise you get expect errors like:
|
|
158
|
+
// `expected to resolve but rejected with "[object Object]"`
|
|
159
|
+
Object.defineProperty(d, "toString", {
|
|
160
|
+
value: () => t,
|
|
161
|
+
enumerable: false,
|
|
162
|
+
});
|
|
163
|
+
throw d;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return r.text().then((t) => {
|
|
168
|
+
const response = JSON.parse(t, reviver);
|
|
169
|
+
response.input = parseOutputs(response.input);
|
|
170
|
+
|
|
171
|
+
return response;
|
|
172
|
+
});
|
|
173
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -17,3 +17,195 @@ declare module "vitest" {
|
|
|
17
17
|
interface Assertion<T = any> extends CustomMatchers<T> {}
|
|
18
18
|
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
// Flow Status Types
|
|
22
|
+
export type FlowStatus =
|
|
23
|
+
| "NEW"
|
|
24
|
+
| "RUNNING"
|
|
25
|
+
| "AWAITING_INPUT"
|
|
26
|
+
| "FAILED"
|
|
27
|
+
| "COMPLETED"
|
|
28
|
+
| "CANCELLED";
|
|
29
|
+
|
|
30
|
+
// Step Types
|
|
31
|
+
export type StepType = "FUNCTION" | "UI" | "COMPLETE";
|
|
32
|
+
|
|
33
|
+
// Step Status Types
|
|
34
|
+
export type StepStatus =
|
|
35
|
+
| "NEW"
|
|
36
|
+
| "PENDING"
|
|
37
|
+
| "FAILED"
|
|
38
|
+
| "COMPLETED"
|
|
39
|
+
| "CANCELLED";
|
|
40
|
+
|
|
41
|
+
// Stage Configuration
|
|
42
|
+
export interface FlowStage {
|
|
43
|
+
key: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Flow Configuration
|
|
49
|
+
export interface FlowConfig {
|
|
50
|
+
title: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
stages?: FlowStage[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// UI Action Configuration
|
|
56
|
+
export interface UIAction {
|
|
57
|
+
label: string;
|
|
58
|
+
mode: "primary" | "secondary";
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// UI Input Elements
|
|
63
|
+
export interface UITextInput {
|
|
64
|
+
__type: "ui.input.text";
|
|
65
|
+
label: string;
|
|
66
|
+
name: string;
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
optional?: boolean;
|
|
69
|
+
defaultValue?: string;
|
|
70
|
+
placeholder?: string;
|
|
71
|
+
validationError?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface UINumberInput {
|
|
75
|
+
__type: "ui.input.number";
|
|
76
|
+
label: string;
|
|
77
|
+
name: string;
|
|
78
|
+
disabled?: boolean;
|
|
79
|
+
optional?: boolean;
|
|
80
|
+
defaultValue?: number;
|
|
81
|
+
validationError?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface UIBooleanInput {
|
|
85
|
+
__type: "ui.input.boolean";
|
|
86
|
+
label: string;
|
|
87
|
+
name: string;
|
|
88
|
+
disabled?: boolean;
|
|
89
|
+
optional?: boolean;
|
|
90
|
+
mode: "checkbox";
|
|
91
|
+
validationError?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// UI Display Elements
|
|
95
|
+
export interface UIDivider {
|
|
96
|
+
__type: "ui.display.divider";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface UIMarkdown {
|
|
100
|
+
__type: "ui.display.markdown";
|
|
101
|
+
content: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface UIGrid {
|
|
105
|
+
__type: "ui.display.grid";
|
|
106
|
+
data: Array<{ title: string; [key: string]: any }>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// UI Complete Element
|
|
110
|
+
export interface UIComplete {
|
|
111
|
+
__type: "ui.complete";
|
|
112
|
+
title: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
stage?: string;
|
|
115
|
+
content: UIElement[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// UI Page Element
|
|
119
|
+
export interface UIPage {
|
|
120
|
+
__type: "ui.page";
|
|
121
|
+
title: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
content: UIElement[];
|
|
124
|
+
actions?: UIAction[];
|
|
125
|
+
hasValidationErrors: boolean;
|
|
126
|
+
validationError?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Union type for all UI elements
|
|
130
|
+
export type UIElement =
|
|
131
|
+
| UITextInput
|
|
132
|
+
| UINumberInput
|
|
133
|
+
| UIBooleanInput
|
|
134
|
+
| UIDivider
|
|
135
|
+
| UIMarkdown
|
|
136
|
+
| UIGrid;
|
|
137
|
+
|
|
138
|
+
// Union type for UI configurations
|
|
139
|
+
export type UIConfig = UIPage | UIComplete;
|
|
140
|
+
|
|
141
|
+
declare class FlowExecutor<Input = {}> {
|
|
142
|
+
withIdentity(identity: sdk.Identity): FlowExecutor<Input>;
|
|
143
|
+
withAuthToken(token: string): FlowExecutor<Input>;
|
|
144
|
+
start(inputs: Input): Promise<FlowRun<Input>>;
|
|
145
|
+
get(id: string): Promise<FlowRun<Input>>;
|
|
146
|
+
cancel(id: string): Promise<FlowRun<Input>>;
|
|
147
|
+
putStepValues(
|
|
148
|
+
id: string,
|
|
149
|
+
stepId: string,
|
|
150
|
+
values: Record<string, any>,
|
|
151
|
+
action?: string
|
|
152
|
+
): Promise<FlowRun<Input>>;
|
|
153
|
+
untilAwaitingInput(id: string): Promise<FlowRun<Input>>;
|
|
154
|
+
untilFinished(id: string): Promise<FlowRun<Input>>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Step Definition
|
|
158
|
+
export interface FlowStep {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
runId: string;
|
|
162
|
+
stage: string | null;
|
|
163
|
+
status: StepStatus;
|
|
164
|
+
type: StepType;
|
|
165
|
+
value: any;
|
|
166
|
+
error: string | null;
|
|
167
|
+
startTime: string | null;
|
|
168
|
+
endTime: string | null;
|
|
169
|
+
createdAt: string;
|
|
170
|
+
updatedAt: string;
|
|
171
|
+
ui: UIConfig | null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Flow Run Definition
|
|
175
|
+
export interface FlowRun<Input = {}> {
|
|
176
|
+
id: string;
|
|
177
|
+
traceId: string;
|
|
178
|
+
status: FlowStatus;
|
|
179
|
+
name: string;
|
|
180
|
+
startedBy: Date;
|
|
181
|
+
input: Input | {};
|
|
182
|
+
data: any;
|
|
183
|
+
steps: FlowStep[];
|
|
184
|
+
createdAt: Date;
|
|
185
|
+
updatedAt: Date;
|
|
186
|
+
config: FlowConfig;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Step Values Request
|
|
190
|
+
export interface PutStepValuesRequest {
|
|
191
|
+
name: string;
|
|
192
|
+
runId: string;
|
|
193
|
+
stepId: string;
|
|
194
|
+
values: Record<string, any>;
|
|
195
|
+
token: string;
|
|
196
|
+
action?: string | null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Flow Start Request
|
|
200
|
+
export interface StartFlowRequest {
|
|
201
|
+
name: string;
|
|
202
|
+
token: string;
|
|
203
|
+
body: Record<string, any>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Flow Get Request
|
|
207
|
+
export interface GetFlowRequest {
|
|
208
|
+
name: string;
|
|
209
|
+
id: string;
|
|
210
|
+
token: string;
|
|
211
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { sql } from "kysely";
|
|
2
2
|
export { ActionExecutor } from "./ActionExecutor.mjs";
|
|
3
|
+
export { FlowExecutor } from "./FlowExecutor.mjs";
|
|
3
4
|
export { JobExecutor } from "./JobExecutor.mjs";
|
|
4
5
|
export { SubscriberExecutor } from "./SubscriberExecutor.mjs";
|
|
5
6
|
export { toHaveError } from "./toHaveError.mjs";
|
package/src/parsing.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { File } from "@teamkeel/functions-runtime";
|
|
2
|
+
|
|
3
|
+
export async function parseInputs(inputs) {
|
|
4
|
+
if (inputs != null && typeof inputs === "object") {
|
|
5
|
+
for (const keys of Object.keys(inputs)) {
|
|
6
|
+
if (inputs[keys] !== null && typeof inputs[keys] === "object") {
|
|
7
|
+
if (isDuration(inputs[keys])) {
|
|
8
|
+
inputs[keys] = inputs[keys].toISOString();
|
|
9
|
+
} else if (isInlineFileOrFile(inputs[keys])) {
|
|
10
|
+
const contents = await inputs[keys].read();
|
|
11
|
+
inputs[keys] = `data:${inputs[keys].contentType};name=${
|
|
12
|
+
inputs[keys].filename
|
|
13
|
+
};base64,${contents.toString("base64")}`;
|
|
14
|
+
} else {
|
|
15
|
+
inputs[keys] = await parseInputs(inputs[keys]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return inputs;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isInlineFileOrFile(obj) {
|
|
24
|
+
return (
|
|
25
|
+
obj &&
|
|
26
|
+
typeof obj === "object" &&
|
|
27
|
+
(obj.constructor.name === "InlineFile" || obj.constructor.name === "File")
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isDuration(obj) {
|
|
32
|
+
return obj && typeof obj === "object" && obj.constructor.name === "Duration";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseOutputs(data) {
|
|
36
|
+
if (!data) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!isPlainObject(data)) {
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const keys = data ? Object.keys(data) : [];
|
|
45
|
+
const row = {};
|
|
46
|
+
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
const value = data[key];
|
|
49
|
+
|
|
50
|
+
if (isPlainObject(value)) {
|
|
51
|
+
if (value.key && value.size && value.filename && value.contentType) {
|
|
52
|
+
row[key] = File.fromDbRecord(value);
|
|
53
|
+
} else {
|
|
54
|
+
row[key] = parseOutputs(value);
|
|
55
|
+
}
|
|
56
|
+
} else if (
|
|
57
|
+
Array.isArray(value) &&
|
|
58
|
+
value.every((item) => typeof item === "object" && item !== null)
|
|
59
|
+
) {
|
|
60
|
+
const arr = [];
|
|
61
|
+
for (let item of value) {
|
|
62
|
+
if (item.key && item.size && item.filename && item.contentType) {
|
|
63
|
+
arr.push(File.fromDbRecord(item));
|
|
64
|
+
} else {
|
|
65
|
+
arr.push(parseOutputs(item));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
row[key] = arr;
|
|
69
|
+
} else {
|
|
70
|
+
row[key] = value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return row;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isPlainObject(obj) {
|
|
77
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dateFormat =
|
|
81
|
+
/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
82
|
+
|
|
83
|
+
export function reviver(key, value) {
|
|
84
|
+
// Handle date strings
|
|
85
|
+
if (typeof value === "string") {
|
|
86
|
+
if (dateFormat.test(value)) {
|
|
87
|
+
return new Date(value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle nested objects
|
|
92
|
+
if (value !== null && typeof value === "object") {
|
|
93
|
+
// Handle arrays
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value.map((item) => {
|
|
96
|
+
if (typeof item === "string") {
|
|
97
|
+
if (dateFormat.test(item)) {
|
|
98
|
+
return new Date(item);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return item;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle plain objects
|
|
106
|
+
for (const k in value) {
|
|
107
|
+
if (typeof value[k] === "string") {
|
|
108
|
+
if (dateFormat.test(value[k])) {
|
|
109
|
+
value[k] = new Date(value[k]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return value;
|
|
116
|
+
}
|