@rigkit/engine 0.2.2 → 0.2.4
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/authoring.ts +108 -61
- package/src/authoring.typecheck.ts +87 -0
- package/src/db/schema/core.ts +3 -7
- package/src/engine.test.ts +270 -131
- package/src/engine.ts +566 -503
- package/src/host-storage.ts +128 -0
- package/src/index.ts +5 -0
- package/src/provider/types.ts +4 -30
- package/src/state.ts +46 -14
- package/src/types.ts +347 -140
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/authoring.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RESERVED_WORKFLOW_OPERATION_IDS } from "./types.ts";
|
|
1
2
|
import type {
|
|
2
3
|
EnvResolver,
|
|
3
4
|
RigkitConfigDefinition,
|
|
@@ -5,14 +6,14 @@ import type {
|
|
|
5
6
|
OutputSchema,
|
|
6
7
|
OutputSchemaValue,
|
|
7
8
|
WorkflowDefinition,
|
|
8
|
-
WorkflowCreateDefinition,
|
|
9
|
-
WorkflowCreateHandler,
|
|
10
9
|
WorkflowInputFieldDefinition,
|
|
11
10
|
WorkflowInputShape,
|
|
12
11
|
WorkflowOperationDefinition,
|
|
13
12
|
WorkflowOperationInputBuilder,
|
|
14
13
|
WorkflowOperationInputHelpers,
|
|
15
14
|
WorkflowOperationOptions,
|
|
15
|
+
WorkflowWorkspaceOperationDefinition,
|
|
16
|
+
WorkflowWorkspaceOperationOptions,
|
|
16
17
|
WorkflowNodeDefinition,
|
|
17
18
|
WorkflowProviderDefinition,
|
|
18
19
|
WorkflowProviderMap,
|
|
@@ -24,8 +25,8 @@ import type {
|
|
|
24
25
|
WorkflowWorkspaceDefinition,
|
|
25
26
|
} from "./types.ts";
|
|
26
27
|
|
|
27
|
-
const reservedTaskContextKeys = new Set(["ctx", "runtime", "providers"]);
|
|
28
|
-
const reservedHostOperationIds = new Set(
|
|
28
|
+
const reservedTaskContextKeys = new Set(["ctx", "runtime", "providers", "step"]);
|
|
29
|
+
const reservedHostOperationIds = new Set<string>(RESERVED_WORKFLOW_OPERATION_IDS);
|
|
29
30
|
|
|
30
31
|
const readEnv = (name: string, fallback?: string): string => {
|
|
31
32
|
const value = process.env[name];
|
|
@@ -50,9 +51,17 @@ export function workflow<const Name extends string, const Providers extends Work
|
|
|
50
51
|
providers: options.providers,
|
|
51
52
|
sequence: <InputContext extends JsonObject = {}>(sequenceName: string) =>
|
|
52
53
|
createSequence(app as unknown as WorkflowDefinition<string, Providers>, sequenceName, []),
|
|
53
|
-
task: (
|
|
54
|
+
task: (
|
|
55
|
+
taskName: string,
|
|
56
|
+
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, {}, never, any>,
|
|
57
|
+
maybeHandler?: WorkflowTaskHandler<Providers, {}, never, any>,
|
|
58
|
+
) =>
|
|
54
59
|
createTask(app as unknown as WorkflowDefinition<string, Providers>, taskName, optionsOrHandler as any, maybeHandler as any),
|
|
55
|
-
step: (
|
|
60
|
+
step: (
|
|
61
|
+
taskName: string,
|
|
62
|
+
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, {}, never, any>,
|
|
63
|
+
maybeHandler?: WorkflowTaskHandler<Providers, {}, never, any>,
|
|
64
|
+
) =>
|
|
56
65
|
createTask(app as unknown as WorkflowDefinition<string, Providers>, taskName, optionsOrHandler as any, maybeHandler as any),
|
|
57
66
|
};
|
|
58
67
|
|
|
@@ -90,12 +99,11 @@ export function defineProvider<
|
|
|
90
99
|
const ProviderId extends string,
|
|
91
100
|
const Config extends object,
|
|
92
101
|
Runtime = unknown,
|
|
93
|
-
WorkspaceContext extends object = object,
|
|
94
102
|
>(
|
|
95
103
|
providerId: ProviderId,
|
|
96
|
-
config: WorkflowProviderDefinition<ProviderId, Config, Runtime
|
|
104
|
+
config: WorkflowProviderDefinition<ProviderId, Config, Runtime>["config"],
|
|
97
105
|
plugin?: unknown,
|
|
98
|
-
): WorkflowProviderDefinition<ProviderId, Config, Runtime
|
|
106
|
+
): WorkflowProviderDefinition<ProviderId, Config, Runtime> {
|
|
99
107
|
return {
|
|
100
108
|
kind: "rigkit.provider",
|
|
101
109
|
providerId,
|
|
@@ -120,14 +128,30 @@ export function isProviderDefinition(value: unknown): value is WorkflowProviderD
|
|
|
120
128
|
return Boolean(value && typeof value === "object" && getKind(value) === "rigkit.provider");
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
function createSequence<
|
|
131
|
+
function createSequence<
|
|
132
|
+
Providers extends WorkflowProviderMap,
|
|
133
|
+
InputContext extends JsonObject,
|
|
134
|
+
OutputContext extends JsonObject,
|
|
135
|
+
WorkspaceData extends object = JsonObject,
|
|
136
|
+
OperationIds extends string = never,
|
|
137
|
+
WorkspaceOperationIds extends string = never,
|
|
138
|
+
PreviousTaskIds extends string = never,
|
|
139
|
+
>(
|
|
124
140
|
app: WorkflowDefinition<string, Providers>,
|
|
125
141
|
name: string,
|
|
126
142
|
children: readonly WorkflowNodeDefinition<Providers, any, any>[],
|
|
127
|
-
workspace?: WorkflowWorkspaceDefinition<Providers, OutputContext>,
|
|
128
|
-
create?: WorkflowCreateDefinition<Providers, OutputContext>,
|
|
143
|
+
workspace?: WorkflowWorkspaceDefinition<Providers, OutputContext, any>,
|
|
129
144
|
operations: readonly WorkflowOperationDefinition<Providers, any>[] = [],
|
|
130
|
-
|
|
145
|
+
workspaceOperations: readonly WorkflowWorkspaceOperationDefinition<Providers, OutputContext, WorkspaceData, any>[] = [],
|
|
146
|
+
): WorkflowSequenceBuilder<
|
|
147
|
+
Providers,
|
|
148
|
+
InputContext,
|
|
149
|
+
OutputContext,
|
|
150
|
+
WorkspaceData,
|
|
151
|
+
OperationIds,
|
|
152
|
+
WorkspaceOperationIds,
|
|
153
|
+
PreviousTaskIds
|
|
154
|
+
> {
|
|
131
155
|
const node = {
|
|
132
156
|
kind: "rigkit.workflow-node" as const,
|
|
133
157
|
nodeKind: "sequence" as const,
|
|
@@ -135,27 +159,27 @@ function createSequence<Providers extends WorkflowProviderMap, InputContext exte
|
|
|
135
159
|
workflow: app,
|
|
136
160
|
children,
|
|
137
161
|
workspaceDefinition: workspace,
|
|
138
|
-
createDefinition: create,
|
|
139
162
|
operations,
|
|
163
|
+
workspaceOperations,
|
|
140
164
|
task: (
|
|
141
165
|
taskName: string,
|
|
142
|
-
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, OutputContext, any>,
|
|
143
|
-
maybeHandler?: WorkflowTaskHandler<Providers, OutputContext, any>,
|
|
166
|
+
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, OutputContext, PreviousTaskIds, any>,
|
|
167
|
+
maybeHandler?: WorkflowTaskHandler<Providers, OutputContext, PreviousTaskIds, any>,
|
|
144
168
|
) => {
|
|
145
169
|
const task = createTask(app, taskName, optionsOrHandler as any, maybeHandler as any);
|
|
146
|
-
return createSequence(app, name, [...children, task], workspace,
|
|
170
|
+
return createSequence(app, name, [...children, task], workspace, operations, workspaceOperations);
|
|
147
171
|
},
|
|
148
172
|
step: (
|
|
149
173
|
taskName: string,
|
|
150
|
-
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, OutputContext, any>,
|
|
151
|
-
maybeHandler?: WorkflowTaskHandler<Providers, OutputContext, any>,
|
|
174
|
+
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, OutputContext, PreviousTaskIds, any>,
|
|
175
|
+
maybeHandler?: WorkflowTaskHandler<Providers, OutputContext, PreviousTaskIds, any>,
|
|
152
176
|
) => {
|
|
153
177
|
const task = createTask(app, taskName, optionsOrHandler as any, maybeHandler as any);
|
|
154
|
-
return createSequence(app, name, [...children, task], workspace,
|
|
178
|
+
return createSequence(app, name, [...children, task], workspace, operations, workspaceOperations);
|
|
155
179
|
},
|
|
156
180
|
add: (child: WorkflowNodeDefinition<Providers, any, any>) => {
|
|
157
181
|
assertSameWorkflow(app, child);
|
|
158
|
-
return createSequence(app, name, [...children, child], workspace,
|
|
182
|
+
return createSequence(app, name, [...children, child], workspace, operations, workspaceOperations);
|
|
159
183
|
},
|
|
160
184
|
parallel: (branches: Record<string, WorkflowNodeDefinition<Providers, any, any>>) => {
|
|
161
185
|
for (const [branchName, branch] of Object.entries(branches)) {
|
|
@@ -173,17 +197,40 @@ function createSequence<Providers extends WorkflowProviderMap, InputContext exte
|
|
|
173
197
|
workflow: app,
|
|
174
198
|
branches,
|
|
175
199
|
};
|
|
176
|
-
return createSequence(app, name, [...children, parallelNode], workspace,
|
|
200
|
+
return createSequence(app, name, [...children, parallelNode], workspace, operations, workspaceOperations);
|
|
201
|
+
},
|
|
202
|
+
workspace: (definition: WorkflowWorkspaceDefinition<Providers, OutputContext, any>) =>
|
|
203
|
+
createSequence(app, name, children, definition, operations, workspaceOperations),
|
|
204
|
+
operation: (id: string, options: WorkflowOperationOptions<Providers, any>) => {
|
|
205
|
+
const operation = createOperation(id, options);
|
|
206
|
+
assertUniqueOperationId(operations, operation.id, "Operation");
|
|
207
|
+
return createSequence(app, name, children, workspace, [...operations, operation], workspaceOperations);
|
|
208
|
+
},
|
|
209
|
+
workspaceOperation: (id: string, options: WorkflowWorkspaceOperationOptions<Providers, OutputContext, any, any>) => {
|
|
210
|
+
const operation = createWorkspaceOperation(id, options);
|
|
211
|
+
assertUniqueOperationId(workspaceOperations, operation.id, "Workspace operation");
|
|
212
|
+
return createSequence(app, name, children, workspace, operations, [...workspaceOperations, operation]);
|
|
177
213
|
},
|
|
178
|
-
workspace: (definition: WorkflowWorkspaceDefinition<Providers, OutputContext>) =>
|
|
179
|
-
createSequence(app, name, children, definition, create, operations),
|
|
180
|
-
create: (handler: WorkflowCreateHandler<Providers, OutputContext, any>) =>
|
|
181
|
-
createSequence(app, name, children, workspace, { handler }, operations),
|
|
182
|
-
operation: (id: string, options: WorkflowOperationOptions<Providers, any>) =>
|
|
183
|
-
createSequence(app, name, children, workspace, create, [...operations, createOperation(id, options)]),
|
|
184
214
|
};
|
|
185
215
|
|
|
186
|
-
return node as unknown as WorkflowSequenceBuilder<
|
|
216
|
+
return node as unknown as WorkflowSequenceBuilder<
|
|
217
|
+
Providers,
|
|
218
|
+
InputContext,
|
|
219
|
+
OutputContext,
|
|
220
|
+
WorkspaceData,
|
|
221
|
+
OperationIds,
|
|
222
|
+
WorkspaceOperationIds,
|
|
223
|
+
PreviousTaskIds
|
|
224
|
+
>;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function assertUniqueOperationId(
|
|
228
|
+
operations: readonly { id: string }[],
|
|
229
|
+
id: string,
|
|
230
|
+
label: string,
|
|
231
|
+
): void {
|
|
232
|
+
if (!operations.some((operation) => operation.id === id)) return;
|
|
233
|
+
throw new Error(`${label} id ${id} is already defined`);
|
|
187
234
|
}
|
|
188
235
|
|
|
189
236
|
function createOperation<Providers extends WorkflowProviderMap, Input extends object>(
|
|
@@ -199,9 +246,6 @@ function createOperation<Providers extends WorkflowProviderMap, Input extends ob
|
|
|
199
246
|
id: normalized,
|
|
200
247
|
title: options.title,
|
|
201
248
|
description: options.description,
|
|
202
|
-
createsWorkspace: options.createsWorkspace,
|
|
203
|
-
requiredHostMethods: normalizeHostMethodRequirements(options.requiredHostMethods),
|
|
204
|
-
requiredHostCapabilities: normalizeHostCapabilityRequirements(options.requiredHostCapabilities),
|
|
205
249
|
input: typeof options.input === "function"
|
|
206
250
|
? options.input(createOperationInputHelpers())
|
|
207
251
|
: options.input,
|
|
@@ -209,31 +253,30 @@ function createOperation<Providers extends WorkflowProviderMap, Input extends ob
|
|
|
209
253
|
};
|
|
210
254
|
}
|
|
211
255
|
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
});
|
|
256
|
+
function createWorkspaceOperation<
|
|
257
|
+
Providers extends WorkflowProviderMap,
|
|
258
|
+
Context extends JsonObject,
|
|
259
|
+
Data extends JsonObject,
|
|
260
|
+
Input extends object,
|
|
261
|
+
>(
|
|
262
|
+
id: string,
|
|
263
|
+
options: WorkflowWorkspaceOperationOptions<Providers, Context, Data, Input>,
|
|
264
|
+
): WorkflowWorkspaceOperationDefinition<Providers, Context, Data, Input> {
|
|
265
|
+
const normalized = id.trim();
|
|
266
|
+
if (!normalized) throw new Error(`Workspace operation ids must be non-empty`);
|
|
267
|
+
if (normalized.includes("/")) throw new Error(`Workspace operation ids cannot contain "/"`);
|
|
268
|
+
if (reservedHostOperationIds.has(normalized)) {
|
|
269
|
+
throw new Error(`Workspace operation id ${normalized} is reserved by the Rigkit host`);
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
id: normalized,
|
|
273
|
+
title: options.title,
|
|
274
|
+
description: options.description,
|
|
275
|
+
input: typeof options.input === "function"
|
|
276
|
+
? options.input(createOperationInputHelpers())
|
|
277
|
+
: options.input,
|
|
278
|
+
run: options.run,
|
|
279
|
+
};
|
|
237
280
|
}
|
|
238
281
|
|
|
239
282
|
function createOperationInputHelpers(): WorkflowOperationInputHelpers {
|
|
@@ -287,15 +330,19 @@ function createOperationInputBuilder<Input extends object>(
|
|
|
287
330
|
} as WorkflowOperationInputBuilder<Input>;
|
|
288
331
|
}
|
|
289
332
|
|
|
290
|
-
function createTask<
|
|
333
|
+
function createTask<
|
|
334
|
+
Providers extends WorkflowProviderMap,
|
|
335
|
+
InputContext extends JsonObject,
|
|
336
|
+
PreviousTaskIds extends string = string,
|
|
337
|
+
>(
|
|
291
338
|
app: WorkflowDefinition<string, Providers>,
|
|
292
339
|
name: string,
|
|
293
|
-
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, InputContext, any>,
|
|
294
|
-
maybeHandler?: WorkflowTaskHandler<Providers, InputContext, any>,
|
|
340
|
+
optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, InputContext, PreviousTaskIds, any>,
|
|
341
|
+
maybeHandler?: WorkflowTaskHandler<Providers, InputContext, PreviousTaskIds, any>,
|
|
295
342
|
): WorkflowTaskNode<Providers, InputContext, any> {
|
|
296
343
|
const options = typeof optionsOrHandler === "function" ? undefined : optionsOrHandler;
|
|
297
344
|
const handler = (typeof optionsOrHandler === "function" ? optionsOrHandler : maybeHandler) as
|
|
298
|
-
| WorkflowTaskHandler<Providers, InputContext, any>
|
|
345
|
+
| WorkflowTaskHandler<Providers, InputContext, PreviousTaskIds, any>
|
|
299
346
|
| undefined;
|
|
300
347
|
if (!handler) throw new Error(`Task ${name} is missing a handler`);
|
|
301
348
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { sequence } from "./authoring.ts";
|
|
2
|
+
|
|
3
|
+
sequence("normal-operation-ids")
|
|
4
|
+
.operation("open" as const, {
|
|
5
|
+
run: async () => null,
|
|
6
|
+
})
|
|
7
|
+
// @ts-expect-error duplicate operation ids are rejected for literal ids
|
|
8
|
+
.operation("open" as const, {
|
|
9
|
+
run: async () => null,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
sequence("workspace-operation-ids")
|
|
13
|
+
.step("prepare", async () => ({ ctx: { snapshotId: "snap-1" } }))
|
|
14
|
+
.workspace({
|
|
15
|
+
create: async ({ workflow, workspace }) => {
|
|
16
|
+
const snapshotId: string = workflow.ctx.snapshotId;
|
|
17
|
+
const name: string = workspace.name;
|
|
18
|
+
void snapshotId;
|
|
19
|
+
void name;
|
|
20
|
+
return { vmId: "vm-1", test: "ok" };
|
|
21
|
+
},
|
|
22
|
+
remove: async ({ workspace }) => {
|
|
23
|
+
const vmId: string = workspace.ctx.vmId;
|
|
24
|
+
const test: string = workspace.ctx.test;
|
|
25
|
+
void vmId;
|
|
26
|
+
void test;
|
|
27
|
+
// @ts-expect-error workspace context is read-only
|
|
28
|
+
workspace.ctx.vmId = "next";
|
|
29
|
+
// @ts-expect-error provider resources are not part of the workspace authoring API
|
|
30
|
+
workspace.resources;
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
.workspaceOperation("open-cmux" as const, {
|
|
34
|
+
run: async ({ workspace }) => {
|
|
35
|
+
const vmId: string = workspace.ctx.vmId;
|
|
36
|
+
void vmId;
|
|
37
|
+
// @ts-expect-error missing data properties are rejected
|
|
38
|
+
workspace.ctx.missing;
|
|
39
|
+
return null;
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
// @ts-expect-error duplicate workspace operation ids are rejected for literal ids
|
|
43
|
+
.workspaceOperation("open-cmux" as const, {
|
|
44
|
+
run: async () => null,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
sequence("typed-step-invalidation")
|
|
48
|
+
.step("github-auth", async () => ({ ctx: { token: "ok" } }))
|
|
49
|
+
.step("check-auth", async ({ step }) => {
|
|
50
|
+
const token: string = step.ctx.token;
|
|
51
|
+
void token;
|
|
52
|
+
return step.invalidate("github-auth");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
sequence("typed-step-invalidation-targets")
|
|
56
|
+
.step("github-auth", async () => ({ ctx: { token: "ok" } }))
|
|
57
|
+
.step("check-auth", async ({ step }) => {
|
|
58
|
+
// @ts-expect-error invalidation target must be a previous task id
|
|
59
|
+
return step.invalidate("missing-auth");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
sequence("duplicate-step-id")
|
|
63
|
+
.step("prepare" as const, async () => ({ ctx: { snapshotId: "snap-1" } }))
|
|
64
|
+
// @ts-expect-error duplicate task ids are rejected for literal ids
|
|
65
|
+
.step("prepare" as const, async ({ step }) => ({ ctx: step.ctx }));
|
|
66
|
+
|
|
67
|
+
sequence("reserved-operation-id")
|
|
68
|
+
// @ts-expect-error reserved operation ids are rejected for literal ids
|
|
69
|
+
.operation("create" as const, {
|
|
70
|
+
run: async () => null,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
sequence("reserved-workspace-operation-id")
|
|
74
|
+
.workspace({
|
|
75
|
+
create: async () => ({}),
|
|
76
|
+
remove: async () => {},
|
|
77
|
+
})
|
|
78
|
+
// @ts-expect-error reserved workspace operation ids are rejected for literal ids
|
|
79
|
+
.workspaceOperation("remove" as const, {
|
|
80
|
+
run: async () => null,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
sequence("slash-operation-id")
|
|
84
|
+
// @ts-expect-error operation ids cannot contain slashes
|
|
85
|
+
.operation("workspace/open" as const, {
|
|
86
|
+
run: async () => null,
|
|
87
|
+
});
|
package/src/db/schema/core.ts
CHANGED
|
@@ -6,19 +6,15 @@ export const workspaces = sqliteTable(
|
|
|
6
6
|
{
|
|
7
7
|
id: text("id").primaryKey(),
|
|
8
8
|
name: text("name").notNull(),
|
|
9
|
-
providerId: text("provider_id").notNull(),
|
|
10
9
|
workflow: text("workflow").notNull(),
|
|
11
|
-
|
|
12
|
-
snapshotId: text("snapshot_id"),
|
|
13
|
-
sourceRef: text("source_ref_json", { mode: "json" }).$type<JsonValue>().notNull(),
|
|
14
|
-
context: text("context_json", { mode: "json" }).$type<Record<string, JsonValue>>().notNull(),
|
|
10
|
+
workflowCtx: text("workflow_ctx_json", { mode: "json" }).$type<Record<string, JsonValue>>().notNull(),
|
|
15
11
|
createdAt: text("created_at").notNull(),
|
|
16
12
|
updatedAt: text("updated_at").notNull(),
|
|
17
|
-
|
|
13
|
+
ctx: text("ctx_json", { mode: "json" }).$type<Record<string, JsonValue>>().notNull(),
|
|
18
14
|
},
|
|
19
15
|
(table) => [
|
|
20
16
|
uniqueIndex("workspaces_name_idx").on(table.name),
|
|
21
|
-
index("
|
|
17
|
+
index("workspaces_workflow_idx").on(table.workflow),
|
|
22
18
|
],
|
|
23
19
|
);
|
|
24
20
|
|