@brandboostinggmbh/observable-workflows 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +299 -4
- package/dist/index.js +630 -5
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,301 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
declare
|
|
3
|
-
|
|
1
|
+
//#region src/observableWorkflows/createStepContext.d.ts
|
|
2
|
+
declare function createStepContext(context: StepContextOptions): Promise<(<RESULT>(step: {
|
|
3
|
+
name: string;
|
|
4
|
+
metadata?: Record<string, any>;
|
|
5
|
+
}, callback: (ctx: StepCtx) => Promise<RESULT>) => Promise<RESULT>)>;
|
|
4
6
|
|
|
5
7
|
//#endregion
|
|
6
|
-
|
|
8
|
+
//#region src/observableWorkflows/helperFunctions.d.ts
|
|
9
|
+
declare function finalizeWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
10
|
+
workflowStatus,
|
|
11
|
+
endTime,
|
|
12
|
+
instanceId
|
|
13
|
+
}: {
|
|
14
|
+
workflowStatus: StepWorkflowStatus;
|
|
15
|
+
endTime: number;
|
|
16
|
+
instanceId: string;
|
|
17
|
+
}): Promise<D1Result<Record<string, unknown>>>;
|
|
18
|
+
declare function insertWorkflowRecord(options: InternalWorkflowContextOptions, {
|
|
19
|
+
instanceId,
|
|
20
|
+
workflowType,
|
|
21
|
+
workflowName,
|
|
22
|
+
workflowMetadata,
|
|
23
|
+
input,
|
|
24
|
+
workflowStatus,
|
|
25
|
+
startTime,
|
|
26
|
+
endTime,
|
|
27
|
+
parentInstanceId,
|
|
28
|
+
tenantId
|
|
29
|
+
}: {
|
|
30
|
+
instanceId: string;
|
|
31
|
+
workflowType: string;
|
|
32
|
+
workflowName: string;
|
|
33
|
+
workflowMetadata: any;
|
|
34
|
+
input: any;
|
|
35
|
+
workflowStatus: StepWorkflowStatus;
|
|
36
|
+
startTime: number;
|
|
37
|
+
endTime?: number | null;
|
|
38
|
+
parentInstanceId?: string | null;
|
|
39
|
+
tenantId: string;
|
|
40
|
+
}): Promise<D1Result<Record<string, unknown>>>;
|
|
41
|
+
declare function insertStepRecordFull(context: StepContextOptions, {
|
|
42
|
+
instanceId,
|
|
43
|
+
name,
|
|
44
|
+
status,
|
|
45
|
+
metadata,
|
|
46
|
+
startTime,
|
|
47
|
+
endTime,
|
|
48
|
+
result,
|
|
49
|
+
error
|
|
50
|
+
}: {
|
|
51
|
+
instanceId: string;
|
|
52
|
+
name: string;
|
|
53
|
+
status: StepWorkflowStatus;
|
|
54
|
+
metadata: string;
|
|
55
|
+
startTime: number;
|
|
56
|
+
endTime: number | null;
|
|
57
|
+
result: string | null;
|
|
58
|
+
error: string | null;
|
|
59
|
+
}): Promise<D1Result<Record<string, unknown>>>;
|
|
60
|
+
declare function pushLogToDB(options: InternalWorkflowContextOptions, {
|
|
61
|
+
instanceId,
|
|
62
|
+
stepName,
|
|
63
|
+
message,
|
|
64
|
+
timestamp,
|
|
65
|
+
type,
|
|
66
|
+
logOrder,
|
|
67
|
+
tenantId
|
|
68
|
+
}: {
|
|
69
|
+
instanceId: string;
|
|
70
|
+
stepName: string | null;
|
|
71
|
+
message: string;
|
|
72
|
+
timestamp: number;
|
|
73
|
+
type: string;
|
|
74
|
+
logOrder: number;
|
|
75
|
+
tenantId: string;
|
|
76
|
+
}): Promise<D1Result<Record<string, unknown>>>;
|
|
77
|
+
declare function tryDeserializeObj(obj: string, serializer: Serializer): any;
|
|
78
|
+
/**
|
|
79
|
+
* We want to save steps to a databse, we are using D1, so SQLite is the database.
|
|
80
|
+
* Step Table:
|
|
81
|
+
* - instanceId: string
|
|
82
|
+
* - stepName: string
|
|
83
|
+
* - stepStatus: string (pending, completed, failed)
|
|
84
|
+
* - stepMetadata: string (JSON)
|
|
85
|
+
* - startTime: number (timestamp)
|
|
86
|
+
* - endTime: number (timestamp) (optional)
|
|
87
|
+
* - result: string (JSON) (optional)
|
|
88
|
+
* - error: string (JSON) (optional)
|
|
89
|
+
*
|
|
90
|
+
* Log Table:
|
|
91
|
+
* - instanceId: string
|
|
92
|
+
* - stepName: string
|
|
93
|
+
* - log: string
|
|
94
|
+
* - timestamp: number (timestamp)
|
|
95
|
+
* - type: string (info, error, warning)
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Ensure that the required tables exist in D1 (SQLite).
|
|
99
|
+
*/
|
|
100
|
+
declare function ensureTables(db: D1Database): Promise<void>;
|
|
101
|
+
declare function workflowTableRowToWorkflowRun(row: {
|
|
102
|
+
instanceId: string;
|
|
103
|
+
workflowType: string;
|
|
104
|
+
workflowName: string;
|
|
105
|
+
input: string;
|
|
106
|
+
tenantId: string;
|
|
107
|
+
workflowStatus: StepWorkflowStatus;
|
|
108
|
+
startTime: number;
|
|
109
|
+
endTime: number | null;
|
|
110
|
+
parentInstanceId: string | null;
|
|
111
|
+
}, serializer: Serializer): WorkflowRun;
|
|
112
|
+
declare function updateWorkflowName(context: {
|
|
113
|
+
D1: D1Database;
|
|
114
|
+
}, instanceId: string, newWorkflowName: string): Promise<D1Result<Record<string, unknown>>>;
|
|
115
|
+
type ValueTypeMap = {
|
|
116
|
+
string: string;
|
|
117
|
+
number: number;
|
|
118
|
+
boolean: boolean;
|
|
119
|
+
object: object;
|
|
120
|
+
};
|
|
121
|
+
type PossibleValueTypeNames = keyof ValueTypeMap;
|
|
122
|
+
type PossibleValueTypes = ValueTypeMap[PossibleValueTypeNames];
|
|
123
|
+
declare function upsertWorkflowProperty<T extends keyof ValueTypeMap>({
|
|
124
|
+
context,
|
|
125
|
+
instanceId,
|
|
126
|
+
key,
|
|
127
|
+
valueType,
|
|
128
|
+
value,
|
|
129
|
+
tenantId
|
|
130
|
+
}: {
|
|
131
|
+
context: InternalWorkflowContextOptions;
|
|
132
|
+
instanceId: string;
|
|
133
|
+
key: string;
|
|
134
|
+
valueType: T;
|
|
135
|
+
value: ValueTypeMap[T];
|
|
136
|
+
tenantId: string;
|
|
137
|
+
}): Promise<boolean>;
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/observableWorkflows/types.d.ts
|
|
141
|
+
type StepContextOptions = {
|
|
142
|
+
D1: D1Database;
|
|
143
|
+
tenantId: string;
|
|
144
|
+
instanceId: string;
|
|
145
|
+
/** If the currect execution is a retry, this field references the original execution. */
|
|
146
|
+
parentInstanceId?: string;
|
|
147
|
+
serializer: Serializer;
|
|
148
|
+
idFactory: () => string;
|
|
149
|
+
};
|
|
150
|
+
type ConsoleWrapper = {
|
|
151
|
+
log: (message: string) => void;
|
|
152
|
+
info: (message: string) => void;
|
|
153
|
+
error: (message: string) => void;
|
|
154
|
+
warn: (message: string) => void;
|
|
155
|
+
};
|
|
156
|
+
type StepCtx = {
|
|
157
|
+
console: ConsoleWrapper;
|
|
158
|
+
};
|
|
159
|
+
type Log = {
|
|
160
|
+
instanceId: string;
|
|
161
|
+
stepName: string | null;
|
|
162
|
+
log: string;
|
|
163
|
+
timestamp: number;
|
|
164
|
+
type: 'info' | 'error' | 'warn';
|
|
165
|
+
};
|
|
166
|
+
type StepWorkflowStatus = 'pending' | 'completed' | 'failed';
|
|
167
|
+
type Step = {
|
|
168
|
+
instanceId: string;
|
|
169
|
+
name: string;
|
|
170
|
+
metadata: Record<string, any>;
|
|
171
|
+
status: StepWorkflowStatus;
|
|
172
|
+
startTime: number;
|
|
173
|
+
endTime: number | null;
|
|
174
|
+
result: any | null;
|
|
175
|
+
error: any | null;
|
|
176
|
+
logs?: Log[];
|
|
177
|
+
tenantId?: string;
|
|
178
|
+
};
|
|
179
|
+
type WorkflowRun = {
|
|
180
|
+
instanceId: string;
|
|
181
|
+
workflowType: string;
|
|
182
|
+
workflowName: string;
|
|
183
|
+
input: any;
|
|
184
|
+
tenantId: string;
|
|
185
|
+
workflowStatus: StepWorkflowStatus;
|
|
186
|
+
startTime: number;
|
|
187
|
+
endTime: number | null;
|
|
188
|
+
parentInstanceId: string | null;
|
|
189
|
+
steps?: Step[];
|
|
190
|
+
/** All Logs associated with the workflow. Includes logs that are part of steps */
|
|
191
|
+
logs?: Log[];
|
|
192
|
+
isRetryOf?: WorkflowRun | null;
|
|
193
|
+
retries?: WorkflowRun[] | null;
|
|
194
|
+
properties?: WorkflowProperty[];
|
|
195
|
+
};
|
|
196
|
+
type WorkflowProperty<T extends keyof ValueTypeMap = keyof ValueTypeMap> = {
|
|
197
|
+
key: string;
|
|
198
|
+
valueType: T;
|
|
199
|
+
value: ValueTypeMap[T];
|
|
200
|
+
};
|
|
201
|
+
type WorkflowPropertyDefinition = {
|
|
202
|
+
key: string;
|
|
203
|
+
valueType: 'string' | 'number' | 'boolean' | 'object';
|
|
204
|
+
};
|
|
205
|
+
type StepContext = Awaited<ReturnType<typeof createStepContext>>;
|
|
206
|
+
type Serializer = {
|
|
207
|
+
serialize: (data: any) => string;
|
|
208
|
+
deserialize: (data: string) => any;
|
|
209
|
+
};
|
|
210
|
+
type WorkflowContextOptions = {
|
|
211
|
+
D1: D1Database;
|
|
212
|
+
idFactory?: () => string;
|
|
213
|
+
serializer?: Serializer;
|
|
214
|
+
};
|
|
215
|
+
type InternalWorkflowContextOptions = WorkflowContextOptions & Required<Pick<WorkflowContextOptions, 'serializer' | 'idFactory'>>;
|
|
216
|
+
type QueueWorkflowContextOptions = {
|
|
217
|
+
QUEUE: Queue<WorkflowQueueMessage>;
|
|
218
|
+
serializer?: Serializer;
|
|
219
|
+
idFactory?: () => string;
|
|
220
|
+
};
|
|
221
|
+
type WorkflowContext = {
|
|
222
|
+
step: StepContext;
|
|
223
|
+
console: ConsoleWrapper;
|
|
224
|
+
setWorkflowName: (newName: string) => Promise<void>;
|
|
225
|
+
setWorkflowProperty: <T extends keyof ValueTypeMap>(key: string, valueType: T, value: ValueTypeMap[T]) => Promise<void>;
|
|
226
|
+
};
|
|
227
|
+
type WorkflowFunction<INPUT, TYPE extends string = string> = {
|
|
228
|
+
workflowType: TYPE;
|
|
229
|
+
metadata: Record<string, any>;
|
|
230
|
+
/**
|
|
231
|
+
* The code that will be executed.
|
|
232
|
+
* Do not call this directly.
|
|
233
|
+
* Use either the WorkflowContect, or the QueueWorkflowContext to run or enqueue this. */
|
|
234
|
+
_callback: (input: INPUT, ctx: WorkflowContext) => Promise<any>;
|
|
235
|
+
};
|
|
236
|
+
type WorkflowQueueMessage = {
|
|
237
|
+
type: 'workflow-run';
|
|
238
|
+
workflowType: string;
|
|
239
|
+
workflowName: string;
|
|
240
|
+
tenantId: string;
|
|
241
|
+
input: any;
|
|
242
|
+
} | {
|
|
243
|
+
type: 'workflow-retry';
|
|
244
|
+
workflowType: string;
|
|
245
|
+
tenantId: string;
|
|
246
|
+
retryInstanceId: string;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/observableWorkflows/defineWorkflow.d.ts
|
|
251
|
+
declare function defineWorkflow<I extends {} | null>(workflow: {
|
|
252
|
+
workflowType: string;
|
|
253
|
+
metadata?: Record<string, any>;
|
|
254
|
+
}, callback: (input: I, ctx: WorkflowContext) => Promise<any>): WorkflowFunction<I>;
|
|
255
|
+
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/observableWorkflows/createWorkflowContext.d.ts
|
|
258
|
+
declare function createWorkflowContext(options: WorkflowContextOptions): Promise<{
|
|
259
|
+
call: <I>({
|
|
260
|
+
workflow,
|
|
261
|
+
input,
|
|
262
|
+
workflowName,
|
|
263
|
+
tenantId,
|
|
264
|
+
parentInstanceId
|
|
265
|
+
}: {
|
|
266
|
+
workflow: WorkflowFunction<I>;
|
|
267
|
+
input: I;
|
|
268
|
+
workflowName: string;
|
|
269
|
+
tenantId: string;
|
|
270
|
+
parentInstanceId?: string | undefined;
|
|
271
|
+
}) => Promise<void>;
|
|
272
|
+
retry: <I>(workflow: WorkflowFunction<I>, retryInstanceId: string) => Promise<void>;
|
|
273
|
+
}>;
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/observableWorkflows/createQueueWorkflowContext.d.ts
|
|
277
|
+
declare function createQueueWorkflowContext(options: QueueWorkflowContextOptions): {
|
|
278
|
+
enqueueWorkflow: <I>(workflow: WorkflowFunction<I>, tenantId: string, input: I, initialName: string) => Promise<void>;
|
|
279
|
+
enqueueRetryWorkflow: <I>(workflow: WorkflowFunction<I>, tenantId: string, oldInstanceId: string) => Promise<void>;
|
|
280
|
+
handleWorkflowQueueMessage: <T>(message: WorkflowQueueMessage, env: {
|
|
281
|
+
LOG_DB: D1Database;
|
|
282
|
+
}, workflowResolver: (workflowType: string) => WorkflowFunction<T> | undefined) => Promise<void>;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/observableWorkflows/createLogAccessor.d.ts
|
|
287
|
+
declare const createLogAccessor: (context: {
|
|
288
|
+
D1: D1Database;
|
|
289
|
+
tenantId: string;
|
|
290
|
+
serializer: Serializer;
|
|
291
|
+
}) => {
|
|
292
|
+
listSteps: (limit: number, offset: number, instanceId?: string | undefined) => Promise<Step[]>;
|
|
293
|
+
getStep: (instanceId: string, stepName: string) => Promise<Step | null>;
|
|
294
|
+
listWorkflows: (limit: number, offset: number) => Promise<WorkflowRun[]>;
|
|
295
|
+
getWorkflow: (instanceId: string) => Promise<WorkflowRun | null>;
|
|
296
|
+
getWorkflowTypesByTenantId: (tenantId: string) => Promise<string[]>;
|
|
297
|
+
getPropertiesKeys: (instanceId?: string) => Promise<WorkflowPropertyDefinition[]>;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
export { ConsoleWrapper, InternalWorkflowContextOptions, Log, PossibleValueTypeNames, PossibleValueTypes, QueueWorkflowContextOptions, Serializer, Step, StepContextOptions, StepCtx, StepWorkflowStatus, ValueTypeMap, WorkflowContext, WorkflowContextOptions, WorkflowFunction, WorkflowProperty, WorkflowPropertyDefinition, WorkflowQueueMessage, WorkflowRun, createLogAccessor, createQueueWorkflowContext, createStepContext, createWorkflowContext, defineWorkflow, ensureTables, finalizeWorkflowRecord, insertStepRecordFull, insertWorkflowRecord, pushLogToDB, tryDeserializeObj, updateWorkflowName, upsertWorkflowProperty, workflowTableRowToWorkflowRun };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,633 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
//#region src/observableWorkflows/helperFunctions.ts
|
|
2
|
+
function finalizeWorkflowRecord(options, { workflowStatus, endTime, instanceId }) {
|
|
3
|
+
return options.D1.prepare(
|
|
4
|
+
/* sql */
|
|
5
|
+
`UPDATE WorkflowTable
|
|
6
|
+
SET workflowStatus = ?, endTime = ?
|
|
7
|
+
WHERE instanceId = ?`
|
|
8
|
+
).bind(workflowStatus, endTime, instanceId).run();
|
|
5
9
|
}
|
|
10
|
+
function insertWorkflowRecord(options, { instanceId, workflowType, workflowName, workflowMetadata, input, workflowStatus, startTime, endTime, parentInstanceId, tenantId }) {
|
|
11
|
+
return options.D1.prepare(
|
|
12
|
+
/* sql */
|
|
13
|
+
`INSERT INTO WorkflowTable
|
|
14
|
+
(instanceId, workflowType, workflowName, workflowMetadata, input, tenantId, workflowStatus, startTime, endTime, parentInstanceId)
|
|
15
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
16
|
+
).bind(instanceId, workflowType, workflowName, options.serializer.serialize(workflowMetadata), options.serializer.serialize(input), tenantId, workflowStatus, startTime, endTime ?? null, parentInstanceId ?? null).run();
|
|
17
|
+
}
|
|
18
|
+
function insertStepRecordFull(context, { instanceId, name, status, metadata, startTime, endTime, result, error }) {
|
|
19
|
+
return context.D1.prepare(
|
|
20
|
+
/* sql */
|
|
21
|
+
`INSERT INTO StepTable
|
|
22
|
+
(instanceId, stepName, stepStatus, stepMetadata, startTime, endTime, result, error, tenantId)
|
|
23
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
24
|
+
).bind(instanceId, name, status, metadata, startTime, endTime, result, error, context.tenantId).run();
|
|
25
|
+
}
|
|
26
|
+
function pushLogToDB(options, { instanceId, stepName, message, timestamp, type, logOrder, tenantId }) {
|
|
27
|
+
return options.D1.prepare(
|
|
28
|
+
/* sql */
|
|
29
|
+
`INSERT INTO LogTable
|
|
30
|
+
(instanceId, stepName, log, timestamp, type, logOrder, tenantId)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
32
|
+
).bind(instanceId, stepName, message, timestamp, type, logOrder, tenantId).run();
|
|
33
|
+
}
|
|
34
|
+
function tryDeserializeObj(obj, serializer) {
|
|
35
|
+
try {
|
|
36
|
+
return serializer.deserialize(obj);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("Error deserializing object:", error);
|
|
39
|
+
return obj;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* We want to save steps to a databse, we are using D1, so SQLite is the database.
|
|
44
|
+
* Step Table:
|
|
45
|
+
* - instanceId: string
|
|
46
|
+
* - stepName: string
|
|
47
|
+
* - stepStatus: string (pending, completed, failed)
|
|
48
|
+
* - stepMetadata: string (JSON)
|
|
49
|
+
* - startTime: number (timestamp)
|
|
50
|
+
* - endTime: number (timestamp) (optional)
|
|
51
|
+
* - result: string (JSON) (optional)
|
|
52
|
+
* - error: string (JSON) (optional)
|
|
53
|
+
*
|
|
54
|
+
* Log Table:
|
|
55
|
+
* - instanceId: string
|
|
56
|
+
* - stepName: string
|
|
57
|
+
* - log: string
|
|
58
|
+
* - timestamp: number (timestamp)
|
|
59
|
+
* - type: string (info, error, warning)
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Ensure that the required tables exist in D1 (SQLite).
|
|
63
|
+
*/
|
|
64
|
+
async function ensureTables(db) {
|
|
65
|
+
const s1 = db.prepare(
|
|
66
|
+
/* sql */
|
|
67
|
+
`CREATE TABLE IF NOT EXISTS WorkflowTable (
|
|
68
|
+
instanceId TEXT NOT NULL,
|
|
69
|
+
workflowType TEXT NOT NULL,
|
|
70
|
+
workflowName TEXT NOT NULL,
|
|
71
|
+
workflowMetadata TEXT NOT NULL,
|
|
72
|
+
input TEXT NOT NULL,
|
|
73
|
+
tenantId TEXT NOT NULL,
|
|
74
|
+
workflowStatus TEXT NOT NULL,
|
|
75
|
+
startTime INTEGER NOT NULL,
|
|
76
|
+
endTime INTEGER,
|
|
77
|
+
parentInstanceId TEXT,
|
|
78
|
+
PRIMARY KEY (instanceId)
|
|
79
|
+
)`
|
|
80
|
+
);
|
|
81
|
+
const s2 = db.prepare(
|
|
82
|
+
/* sql */
|
|
83
|
+
`CREATE TABLE IF NOT EXISTS StepTable (
|
|
84
|
+
instanceId TEXT NOT NULL,
|
|
85
|
+
stepName TEXT NOT NULL,
|
|
86
|
+
stepStatus TEXT NOT NULL,
|
|
87
|
+
stepMetadata TEXT NOT NULL,
|
|
88
|
+
startTime INTEGER NOT NULL,
|
|
89
|
+
endTime INTEGER,
|
|
90
|
+
result TEXT,
|
|
91
|
+
error TEXT,
|
|
92
|
+
tenantId TEXT NOT NULL,
|
|
93
|
+
PRIMARY KEY (instanceId, stepName)
|
|
94
|
+
)`
|
|
95
|
+
);
|
|
96
|
+
const s3 = db.prepare(
|
|
97
|
+
/* sql */
|
|
98
|
+
`CREATE TABLE IF NOT EXISTS LogTable (
|
|
99
|
+
instanceId TEXT NOT NULL,
|
|
100
|
+
stepName TEXT,
|
|
101
|
+
log TEXT NOT NULL,
|
|
102
|
+
timestamp INTEGER NOT NULL,
|
|
103
|
+
type TEXT NOT NULL,
|
|
104
|
+
logOrder INTEGER NOT NULL,
|
|
105
|
+
tenantId TEXT NOT NULL,
|
|
106
|
+
FOREIGN KEY (instanceId, stepName) REFERENCES StepTable(instanceId, stepName)
|
|
107
|
+
DEFERRABLE INITIALLY DEFERRED -- Makes constraint check at commit time, this is needed, since stepName is nullable in the log table.
|
|
108
|
+
|
|
109
|
+
)`
|
|
110
|
+
);
|
|
111
|
+
const s7 = db.prepare(
|
|
112
|
+
/* sql */
|
|
113
|
+
`
|
|
114
|
+
CREATE TABLE IF NOT EXISTS WorkflowProperties (
|
|
115
|
+
instanceId TEXT NOT NULL,
|
|
116
|
+
key TEXT NOT NULL,
|
|
117
|
+
value TEXT NOT NULL,
|
|
118
|
+
valueType TEXT NOT NULL,
|
|
119
|
+
tenantId TEXT NOT NULL,
|
|
120
|
+
PRIMARY KEY (instanceId, key),
|
|
121
|
+
FOREIGN KEY (instanceId) REFERENCES WorkflowTable(instanceId)
|
|
122
|
+
ON DELETE CASCADE
|
|
123
|
+
)`
|
|
124
|
+
);
|
|
125
|
+
const s6 = db.prepare(
|
|
126
|
+
/* sql */
|
|
127
|
+
`
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_parent_instance_id ON WorkflowTable (parentInstanceId)
|
|
129
|
+
`
|
|
130
|
+
);
|
|
131
|
+
const p1 = db.prepare(`PRAGMA foreign_keys = ON`);
|
|
132
|
+
await db.batch([
|
|
133
|
+
s1,
|
|
134
|
+
s2,
|
|
135
|
+
s3,
|
|
136
|
+
s6,
|
|
137
|
+
s7,
|
|
138
|
+
p1
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
function workflowTableRowToWorkflowRun(row, serializer) {
|
|
142
|
+
return {
|
|
143
|
+
instanceId: row.instanceId,
|
|
144
|
+
workflowType: row.workflowType,
|
|
145
|
+
workflowName: row.workflowName,
|
|
146
|
+
input: tryDeserializeObj(row.input, serializer),
|
|
147
|
+
tenantId: row.tenantId,
|
|
148
|
+
workflowStatus: row.workflowStatus,
|
|
149
|
+
startTime: row.startTime,
|
|
150
|
+
endTime: row.endTime,
|
|
151
|
+
parentInstanceId: row.parentInstanceId
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function updateWorkflowName(context, instanceId, newWorkflowName) {
|
|
155
|
+
return await context.D1.prepare(
|
|
156
|
+
/* sql */
|
|
157
|
+
`UPDATE WorkflowTable
|
|
158
|
+
SET workflowName = ?
|
|
159
|
+
WHERE instanceId = ?`
|
|
160
|
+
).bind(newWorkflowName, instanceId).run();
|
|
161
|
+
}
|
|
162
|
+
async function upsertWorkflowProperty({ context, instanceId, key, valueType, value, tenantId }) {
|
|
163
|
+
const serializedValue = valueType === "object" ? JSON.stringify(value) : String(value);
|
|
164
|
+
const res = await context.D1.prepare(
|
|
165
|
+
/* sql */
|
|
166
|
+
`INSERT OR REPLACE INTO WorkflowProperties
|
|
167
|
+
(instanceId, key, value, valueType, tenantId)
|
|
168
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
169
|
+
).bind(instanceId, key, serializedValue, valueType, tenantId).run();
|
|
170
|
+
if (res.error) {
|
|
171
|
+
console.error("Error inserting workflow property:", res.error);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return res.success;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/observableWorkflows/defineWorkflow.ts
|
|
179
|
+
function defineWorkflow(workflow, callback) {
|
|
180
|
+
return {
|
|
181
|
+
workflowType: workflow.workflowType,
|
|
182
|
+
metadata: workflow.metadata || {},
|
|
183
|
+
_callback: callback
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/observableWorkflows/createStepContext.ts
|
|
189
|
+
async function createStepContext(context) {
|
|
190
|
+
const instanceId = context.instanceId;
|
|
191
|
+
await ensureTables(context.D1);
|
|
192
|
+
const step = async (step$1, callback) => {
|
|
193
|
+
if (context.parentInstanceId) {
|
|
194
|
+
const existingStep = await context.D1.prepare(
|
|
195
|
+
/* sql */
|
|
196
|
+
`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`
|
|
197
|
+
).bind(context.parentInstanceId, step$1.name, context.tenantId).first();
|
|
198
|
+
if (existingStep) {
|
|
199
|
+
const row = existingStep;
|
|
200
|
+
if (row.status === "completed") {
|
|
201
|
+
await insertStepRecordFull(context, {
|
|
202
|
+
instanceId,
|
|
203
|
+
name: row.name,
|
|
204
|
+
status: row.status,
|
|
205
|
+
metadata: row.metadata,
|
|
206
|
+
startTime: row.startTime,
|
|
207
|
+
endTime: row.endTime,
|
|
208
|
+
result: row.result,
|
|
209
|
+
error: row.error
|
|
210
|
+
});
|
|
211
|
+
return context.serializer.deserialize(row.result);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
let waitFor = [];
|
|
216
|
+
const startTime = Date.now();
|
|
217
|
+
const stepStatus = "pending";
|
|
218
|
+
const stepMetadata = context.serializer.serialize(step$1.metadata || {});
|
|
219
|
+
const stepName = step$1.name;
|
|
220
|
+
const stepResult = null;
|
|
221
|
+
const stepError = null;
|
|
222
|
+
await insertStepRecordFull(context, {
|
|
223
|
+
instanceId,
|
|
224
|
+
name: stepName,
|
|
225
|
+
status: stepStatus,
|
|
226
|
+
metadata: stepMetadata,
|
|
227
|
+
startTime,
|
|
228
|
+
endTime: null,
|
|
229
|
+
result: stepResult,
|
|
230
|
+
error: stepError
|
|
231
|
+
});
|
|
232
|
+
let logOrder = 0;
|
|
233
|
+
const log = (message, type = "info") => {
|
|
234
|
+
logOrder++;
|
|
235
|
+
const timestamp = Date.now();
|
|
236
|
+
const logPromise = pushLogToDB(context, {
|
|
237
|
+
stepName: step$1.name,
|
|
238
|
+
instanceId,
|
|
239
|
+
message,
|
|
240
|
+
timestamp,
|
|
241
|
+
type,
|
|
242
|
+
logOrder,
|
|
243
|
+
tenantId: context.tenantId
|
|
244
|
+
});
|
|
245
|
+
waitFor.push(logPromise);
|
|
246
|
+
};
|
|
247
|
+
const ctx = { console: {
|
|
248
|
+
log: (message) => {
|
|
249
|
+
log(message, "info");
|
|
250
|
+
},
|
|
251
|
+
info: (message) => {
|
|
252
|
+
log(message, "info");
|
|
253
|
+
},
|
|
254
|
+
error: (message) => {
|
|
255
|
+
log(message, "error");
|
|
256
|
+
},
|
|
257
|
+
warn: (message) => {
|
|
258
|
+
log(message, "warn");
|
|
259
|
+
}
|
|
260
|
+
} };
|
|
261
|
+
try {
|
|
262
|
+
const result = await callback(ctx);
|
|
263
|
+
const endTime = Date.now();
|
|
264
|
+
const stepStatus$1 = "completed";
|
|
265
|
+
let stepResult$1 = null;
|
|
266
|
+
try {
|
|
267
|
+
stepResult$1 = result ? context.serializer.serialize(result) : null;
|
|
268
|
+
} catch {
|
|
269
|
+
stepResult$1 = String(result);
|
|
270
|
+
}
|
|
271
|
+
const stepError$1 = null;
|
|
272
|
+
await context.D1.prepare(
|
|
273
|
+
/* sql */
|
|
274
|
+
`UPDATE StepTable
|
|
275
|
+
SET stepStatus = ?, endTime = ?, result = ?, error = ?
|
|
276
|
+
WHERE instanceId = ? AND stepName = ?`
|
|
277
|
+
).bind(stepStatus$1, endTime, stepResult$1, stepError$1, instanceId, stepName).run();
|
|
278
|
+
await Promise.allSettled(waitFor);
|
|
279
|
+
return result;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const endTime = Date.now();
|
|
282
|
+
const stepStatus$1 = "failed";
|
|
283
|
+
const stepResult$1 = null;
|
|
284
|
+
let stepError$1;
|
|
285
|
+
try {
|
|
286
|
+
stepError$1 = context.serializer.serialize(error);
|
|
287
|
+
} catch {
|
|
288
|
+
stepError$1 = String(error);
|
|
289
|
+
}
|
|
290
|
+
await context.D1.prepare(
|
|
291
|
+
/* sql */
|
|
292
|
+
`UPDATE StepTable
|
|
293
|
+
SET stepStatus = ?, endTime = ?, result = ?, error = ?
|
|
294
|
+
WHERE instanceId = ? AND stepName = ?`
|
|
295
|
+
).bind(stepStatus$1, endTime, stepResult$1, stepError$1, instanceId, stepName).run();
|
|
296
|
+
await Promise.allSettled(waitFor);
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
return step;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/observableWorkflows/createWorkflowContext.ts
|
|
305
|
+
async function createWorkflowContext(options) {
|
|
306
|
+
await ensureTables(options.D1);
|
|
307
|
+
const internalContext = {
|
|
308
|
+
...options,
|
|
309
|
+
serializer: options.serializer ?? {
|
|
310
|
+
serialize: JSON.stringify,
|
|
311
|
+
deserialize: JSON.parse
|
|
312
|
+
},
|
|
313
|
+
idFactory: options.idFactory ?? (() => crypto.randomUUID())
|
|
314
|
+
};
|
|
315
|
+
const call = async ({ workflow, input, workflowName, tenantId, parentInstanceId }) => {
|
|
316
|
+
const instanceId = internalContext.idFactory();
|
|
317
|
+
const startTime = Date.now();
|
|
318
|
+
await insertWorkflowRecord(internalContext, {
|
|
319
|
+
instanceId,
|
|
320
|
+
workflowType: workflow.workflowType,
|
|
321
|
+
workflowName,
|
|
322
|
+
workflowMetadata: workflow.metadata,
|
|
323
|
+
input,
|
|
324
|
+
workflowStatus: "pending",
|
|
325
|
+
startTime,
|
|
326
|
+
endTime: null,
|
|
327
|
+
parentInstanceId,
|
|
328
|
+
tenantId
|
|
329
|
+
});
|
|
330
|
+
let waitFor = [];
|
|
331
|
+
let logOrder = 0;
|
|
332
|
+
const log = (message, type = "info") => {
|
|
333
|
+
logOrder++;
|
|
334
|
+
const timestamp = Date.now();
|
|
335
|
+
const logPromise = pushLogToDB(internalContext, {
|
|
336
|
+
instanceId,
|
|
337
|
+
stepName: null,
|
|
338
|
+
message,
|
|
339
|
+
timestamp,
|
|
340
|
+
type,
|
|
341
|
+
logOrder,
|
|
342
|
+
tenantId
|
|
343
|
+
});
|
|
344
|
+
waitFor.push(logPromise);
|
|
345
|
+
};
|
|
346
|
+
const stepContext = await createStepContext({
|
|
347
|
+
D1: options.D1,
|
|
348
|
+
tenantId,
|
|
349
|
+
instanceId,
|
|
350
|
+
serializer: internalContext.serializer,
|
|
351
|
+
idFactory: internalContext.idFactory
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
await workflow._callback(input, {
|
|
355
|
+
step: stepContext,
|
|
356
|
+
console: {
|
|
357
|
+
log: (message) => {
|
|
358
|
+
log(message, "info");
|
|
359
|
+
},
|
|
360
|
+
info: (message) => {
|
|
361
|
+
log(message, "info");
|
|
362
|
+
},
|
|
363
|
+
error: (message) => {
|
|
364
|
+
log(message, "error");
|
|
365
|
+
},
|
|
366
|
+
warn: (message) => {
|
|
367
|
+
log(message, "warn");
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
async setWorkflowName(newName) {
|
|
371
|
+
await updateWorkflowName({ D1: options.D1 }, instanceId, newName);
|
|
372
|
+
},
|
|
373
|
+
async setWorkflowProperty(key, valueType, value) {
|
|
374
|
+
await upsertWorkflowProperty({
|
|
375
|
+
context: internalContext,
|
|
376
|
+
instanceId,
|
|
377
|
+
key,
|
|
378
|
+
valueType,
|
|
379
|
+
value,
|
|
380
|
+
tenantId
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
const endTime = Date.now();
|
|
385
|
+
const workflowStatus = "completed";
|
|
386
|
+
await finalizeWorkflowRecord(internalContext, {
|
|
387
|
+
workflowStatus,
|
|
388
|
+
endTime,
|
|
389
|
+
instanceId
|
|
390
|
+
});
|
|
391
|
+
await Promise.allSettled(waitFor);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
const endTime = Date.now();
|
|
394
|
+
const workflowStatus = "failed";
|
|
395
|
+
await finalizeWorkflowRecord(internalContext, {
|
|
396
|
+
workflowStatus,
|
|
397
|
+
endTime,
|
|
398
|
+
instanceId
|
|
399
|
+
});
|
|
400
|
+
await Promise.allSettled(waitFor);
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const retry = async (workflow, retryInstanceId) => {
|
|
405
|
+
const oldRun = await options.D1.prepare(
|
|
406
|
+
/* sql */
|
|
407
|
+
`SELECT input, workflowName, tenantId FROM WorkflowTable WHERE instanceId = ? `
|
|
408
|
+
).bind(retryInstanceId).first();
|
|
409
|
+
const oldWorkflowName = oldRun?.workflowName;
|
|
410
|
+
const tenantId = oldRun?.tenantId;
|
|
411
|
+
if (!tenantId) throw new Error(`No tenantId found for instanceId ${retryInstanceId}`);
|
|
412
|
+
const encodedInput = oldRun?.input;
|
|
413
|
+
if (!encodedInput) throw new Error(`No input found for instanceId ${retryInstanceId}`);
|
|
414
|
+
const input = JSON.parse(encodedInput);
|
|
415
|
+
await call({
|
|
416
|
+
workflow,
|
|
417
|
+
input,
|
|
418
|
+
workflowName: oldWorkflowName ?? "unknown",
|
|
419
|
+
parentInstanceId: retryInstanceId,
|
|
420
|
+
tenantId
|
|
421
|
+
});
|
|
422
|
+
};
|
|
423
|
+
return {
|
|
424
|
+
call,
|
|
425
|
+
retry
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/observableWorkflows/createQueueWorkflowContext.ts
|
|
431
|
+
function createQueueWorkflowContext(options) {
|
|
432
|
+
const enqueueWorkflow = async (workflow, tenantId, input, initialName) => {
|
|
433
|
+
await options.QUEUE.send({
|
|
434
|
+
type: "workflow-run",
|
|
435
|
+
workflowType: workflow.workflowType,
|
|
436
|
+
workflowName: initialName,
|
|
437
|
+
input,
|
|
438
|
+
tenantId
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
const enqueueRetryWorkflow = async (workflow, tenantId, oldInstanceId) => {
|
|
442
|
+
await options.QUEUE.send({
|
|
443
|
+
type: "workflow-retry",
|
|
444
|
+
workflowType: workflow.workflowType,
|
|
445
|
+
retryInstanceId: oldInstanceId,
|
|
446
|
+
tenantId
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
const handleWorkflowQueueMessage = async (message, env, workflowResolver) => {
|
|
450
|
+
const workflowFunction = workflowResolver(message.workflowType);
|
|
451
|
+
if (!workflowFunction) throw new Error(`Workflow ${message.workflowType} not found`);
|
|
452
|
+
const wfc = await createWorkflowContext({
|
|
453
|
+
D1: env.LOG_DB,
|
|
454
|
+
serializer: options.serializer,
|
|
455
|
+
idFactory: options.idFactory
|
|
456
|
+
});
|
|
457
|
+
if (message.type === "workflow-run") {
|
|
458
|
+
console.log("running workflow", message.input);
|
|
459
|
+
await wfc.call({
|
|
460
|
+
input: message.input,
|
|
461
|
+
workflow: workflowFunction,
|
|
462
|
+
workflowName: message.workflowName,
|
|
463
|
+
tenantId: message.tenantId
|
|
464
|
+
});
|
|
465
|
+
} else if (message.type === "workflow-retry") {
|
|
466
|
+
console.log("retrying workflow", message.retryInstanceId);
|
|
467
|
+
await wfc.retry(workflowFunction, message.retryInstanceId);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
return {
|
|
471
|
+
enqueueWorkflow,
|
|
472
|
+
enqueueRetryWorkflow,
|
|
473
|
+
handleWorkflowQueueMessage
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/observableWorkflows/createLogAccessor.ts
|
|
479
|
+
const createLogAccessor = (context) => {
|
|
480
|
+
const listSteps = async (limit, offset, instanceId) => {
|
|
481
|
+
const result = await context.D1.prepare(
|
|
482
|
+
/* sql */
|
|
483
|
+
`SELECT * FROM StepTable
|
|
484
|
+
WHERE tenantId = ? AND (? IS NULL OR instanceId = ?)
|
|
485
|
+
ORDER BY startTime ASC LIMIT ? OFFSET ?`
|
|
486
|
+
).bind(context.tenantId, instanceId ?? null, instanceId ?? null, limit, offset).all();
|
|
487
|
+
return result.results ? result.results.map((row) => ({
|
|
488
|
+
instanceId: row.instanceId,
|
|
489
|
+
name: row.stepName,
|
|
490
|
+
metadata: tryDeserializeObj(row.stepMetadata, context.serializer),
|
|
491
|
+
status: row.stepStatus,
|
|
492
|
+
startTime: row.startTime,
|
|
493
|
+
endTime: row.endTime,
|
|
494
|
+
result: row.result ? tryDeserializeObj(row.result, context.serializer) : null,
|
|
495
|
+
error: row.error ? tryDeserializeObj(row.error, context.serializer) : null
|
|
496
|
+
})) : [];
|
|
497
|
+
};
|
|
498
|
+
const listWorkflows = async (limit, offset) => {
|
|
499
|
+
const result = await context.D1.prepare(
|
|
500
|
+
/* sql */
|
|
501
|
+
`SELECT *
|
|
502
|
+
FROM WorkflowTable
|
|
503
|
+
WHERE tenantId = ?
|
|
504
|
+
ORDER BY startTime DESC LIMIT ? OFFSET ?`
|
|
505
|
+
).bind(context.tenantId, limit, offset).all();
|
|
506
|
+
return result.results ? result.results.map((row) => workflowTableRowToWorkflowRun(row, context.serializer)) : [];
|
|
507
|
+
};
|
|
508
|
+
const getWorkflowByParentId = async (parentInstanceId) => {
|
|
509
|
+
const result = await context.D1.prepare(
|
|
510
|
+
/* sql */
|
|
511
|
+
`SELECT * FROM WorkflowTable WHERE parentInstanceId = ? AND tenantId = ?`
|
|
512
|
+
).bind(parentInstanceId, context.tenantId).all();
|
|
513
|
+
return result.results ? result.results.map((row) => workflowTableRowToWorkflowRun(row, context.serializer)) : null;
|
|
514
|
+
};
|
|
515
|
+
const getWorkflowTypesByTenantId = async (tenantId) => {
|
|
516
|
+
const result = await context.D1.prepare(
|
|
517
|
+
/* sql */
|
|
518
|
+
`SELECT DISTINCT workflowType FROM WorkflowTable WHERE tenantId = ?`
|
|
519
|
+
).bind(tenantId).all();
|
|
520
|
+
if (!result.results) return [];
|
|
521
|
+
return result.results.map((row) => row.workflowType);
|
|
522
|
+
};
|
|
523
|
+
const getWorkflowProperties = async (instanceId) => {
|
|
524
|
+
const result = await context.D1.prepare(
|
|
525
|
+
/* sql */
|
|
526
|
+
`SELECT * FROM WorkflowProperties WHERE instanceId = ?`
|
|
527
|
+
).bind(instanceId).all();
|
|
528
|
+
if (!result.results) return [];
|
|
529
|
+
return result.results.map((row) => ({
|
|
530
|
+
key: row.key,
|
|
531
|
+
valueType: row.valueType,
|
|
532
|
+
value: tryDeserializeObj(row.value, context.serializer)
|
|
533
|
+
}));
|
|
534
|
+
};
|
|
535
|
+
/** This function gets the basic data of a workflow, without populating any of it's complex fields */
|
|
536
|
+
const getWorkflowShallow = async (instanceId) => {
|
|
537
|
+
const result = await context.D1.prepare(
|
|
538
|
+
/* sql */
|
|
539
|
+
`SELECT * FROM WorkflowTable WHERE instanceId = ? AND tenantId = ?`
|
|
540
|
+
).bind(instanceId, context.tenantId).first();
|
|
541
|
+
if (!result) return null;
|
|
542
|
+
const workflow = workflowTableRowToWorkflowRun(result, context.serializer);
|
|
543
|
+
return workflow;
|
|
544
|
+
};
|
|
545
|
+
const getWorkflow = async (instanceId) => {
|
|
546
|
+
const workflow = await getWorkflowShallow(instanceId);
|
|
547
|
+
if (!workflow) return null;
|
|
548
|
+
const steps = await listSteps(100, 0, instanceId);
|
|
549
|
+
const retryWorkflows = await getWorkflowByParentId(instanceId);
|
|
550
|
+
const parentWorkflow = workflow.parentInstanceId ? await getWorkflowShallow(workflow.parentInstanceId) : null;
|
|
551
|
+
workflow.retries = retryWorkflows;
|
|
552
|
+
retryWorkflows?.forEach((retryWorkflow) => {
|
|
553
|
+
retryWorkflow.isRetryOf = workflow;
|
|
554
|
+
});
|
|
555
|
+
workflow.isRetryOf = parentWorkflow;
|
|
556
|
+
const allLogs = await context.D1.prepare(
|
|
557
|
+
/* sql */
|
|
558
|
+
`SELECT * FROM LogTable WHERE instanceId = ? ORDER BY timestamp ASC, logOrder ASC`
|
|
559
|
+
).bind(instanceId).all();
|
|
560
|
+
const logs = allLogs.results?.map((logRow) => ({
|
|
561
|
+
instanceId: logRow.instanceId,
|
|
562
|
+
stepName: logRow.stepName,
|
|
563
|
+
log: logRow.log,
|
|
564
|
+
timestamp: logRow.timestamp,
|
|
565
|
+
type: logRow.type
|
|
566
|
+
}));
|
|
567
|
+
steps.forEach((step) => {
|
|
568
|
+
step.logs = logs?.filter((log) => log.stepName === step.name) ?? [];
|
|
569
|
+
});
|
|
570
|
+
workflow.steps = steps;
|
|
571
|
+
workflow.logs = logs;
|
|
572
|
+
const properties = await getWorkflowProperties(instanceId);
|
|
573
|
+
workflow.properties = properties;
|
|
574
|
+
return workflow;
|
|
575
|
+
};
|
|
576
|
+
const getStep = async (instanceId, stepName) => {
|
|
577
|
+
const result = await context.D1.prepare(
|
|
578
|
+
/* sql */
|
|
579
|
+
`SELECT * FROM StepTable WHERE instanceId = ? AND stepName = ? AND tenantId = ?`
|
|
580
|
+
).bind(instanceId, stepName, context.tenantId).all();
|
|
581
|
+
if (!result.results || result.results.length === 0) return null;
|
|
582
|
+
const row = result.results[0];
|
|
583
|
+
if (!row) return null;
|
|
584
|
+
const step = {
|
|
585
|
+
instanceId: row.instanceId,
|
|
586
|
+
name: row.stepName,
|
|
587
|
+
metadata: tryDeserializeObj(row.stepMetadata, context.serializer),
|
|
588
|
+
status: row.stepStatus,
|
|
589
|
+
startTime: row.startTime,
|
|
590
|
+
endTime: row.endTime,
|
|
591
|
+
result: row.result ? tryDeserializeObj(row.result, context.serializer) : null,
|
|
592
|
+
error: row.error ? tryDeserializeObj(row.error, context.serializer) : null,
|
|
593
|
+
logs: []
|
|
594
|
+
};
|
|
595
|
+
const logResult = await context.D1.prepare(
|
|
596
|
+
/* sql */
|
|
597
|
+
`SELECT * FROM LogTable WHERE instanceId = ? AND stepName = ? ORDER BY logOrder ASC`
|
|
598
|
+
).bind(instanceId, stepName).all();
|
|
599
|
+
if (logResult.results) step.logs = logResult.results.map((logRow) => ({
|
|
600
|
+
instanceId: logRow.instanceId,
|
|
601
|
+
stepName: logRow.stepName,
|
|
602
|
+
log: logRow.log,
|
|
603
|
+
timestamp: logRow.timestamp,
|
|
604
|
+
type: logRow.type
|
|
605
|
+
}));
|
|
606
|
+
return step;
|
|
607
|
+
};
|
|
608
|
+
const getPropertiesKeys = async (instanceId) => {
|
|
609
|
+
const result = await context.D1.prepare(
|
|
610
|
+
/* sql */
|
|
611
|
+
`
|
|
612
|
+
SELECT DISTINCT key, valueType FROM PropertiesTable
|
|
613
|
+
WHERE tenantId = ? AND (? IS NULL OR instanceId = ?)
|
|
614
|
+
`
|
|
615
|
+
).bind(context.tenantId, instanceId ?? null, instanceId ?? null).all();
|
|
616
|
+
if (!result.results) return [];
|
|
617
|
+
return result.results.map((row) => ({
|
|
618
|
+
key: row.key,
|
|
619
|
+
valueType: row.valueType
|
|
620
|
+
}));
|
|
621
|
+
};
|
|
622
|
+
return {
|
|
623
|
+
listSteps,
|
|
624
|
+
getStep,
|
|
625
|
+
listWorkflows,
|
|
626
|
+
getWorkflow,
|
|
627
|
+
getWorkflowTypesByTenantId,
|
|
628
|
+
getPropertiesKeys
|
|
629
|
+
};
|
|
630
|
+
};
|
|
6
631
|
|
|
7
632
|
//#endregion
|
|
8
|
-
export {
|
|
633
|
+
export { createLogAccessor, createQueueWorkflowContext, createStepContext, createWorkflowContext, defineWorkflow, ensureTables, finalizeWorkflowRecord, insertStepRecordFull, insertWorkflowRecord, pushLogToDB, tryDeserializeObj, updateWorkflowName, upsertWorkflowProperty, workflowTableRowToWorkflowRun };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brandboostinggmbh/observable-workflows",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "My awesome typescript library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@cloudflare/workers-types": "^4.20250515.0",
|
|
30
31
|
"@sxzz/eslint-config": "^7.0.1",
|
|
31
32
|
"@sxzz/prettier-config": "^2.2.1",
|
|
32
33
|
"@types/node": "^22.15.17",
|