@rigkit/engine 0.0.0-canary-20260518T014918-c5bc0c2
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/README.md +5 -0
- package/package.json +34 -0
- package/src/authoring.ts +530 -0
- package/src/authoring.typecheck.ts +114 -0
- package/src/console-intercept.test.ts +121 -0
- package/src/console-intercept.ts +75 -0
- package/src/db/index.ts +157 -0
- package/src/db/schema/core.ts +71 -0
- package/src/db/schema/index.ts +7 -0
- package/src/engine.test.ts +1244 -0
- package/src/engine.ts +2604 -0
- package/src/env-file.ts +52 -0
- package/src/hash.ts +21 -0
- package/src/host-storage.ts +128 -0
- package/src/index.ts +46 -0
- package/src/provider/types.ts +113 -0
- package/src/state.ts +386 -0
- package/src/types.ts +873 -0
- package/src/version.ts +1 -0
package/src/state.ts
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { and, asc, desc, eq } from "drizzle-orm";
|
|
3
|
+
import type { ProviderStorage, ProviderStorageRecord } from "./provider/types.ts";
|
|
4
|
+
import type { JsonValue, WorkspaceRecord } from "./types.ts";
|
|
5
|
+
import {
|
|
6
|
+
createRigkitDatabase,
|
|
7
|
+
syncRigkitDatabaseSchema,
|
|
8
|
+
type RigkitDatabase,
|
|
9
|
+
type SchemaSyncResult,
|
|
10
|
+
} from "./db/index.ts";
|
|
11
|
+
import { coreSchema, type CoreSchema } from "./db/schema/index.ts";
|
|
12
|
+
import { providerState, runtimeMetadata, workflowNodeRuns, workspaces } from "./db/schema/index.ts";
|
|
13
|
+
import { stableJson } from "./hash.ts";
|
|
14
|
+
import { RIGKIT_ENGINE_VERSION } from "./version.ts";
|
|
15
|
+
|
|
16
|
+
export type WorkflowNodeRunRecord = {
|
|
17
|
+
id: string;
|
|
18
|
+
workflow: string;
|
|
19
|
+
nodePath: string;
|
|
20
|
+
nodeName: string;
|
|
21
|
+
nodeKind: string;
|
|
22
|
+
nodeKey: string;
|
|
23
|
+
providerFingerprint: string;
|
|
24
|
+
upstreamRunIds: string[];
|
|
25
|
+
output: Record<string, JsonValue>;
|
|
26
|
+
artifacts: JsonValue[];
|
|
27
|
+
invalidated: boolean;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
metadata: Record<string, JsonValue>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type SnapshotRecord = WorkflowNodeRunRecord;
|
|
33
|
+
|
|
34
|
+
export type StateServiceOptions = {
|
|
35
|
+
projectDir: string;
|
|
36
|
+
statePath?: string;
|
|
37
|
+
projectId?: string;
|
|
38
|
+
configPath?: string;
|
|
39
|
+
runtimeVersion?: string;
|
|
40
|
+
source?: JsonValue;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type StateServiceFactory = (options: StateServiceOptions) => StateService;
|
|
44
|
+
|
|
45
|
+
export interface StateService {
|
|
46
|
+
readonly path: string;
|
|
47
|
+
syncSchema(): Promise<SchemaSyncResult>;
|
|
48
|
+
listWorkspaces(): WorkspaceRecord[];
|
|
49
|
+
findWorkspace(nameOrResourceId: string): WorkspaceRecord | undefined;
|
|
50
|
+
getWorkspace(name: string): WorkspaceRecord | undefined;
|
|
51
|
+
saveWorkspace(workspace: WorkspaceRecord): void;
|
|
52
|
+
deleteWorkspace(name: string): void;
|
|
53
|
+
listNodeRuns(): WorkflowNodeRunRecord[];
|
|
54
|
+
listSnapshots(): SnapshotRecord[];
|
|
55
|
+
findReusableNodeRun(input: {
|
|
56
|
+
workflow: string;
|
|
57
|
+
nodePath: string;
|
|
58
|
+
nodeKey: string;
|
|
59
|
+
providerFingerprint: string;
|
|
60
|
+
upstreamRunIds: readonly string[];
|
|
61
|
+
}): WorkflowNodeRunRecord | undefined;
|
|
62
|
+
saveNodeRun(run: WorkflowNodeRunRecord): void;
|
|
63
|
+
clearNodeRuns(input?: {
|
|
64
|
+
workflow?: string;
|
|
65
|
+
nodePaths?: readonly string[];
|
|
66
|
+
}): number;
|
|
67
|
+
deleteNodeRunsById(ids: readonly string[]): number;
|
|
68
|
+
invalidateNodeRuns(input: {
|
|
69
|
+
workflow: string;
|
|
70
|
+
nodePaths: readonly string[];
|
|
71
|
+
}): string[];
|
|
72
|
+
providerStorage(providerId: string): ProviderStorage;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class StateStore implements StateService {
|
|
76
|
+
readonly path: string;
|
|
77
|
+
readonly db: RigkitDatabase<CoreSchema>;
|
|
78
|
+
private readonly schema = coreSchema;
|
|
79
|
+
private readonly projectDir: string;
|
|
80
|
+
private readonly metadata: Omit<StateServiceOptions, "projectDir" | "statePath">;
|
|
81
|
+
private schemaSync?: Promise<SchemaSyncResult>;
|
|
82
|
+
|
|
83
|
+
constructor(projectDir: string, options: Omit<StateServiceOptions, "projectDir"> = {}) {
|
|
84
|
+
this.projectDir = projectDir;
|
|
85
|
+
this.path = options.statePath ?? join(projectDir, ".rigkit", "state.sqlite");
|
|
86
|
+
this.metadata = {
|
|
87
|
+
projectId: options.projectId,
|
|
88
|
+
configPath: options.configPath,
|
|
89
|
+
runtimeVersion: options.runtimeVersion,
|
|
90
|
+
source: options.source,
|
|
91
|
+
};
|
|
92
|
+
this.db = createRigkitDatabase(this.path, { schema: this.schema });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async syncSchema(): Promise<SchemaSyncResult> {
|
|
96
|
+
this.schemaSync ??= this.syncSchemaOnce();
|
|
97
|
+
return await this.schemaSync;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
listWorkspaces(): WorkspaceRecord[] {
|
|
101
|
+
return this.db.select().from(workspaces).orderBy(asc(workspaces.name)).all().map(toWorkspaceRecord);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
findWorkspace(nameOrResourceId: string): WorkspaceRecord | undefined {
|
|
105
|
+
const row = this.db
|
|
106
|
+
.select()
|
|
107
|
+
.from(workspaces)
|
|
108
|
+
.where(eq(workspaces.name, nameOrResourceId))
|
|
109
|
+
.get();
|
|
110
|
+
return row ? toWorkspaceRecord(row) : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getWorkspace(name: string): WorkspaceRecord | undefined {
|
|
114
|
+
const row = this.db.select().from(workspaces).where(eq(workspaces.name, name)).get();
|
|
115
|
+
return row ? toWorkspaceRecord(row) : undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
saveWorkspace(workspace: WorkspaceRecord): void {
|
|
119
|
+
this.db
|
|
120
|
+
.insert(workspaces)
|
|
121
|
+
.values(workspace)
|
|
122
|
+
.onConflictDoUpdate({
|
|
123
|
+
target: workspaces.name,
|
|
124
|
+
set: {
|
|
125
|
+
id: workspace.id,
|
|
126
|
+
workflow: workspace.workflow,
|
|
127
|
+
workflowCtx: workspace.workflowCtx,
|
|
128
|
+
updatedAt: workspace.updatedAt,
|
|
129
|
+
ctx: workspace.ctx,
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
.run();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
deleteWorkspace(name: string): void {
|
|
136
|
+
this.db.delete(workspaces).where(eq(workspaces.name, name)).run();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
listNodeRuns(): WorkflowNodeRunRecord[] {
|
|
140
|
+
return this.db
|
|
141
|
+
.select()
|
|
142
|
+
.from(workflowNodeRuns)
|
|
143
|
+
.orderBy(desc(workflowNodeRuns.createdAt))
|
|
144
|
+
.all()
|
|
145
|
+
.map(toNodeRunRecord);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
listSnapshots(): SnapshotRecord[] {
|
|
149
|
+
return this.listNodeRuns();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
findReusableNodeRun(input: {
|
|
153
|
+
workflow: string;
|
|
154
|
+
nodePath: string;
|
|
155
|
+
nodeKey: string;
|
|
156
|
+
providerFingerprint: string;
|
|
157
|
+
upstreamRunIds: readonly string[];
|
|
158
|
+
}): WorkflowNodeRunRecord | undefined {
|
|
159
|
+
const upstream = stableJson([...input.upstreamRunIds]);
|
|
160
|
+
const candidates = this.db
|
|
161
|
+
.select()
|
|
162
|
+
.from(workflowNodeRuns)
|
|
163
|
+
.where(eq(workflowNodeRuns.workflow, input.workflow))
|
|
164
|
+
.orderBy(desc(workflowNodeRuns.createdAt))
|
|
165
|
+
.all()
|
|
166
|
+
.filter((run) =>
|
|
167
|
+
!run.invalidated &&
|
|
168
|
+
run.nodePath === input.nodePath &&
|
|
169
|
+
run.nodeKey === input.nodeKey &&
|
|
170
|
+
run.providerFingerprint === input.providerFingerprint &&
|
|
171
|
+
stableJson(run.upstreamRunIds) === upstream
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const row = candidates[0];
|
|
175
|
+
return row ? toNodeRunRecord(row) : undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
saveNodeRun(run: WorkflowNodeRunRecord): void {
|
|
179
|
+
this.db.insert(workflowNodeRuns).values(run).run();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
clearNodeRuns(input: {
|
|
183
|
+
workflow?: string;
|
|
184
|
+
nodePaths?: readonly string[];
|
|
185
|
+
} = {}): number {
|
|
186
|
+
const nodePaths = input.nodePaths ? new Set(input.nodePaths) : undefined;
|
|
187
|
+
const rows = this.db
|
|
188
|
+
.select({ id: workflowNodeRuns.id, workflow: workflowNodeRuns.workflow, nodePath: workflowNodeRuns.nodePath })
|
|
189
|
+
.from(workflowNodeRuns)
|
|
190
|
+
.all()
|
|
191
|
+
.filter((row) =>
|
|
192
|
+
(input.workflow === undefined || row.workflow === input.workflow) &&
|
|
193
|
+
(nodePaths === undefined || nodePaths.has(row.nodePath))
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
for (const row of rows) {
|
|
197
|
+
this.db.delete(workflowNodeRuns).where(eq(workflowNodeRuns.id, row.id)).run();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return rows.length;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
deleteNodeRunsById(ids: readonly string[]): number {
|
|
204
|
+
if (ids.length === 0) return 0;
|
|
205
|
+
let deleted = 0;
|
|
206
|
+
for (const id of ids) {
|
|
207
|
+
this.db.delete(workflowNodeRuns).where(eq(workflowNodeRuns.id, id)).run();
|
|
208
|
+
deleted += 1;
|
|
209
|
+
}
|
|
210
|
+
return deleted;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
invalidateNodeRuns(input: {
|
|
214
|
+
workflow: string;
|
|
215
|
+
nodePaths: readonly string[];
|
|
216
|
+
}): string[] {
|
|
217
|
+
const targetPaths = new Set(input.nodePaths);
|
|
218
|
+
if (targetPaths.size === 0) return [];
|
|
219
|
+
|
|
220
|
+
const rows = this.db
|
|
221
|
+
.select()
|
|
222
|
+
.from(workflowNodeRuns)
|
|
223
|
+
.where(eq(workflowNodeRuns.workflow, input.workflow))
|
|
224
|
+
.orderBy(asc(workflowNodeRuns.createdAt))
|
|
225
|
+
.all()
|
|
226
|
+
.map(toNodeRunRecord);
|
|
227
|
+
|
|
228
|
+
const invalidatedIds = new Set<string>();
|
|
229
|
+
let changed = true;
|
|
230
|
+
while (changed) {
|
|
231
|
+
changed = false;
|
|
232
|
+
for (const row of rows) {
|
|
233
|
+
if (row.invalidated || invalidatedIds.has(row.id)) continue;
|
|
234
|
+
const isTarget = targetPaths.has(row.nodePath);
|
|
235
|
+
const dependsOnInvalidated = row.upstreamRunIds.some((id) => invalidatedIds.has(id));
|
|
236
|
+
if (!isTarget && !dependsOnInvalidated) continue;
|
|
237
|
+
invalidatedIds.add(row.id);
|
|
238
|
+
changed = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const id of invalidatedIds) {
|
|
243
|
+
this.db.update(workflowNodeRuns).set({ invalidated: true }).where(eq(workflowNodeRuns.id, id)).run();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return [...invalidatedIds];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
providerStorage(providerId: string): ProviderStorage {
|
|
250
|
+
return new StateProviderStorage(this.db, providerId);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private async syncSchemaOnce(): Promise<SchemaSyncResult> {
|
|
254
|
+
const result = await syncRigkitDatabaseSchema(this.db, this.schema);
|
|
255
|
+
this.writeRuntimeMetadata(result.schemaVersion);
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private writeRuntimeMetadata(schemaVersion: string): void {
|
|
260
|
+
const now = new Date().toISOString();
|
|
261
|
+
const entries: Array<[string, JsonValue]> = [
|
|
262
|
+
["engine.version", RIGKIT_ENGINE_VERSION],
|
|
263
|
+
["state.schemaVersion", schemaVersion],
|
|
264
|
+
["project.dir", this.projectDir],
|
|
265
|
+
["state.path", this.path],
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
if (this.metadata.projectId) entries.push(["project.id", this.metadata.projectId]);
|
|
269
|
+
if (this.metadata.configPath) entries.push(["config.path", this.metadata.configPath]);
|
|
270
|
+
if (this.metadata.runtimeVersion) entries.push(["runtime.version", this.metadata.runtimeVersion]);
|
|
271
|
+
if (this.metadata.source !== undefined) entries.push(["source", this.metadata.source]);
|
|
272
|
+
|
|
273
|
+
for (const [key, value] of entries) {
|
|
274
|
+
this.db
|
|
275
|
+
.insert(runtimeMetadata)
|
|
276
|
+
.values({ key, value, updatedAt: now })
|
|
277
|
+
.onConflictDoUpdate({
|
|
278
|
+
target: runtimeMetadata.key,
|
|
279
|
+
set: { value, updatedAt: now },
|
|
280
|
+
})
|
|
281
|
+
.run();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const createStateStore: StateServiceFactory = (options) =>
|
|
287
|
+
new StateStore(options.projectDir, options);
|
|
288
|
+
|
|
289
|
+
function toWorkspaceRecord(row: typeof workspaces.$inferSelect): WorkspaceRecord {
|
|
290
|
+
return {
|
|
291
|
+
id: row.id,
|
|
292
|
+
name: row.name,
|
|
293
|
+
workflow: row.workflow,
|
|
294
|
+
workflowCtx: row.workflowCtx,
|
|
295
|
+
createdAt: row.createdAt,
|
|
296
|
+
updatedAt: row.updatedAt,
|
|
297
|
+
ctx: row.ctx,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function toNodeRunRecord(row: typeof workflowNodeRuns.$inferSelect): WorkflowNodeRunRecord {
|
|
302
|
+
return {
|
|
303
|
+
id: row.id,
|
|
304
|
+
workflow: row.workflow,
|
|
305
|
+
nodePath: row.nodePath,
|
|
306
|
+
nodeName: row.nodeName,
|
|
307
|
+
nodeKind: row.nodeKind,
|
|
308
|
+
nodeKey: row.nodeKey,
|
|
309
|
+
providerFingerprint: row.providerFingerprint,
|
|
310
|
+
upstreamRunIds: row.upstreamRunIds,
|
|
311
|
+
output: row.output,
|
|
312
|
+
artifacts: row.artifacts,
|
|
313
|
+
invalidated: row.invalidated,
|
|
314
|
+
createdAt: row.createdAt,
|
|
315
|
+
metadata: row.metadata,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
class StateProviderStorage implements ProviderStorage {
|
|
320
|
+
constructor(
|
|
321
|
+
private readonly db: RigkitDatabase<CoreSchema>,
|
|
322
|
+
private readonly providerId: string,
|
|
323
|
+
) {}
|
|
324
|
+
|
|
325
|
+
get<Value extends JsonValue = JsonValue>(key: string): ProviderStorageRecord<Value> | undefined {
|
|
326
|
+
const row = this.db
|
|
327
|
+
.select()
|
|
328
|
+
.from(providerState)
|
|
329
|
+
.where(and(eq(providerState.providerId, this.providerId), eq(providerState.key, key)))
|
|
330
|
+
.get();
|
|
331
|
+
return row ? toProviderStorageRecord(row) as ProviderStorageRecord<Value> : undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
set<Value extends JsonValue = JsonValue>(key: string, value: Value): ProviderStorageRecord<Value> {
|
|
335
|
+
const now = new Date().toISOString();
|
|
336
|
+
const existing = this.get(key);
|
|
337
|
+
const record: ProviderStorageRecord<Value> = {
|
|
338
|
+
providerId: this.providerId,
|
|
339
|
+
key,
|
|
340
|
+
value,
|
|
341
|
+
createdAt: existing?.createdAt ?? now,
|
|
342
|
+
updatedAt: now,
|
|
343
|
+
};
|
|
344
|
+
this.db
|
|
345
|
+
.insert(providerState)
|
|
346
|
+
.values(record)
|
|
347
|
+
.onConflictDoUpdate({
|
|
348
|
+
target: [providerState.providerId, providerState.key],
|
|
349
|
+
set: {
|
|
350
|
+
value,
|
|
351
|
+
updatedAt: record.updatedAt,
|
|
352
|
+
},
|
|
353
|
+
})
|
|
354
|
+
.run();
|
|
355
|
+
return record;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
delete(key: string): void {
|
|
359
|
+
this.db
|
|
360
|
+
.delete(providerState)
|
|
361
|
+
.where(and(eq(providerState.providerId, this.providerId), eq(providerState.key, key)))
|
|
362
|
+
.run();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
entries(prefix = ""): ProviderStorageRecord[] {
|
|
366
|
+
const rows = this.db
|
|
367
|
+
.select()
|
|
368
|
+
.from(providerState)
|
|
369
|
+
.where(eq(providerState.providerId, this.providerId))
|
|
370
|
+
.orderBy(asc(providerState.key))
|
|
371
|
+
.all();
|
|
372
|
+
return rows
|
|
373
|
+
.filter((row) => row.key.startsWith(prefix))
|
|
374
|
+
.map(toProviderStorageRecord);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function toProviderStorageRecord(row: typeof providerState.$inferSelect): ProviderStorageRecord {
|
|
379
|
+
return {
|
|
380
|
+
providerId: row.providerId,
|
|
381
|
+
key: row.key,
|
|
382
|
+
value: row.value,
|
|
383
|
+
createdAt: row.createdAt,
|
|
384
|
+
updatedAt: row.updatedAt,
|
|
385
|
+
};
|
|
386
|
+
}
|