@rigkit/engine 0.2.1 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigkit/engine",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
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,
@@ -25,7 +26,7 @@ import type {
25
26
  } from "./types.ts";
26
27
 
27
28
  const reservedTaskContextKeys = new Set(["ctx", "runtime", "providers"]);
28
- const reservedHostOperationIds = new Set(["init", "doctor", "projects", "run", "ls", "help", "version", "completion"]);
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];
@@ -90,12 +91,11 @@ export function defineProvider<
90
91
  const ProviderId extends string,
91
92
  const Config extends object,
92
93
  Runtime = unknown,
93
- WorkspaceContext extends object = object,
94
94
  >(
95
95
  providerId: ProviderId,
96
- config: WorkflowProviderDefinition<ProviderId, Config, Runtime, WorkspaceContext>["config"],
96
+ config: WorkflowProviderDefinition<ProviderId, Config, Runtime>["config"],
97
97
  plugin?: unknown,
98
- ): WorkflowProviderDefinition<ProviderId, Config, Runtime, WorkspaceContext> {
98
+ ): WorkflowProviderDefinition<ProviderId, Config, Runtime> {
99
99
  return {
100
100
  kind: "rigkit.provider",
101
101
  providerId,
@@ -120,14 +120,28 @@ export function isProviderDefinition(value: unknown): value is WorkflowProviderD
120
120
  return Boolean(value && typeof value === "object" && getKind(value) === "rigkit.provider");
121
121
  }
122
122
 
123
- function createSequence<Providers extends WorkflowProviderMap, InputContext extends JsonObject, OutputContext extends JsonObject>(
123
+ function createSequence<
124
+ Providers extends WorkflowProviderMap,
125
+ InputContext extends JsonObject,
126
+ OutputContext extends JsonObject,
127
+ WorkspaceData extends object = JsonObject,
128
+ OperationIds extends string = never,
129
+ WorkspaceOperationIds extends string = never,
130
+ >(
124
131
  app: WorkflowDefinition<string, Providers>,
125
132
  name: string,
126
133
  children: readonly WorkflowNodeDefinition<Providers, any, any>[],
127
- workspace?: WorkflowWorkspaceDefinition<Providers, OutputContext>,
128
- create?: WorkflowCreateDefinition<Providers, OutputContext>,
134
+ workspace?: WorkflowWorkspaceDefinition<Providers, OutputContext, any>,
129
135
  operations: readonly WorkflowOperationDefinition<Providers, any>[] = [],
130
- ): WorkflowSequenceBuilder<Providers, InputContext, OutputContext> {
136
+ workspaceOperations: readonly WorkflowWorkspaceOperationDefinition<Providers, OutputContext, WorkspaceData, any>[] = [],
137
+ ): WorkflowSequenceBuilder<
138
+ Providers,
139
+ InputContext,
140
+ OutputContext,
141
+ WorkspaceData,
142
+ OperationIds,
143
+ WorkspaceOperationIds
144
+ > {
131
145
  const node = {
132
146
  kind: "rigkit.workflow-node" as const,
133
147
  nodeKind: "sequence" as const,
@@ -135,15 +149,15 @@ function createSequence<Providers extends WorkflowProviderMap, InputContext exte
135
149
  workflow: app,
136
150
  children,
137
151
  workspaceDefinition: workspace,
138
- createDefinition: create,
139
152
  operations,
153
+ workspaceOperations,
140
154
  task: (
141
155
  taskName: string,
142
156
  optionsOrHandler: WorkflowTaskOptions | WorkflowTaskHandler<Providers, OutputContext, any>,
143
157
  maybeHandler?: WorkflowTaskHandler<Providers, OutputContext, any>,
144
158
  ) => {
145
159
  const task = createTask(app, taskName, optionsOrHandler as any, maybeHandler as any);
146
- return createSequence(app, name, [...children, task], workspace, create, operations);
160
+ return createSequence(app, name, [...children, task], workspace, operations, workspaceOperations);
147
161
  },
148
162
  step: (
149
163
  taskName: string,
@@ -151,11 +165,11 @@ function createSequence<Providers extends WorkflowProviderMap, InputContext exte
151
165
  maybeHandler?: WorkflowTaskHandler<Providers, OutputContext, any>,
152
166
  ) => {
153
167
  const task = createTask(app, taskName, optionsOrHandler as any, maybeHandler as any);
154
- return createSequence(app, name, [...children, task], workspace, create, operations);
168
+ return createSequence(app, name, [...children, task], workspace, operations, workspaceOperations);
155
169
  },
156
170
  add: (child: WorkflowNodeDefinition<Providers, any, any>) => {
157
171
  assertSameWorkflow(app, child);
158
- return createSequence(app, name, [...children, child], workspace, create, operations);
172
+ return createSequence(app, name, [...children, child], workspace, operations, workspaceOperations);
159
173
  },
160
174
  parallel: (branches: Record<string, WorkflowNodeDefinition<Providers, any, any>>) => {
161
175
  for (const [branchName, branch] of Object.entries(branches)) {
@@ -173,17 +187,39 @@ function createSequence<Providers extends WorkflowProviderMap, InputContext exte
173
187
  workflow: app,
174
188
  branches,
175
189
  };
176
- return createSequence(app, name, [...children, parallelNode], workspace, create, operations);
190
+ return createSequence(app, name, [...children, parallelNode], workspace, operations, workspaceOperations);
191
+ },
192
+ workspace: (definition: WorkflowWorkspaceDefinition<Providers, OutputContext, any>) =>
193
+ createSequence(app, name, children, definition, operations, workspaceOperations),
194
+ operation: (id: string, options: WorkflowOperationOptions<Providers, any>) => {
195
+ const operation = createOperation(id, options);
196
+ assertUniqueOperationId(operations, operation.id, "Operation");
197
+ return createSequence(app, name, children, workspace, [...operations, operation], workspaceOperations);
198
+ },
199
+ workspaceOperation: (id: string, options: WorkflowWorkspaceOperationOptions<Providers, OutputContext, any, any>) => {
200
+ const operation = createWorkspaceOperation(id, options);
201
+ assertUniqueOperationId(workspaceOperations, operation.id, "Workspace operation");
202
+ return createSequence(app, name, children, workspace, operations, [...workspaceOperations, operation]);
177
203
  },
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
204
  };
185
205
 
186
- return node as unknown as WorkflowSequenceBuilder<Providers, InputContext, OutputContext>;
206
+ return node as unknown as WorkflowSequenceBuilder<
207
+ Providers,
208
+ InputContext,
209
+ OutputContext,
210
+ WorkspaceData,
211
+ OperationIds,
212
+ WorkspaceOperationIds
213
+ >;
214
+ }
215
+
216
+ function assertUniqueOperationId(
217
+ operations: readonly { id: string }[],
218
+ id: string,
219
+ label: string,
220
+ ): void {
221
+ if (!operations.some((operation) => operation.id === id)) return;
222
+ throw new Error(`${label} id ${id} is already defined`);
187
223
  }
188
224
 
189
225
  function createOperation<Providers extends WorkflowProviderMap, Input extends object>(
@@ -199,9 +235,6 @@ function createOperation<Providers extends WorkflowProviderMap, Input extends ob
199
235
  id: normalized,
200
236
  title: options.title,
201
237
  description: options.description,
202
- createsWorkspace: options.createsWorkspace,
203
- requiredHostMethods: normalizeHostMethodRequirements(options.requiredHostMethods),
204
- requiredHostCapabilities: normalizeHostCapabilityRequirements(options.requiredHostCapabilities),
205
238
  input: typeof options.input === "function"
206
239
  ? options.input(createOperationInputHelpers())
207
240
  : options.input,
@@ -209,31 +242,30 @@ function createOperation<Providers extends WorkflowProviderMap, Input extends ob
209
242
  };
210
243
  }
211
244
 
212
- function normalizeHostMethodRequirements(
213
- methods: WorkflowOperationOptions<any, any>["requiredHostMethods"],
214
- ) {
215
- return methods?.map((method) => {
216
- const id = method.id.trim();
217
- if (!id) throw new Error(`Host method requirements must have non-empty ids`);
218
- return {
219
- id,
220
- ...(method.modes?.length ? { modes: [...method.modes] } : {}),
221
- };
222
- });
223
- }
224
-
225
- function normalizeHostCapabilityRequirements(
226
- capabilities: WorkflowOperationOptions<any, any>["requiredHostCapabilities"],
227
- ) {
228
- return capabilities?.map((capability) => {
229
- const id = capability.id.trim();
230
- if (!id) throw new Error(`Host capability requirements must have non-empty ids`);
231
- const schemaHash = capability.schemaHash?.trim();
232
- return {
233
- id,
234
- ...(schemaHash ? { schemaHash } : {}),
235
- };
236
- });
245
+ function createWorkspaceOperation<
246
+ Providers extends WorkflowProviderMap,
247
+ Context extends JsonObject,
248
+ Data extends JsonObject,
249
+ Input extends object,
250
+ >(
251
+ id: string,
252
+ options: WorkflowWorkspaceOperationOptions<Providers, Context, Data, Input>,
253
+ ): WorkflowWorkspaceOperationDefinition<Providers, Context, Data, Input> {
254
+ const normalized = id.trim();
255
+ if (!normalized) throw new Error(`Workspace operation ids must be non-empty`);
256
+ if (normalized.includes("/")) throw new Error(`Workspace operation ids cannot contain "/"`);
257
+ if (reservedHostOperationIds.has(normalized)) {
258
+ throw new Error(`Workspace operation id ${normalized} is reserved by the Rigkit host`);
259
+ }
260
+ return {
261
+ id: normalized,
262
+ title: options.title,
263
+ description: options.description,
264
+ input: typeof options.input === "function"
265
+ ? options.input(createOperationInputHelpers())
266
+ : options.input,
267
+ run: options.run,
268
+ };
237
269
  }
238
270
 
239
271
  function createOperationInputHelpers(): WorkflowOperationInputHelpers {
@@ -0,0 +1,67 @@
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 () => ({ 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("reserved-operation-id")
48
+ // @ts-expect-error reserved operation ids are rejected for literal ids
49
+ .operation("create" as const, {
50
+ run: async () => null,
51
+ });
52
+
53
+ sequence("reserved-workspace-operation-id")
54
+ .workspace({
55
+ create: async () => ({}),
56
+ remove: async () => {},
57
+ })
58
+ // @ts-expect-error reserved workspace operation ids are rejected for literal ids
59
+ .workspaceOperation("remove" as const, {
60
+ run: async () => null,
61
+ });
62
+
63
+ sequence("slash-operation-id")
64
+ // @ts-expect-error operation ids cannot contain slashes
65
+ .operation("workspace/open" as const, {
66
+ run: async () => null,
67
+ });
@@ -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
- resourceId: text("resource_id").notNull(),
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
- metadata: text("metadata_json", { mode: "json" }).$type<Record<string, JsonValue>>().notNull(),
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("workspaces_provider_resource_idx").on(table.providerId, table.resourceId),
17
+ index("workspaces_workflow_idx").on(table.workflow),
22
18
  ],
23
19
  );
24
20