@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { hash } from "./hash.ts";
|
|
5
|
+
import type { ProviderStorage, ProviderStorageRecord } from "./provider/types.ts";
|
|
6
|
+
import type { JsonValue } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
export type ProviderHostStorageOptions = {
|
|
9
|
+
providerId: string;
|
|
10
|
+
rootDir?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ProviderHostStorageFactory = (options: ProviderHostStorageOptions) => ProviderStorage;
|
|
14
|
+
|
|
15
|
+
type ProviderHostStorageFile = {
|
|
16
|
+
providerId: string;
|
|
17
|
+
records: Record<string, Omit<ProviderStorageRecord, "providerId" | "key">>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function defaultProviderHostStorageDir(): string {
|
|
21
|
+
return process.env.RIGKIT_HOST_STORAGE_DIR ?? join(homedir(), ".rigkit", "providers");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createFileProviderHostStorage(options: ProviderHostStorageOptions): ProviderStorage {
|
|
25
|
+
return new FileProviderHostStorage(options.providerId, providerHostStoragePath(options));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function providerHostStoragePath(options: ProviderHostStorageOptions): string {
|
|
29
|
+
const rootDir = options.rootDir ?? defaultProviderHostStorageDir();
|
|
30
|
+
const slug = options.providerId.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "provider";
|
|
31
|
+
return join(rootDir, `${slug}-${hash(options.providerId).slice(0, 12)}.json`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class FileProviderHostStorage implements ProviderStorage {
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly providerId: string,
|
|
37
|
+
private readonly path: string,
|
|
38
|
+
) {}
|
|
39
|
+
|
|
40
|
+
get<Value extends JsonValue = JsonValue>(key: string): ProviderStorageRecord<Value> | undefined {
|
|
41
|
+
const file = this.read();
|
|
42
|
+
const record = file.records[key];
|
|
43
|
+
return record
|
|
44
|
+
? {
|
|
45
|
+
providerId: this.providerId,
|
|
46
|
+
key,
|
|
47
|
+
value: record.value as Value,
|
|
48
|
+
createdAt: record.createdAt,
|
|
49
|
+
updatedAt: record.updatedAt,
|
|
50
|
+
}
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set<Value extends JsonValue = JsonValue>(key: string, value: Value): ProviderStorageRecord<Value> {
|
|
55
|
+
const file = this.read();
|
|
56
|
+
const now = new Date().toISOString();
|
|
57
|
+
const existing = file.records[key];
|
|
58
|
+
const record: ProviderStorageRecord<Value> = {
|
|
59
|
+
providerId: this.providerId,
|
|
60
|
+
key,
|
|
61
|
+
value,
|
|
62
|
+
createdAt: existing?.createdAt ?? now,
|
|
63
|
+
updatedAt: now,
|
|
64
|
+
};
|
|
65
|
+
file.records[key] = {
|
|
66
|
+
value,
|
|
67
|
+
createdAt: record.createdAt,
|
|
68
|
+
updatedAt: record.updatedAt,
|
|
69
|
+
};
|
|
70
|
+
this.write(file);
|
|
71
|
+
return record;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
delete(key: string): void {
|
|
75
|
+
const file = this.read();
|
|
76
|
+
delete file.records[key];
|
|
77
|
+
this.write(file);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
entries(prefix = ""): ProviderStorageRecord[] {
|
|
81
|
+
const file = this.read();
|
|
82
|
+
return Object.entries(file.records)
|
|
83
|
+
.filter(([key]) => key.startsWith(prefix))
|
|
84
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
85
|
+
.map(([key, record]) => ({
|
|
86
|
+
providerId: this.providerId,
|
|
87
|
+
key,
|
|
88
|
+
value: record.value,
|
|
89
|
+
createdAt: record.createdAt,
|
|
90
|
+
updatedAt: record.updatedAt,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private read(): ProviderHostStorageFile {
|
|
95
|
+
if (!existsSync(this.path)) {
|
|
96
|
+
return { providerId: this.providerId, records: {} };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const parsed = JSON.parse(readFileSync(this.path, "utf8")) as unknown;
|
|
100
|
+
if (!isHostStorageFile(parsed, this.providerId)) {
|
|
101
|
+
throw new Error(`Invalid Rigkit provider host storage at ${this.path}`);
|
|
102
|
+
}
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private write(file: ProviderHostStorageFile): void {
|
|
107
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
108
|
+
writeFileSync(this.path, `${JSON.stringify(file, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
109
|
+
chmodSync(this.path, 0o600);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isHostStorageFile(value: unknown, providerId: string): value is ProviderHostStorageFile {
|
|
114
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
115
|
+
const record = value as { providerId?: unknown; records?: unknown };
|
|
116
|
+
if (record.providerId !== providerId) return false;
|
|
117
|
+
if (!record.records || typeof record.records !== "object" || Array.isArray(record.records)) return false;
|
|
118
|
+
return Object.values(record.records).every((entry) =>
|
|
119
|
+
Boolean(
|
|
120
|
+
entry &&
|
|
121
|
+
typeof entry === "object" &&
|
|
122
|
+
!Array.isArray(entry) &&
|
|
123
|
+
typeof (entry as { createdAt?: unknown }).createdAt === "string" &&
|
|
124
|
+
typeof (entry as { updatedAt?: unknown }).updatedAt === "string" &&
|
|
125
|
+
"value" in entry
|
|
126
|
+
)
|
|
127
|
+
);
|
|
128
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,10 @@ export type {
|
|
|
15
15
|
} from "./engine.ts";
|
|
16
16
|
export { createRigkitDatabase, RIGKIT_STATE_SCHEMA_VERSION, syncRigkitDatabaseSchema } from "./db/index.ts";
|
|
17
17
|
export { coreSchema } from "./db/schema/index.ts";
|
|
18
|
+
export {
|
|
19
|
+
createFileProviderHostStorage,
|
|
20
|
+
defaultProviderHostStorageDir,
|
|
21
|
+
} from "./host-storage.ts";
|
|
18
22
|
export { createStateStore } from "./state.ts";
|
|
19
23
|
export { RIGKIT_ENGINE_VERSION } from "./version.ts";
|
|
20
24
|
export {
|
|
@@ -30,5 +34,6 @@ export {
|
|
|
30
34
|
} from "./authoring.ts";
|
|
31
35
|
export type * from "./types.ts";
|
|
32
36
|
export type { RigkitDatabase, RigkitDatabaseSchema, SchemaSyncResult } from "./db/index.ts";
|
|
37
|
+
export type { ProviderHostStorageFactory, ProviderHostStorageOptions } from "./host-storage.ts";
|
|
33
38
|
export type * from "./provider/types.ts";
|
|
34
39
|
export type * from "./state.ts";
|
package/src/provider/types.ts
CHANGED
|
@@ -7,8 +7,6 @@ import type {
|
|
|
7
7
|
LoadedProviderDefinition,
|
|
8
8
|
LocalWorkspaceRuntime,
|
|
9
9
|
MaybePromise,
|
|
10
|
-
ProviderWorkspaceContext,
|
|
11
|
-
WorkspaceRecord,
|
|
12
10
|
} from "../types.ts";
|
|
13
11
|
|
|
14
12
|
export type VmHandle = {
|
|
@@ -33,9 +31,7 @@ export type SshConnection = {
|
|
|
33
31
|
command: string;
|
|
34
32
|
};
|
|
35
33
|
|
|
36
|
-
export interface BaseDevMachineProvider
|
|
37
|
-
WorkspaceContext extends ProviderWorkspaceContext = ProviderWorkspaceContext,
|
|
38
|
-
> {
|
|
34
|
+
export interface BaseDevMachineProvider {
|
|
39
35
|
readonly providerId: string;
|
|
40
36
|
createVm(): Promise<VmHandle>;
|
|
41
37
|
createVmFromSnapshot(input: { snapshotId: string }): Promise<VmHandle>;
|
|
@@ -44,7 +40,6 @@ export interface BaseDevMachineProvider<
|
|
|
44
40
|
writeFile(vm: VmHandle, path: string, content: string): Promise<void>;
|
|
45
41
|
snapshot(vm: VmHandle): Promise<SnapshotHandle>;
|
|
46
42
|
ssh(vm: VmHandle, options?: SshOptions): Promise<SshConnection>;
|
|
47
|
-
workspaceContext?(vm: VmHandle, input: { workspace: WorkspaceRecord }): MaybePromise<WorkspaceContext>;
|
|
48
43
|
deleteVm(vm: VmHandle): Promise<void>;
|
|
49
44
|
}
|
|
50
45
|
|
|
@@ -78,38 +73,17 @@ export type ProviderRuntimeContext = {
|
|
|
78
73
|
metadata(metadata: JsonObject): void;
|
|
79
74
|
};
|
|
80
75
|
|
|
81
|
-
export
|
|
82
|
-
providerId?: string;
|
|
83
|
-
resourceId: string;
|
|
84
|
-
snapshotId?: string;
|
|
85
|
-
sourceRef?: JsonValue;
|
|
86
|
-
metadata?: JsonObject;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export interface WorkflowWorkspaceProvider<
|
|
90
|
-
WorkspaceContext extends ProviderWorkspaceContext = ProviderWorkspaceContext,
|
|
91
|
-
> {
|
|
92
|
-
canUse(sourceRef: JsonValue): boolean;
|
|
93
|
-
createWorkspace(sourceRef: JsonValue, input: { name: string }): Promise<WorkflowWorkspaceCreateResult>;
|
|
94
|
-
deleteWorkspace(workspace: WorkspaceRecord): Promise<void>;
|
|
95
|
-
snapshotWorkspace(workspace: WorkspaceRecord): Promise<WorkflowWorkspaceCreateResult>;
|
|
96
|
-
ssh(workspaceOrResourceId: string, options?: SshOptions): Promise<SshConnection>;
|
|
97
|
-
workspaceContext?(workspace: WorkspaceRecord): MaybePromise<WorkspaceContext>;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface WorkflowProviderController<
|
|
101
|
-
Runtime = unknown,
|
|
102
|
-
WorkspaceContext extends ProviderWorkspaceContext = ProviderWorkspaceContext,
|
|
103
|
-
> {
|
|
76
|
+
export interface WorkflowProviderController<Runtime = unknown> {
|
|
104
77
|
readonly providerId: string;
|
|
105
78
|
runtime(context: ProviderRuntimeContext): MaybePromise<Runtime>;
|
|
106
79
|
validateArtifact?(ref: JsonValue): MaybePromise<boolean>;
|
|
107
|
-
workspace?: WorkflowWorkspaceProvider<WorkspaceContext>;
|
|
108
80
|
}
|
|
109
81
|
|
|
110
82
|
export type ProviderFactoryInput = {
|
|
111
83
|
provider: LoadedProviderDefinition;
|
|
112
84
|
storage: ProviderStorage;
|
|
85
|
+
hostStorage: ProviderStorage;
|
|
86
|
+
local: LocalWorkspaceRuntime;
|
|
113
87
|
};
|
|
114
88
|
|
|
115
89
|
export type ProviderFactory = (
|
package/src/state.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { and, asc, desc, eq
|
|
2
|
+
import { and, asc, desc, eq } from "drizzle-orm";
|
|
3
3
|
import type { ProviderStorage, ProviderStorageRecord } from "./provider/types.ts";
|
|
4
4
|
import type { JsonValue, WorkspaceRecord } from "./types.ts";
|
|
5
5
|
import {
|
|
@@ -60,6 +60,10 @@ export interface StateService {
|
|
|
60
60
|
upstreamRunIds: readonly string[];
|
|
61
61
|
}): WorkflowNodeRunRecord | undefined;
|
|
62
62
|
saveNodeRun(run: WorkflowNodeRunRecord): void;
|
|
63
|
+
invalidateNodeRuns(input: {
|
|
64
|
+
workflow: string;
|
|
65
|
+
nodePaths: readonly string[];
|
|
66
|
+
}): string[];
|
|
63
67
|
providerStorage(providerId: string): ProviderStorage;
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -96,7 +100,7 @@ export class StateStore implements StateService {
|
|
|
96
100
|
const row = this.db
|
|
97
101
|
.select()
|
|
98
102
|
.from(workspaces)
|
|
99
|
-
.where(
|
|
103
|
+
.where(eq(workspaces.name, nameOrResourceId))
|
|
100
104
|
.get();
|
|
101
105
|
return row ? toWorkspaceRecord(row) : undefined;
|
|
102
106
|
}
|
|
@@ -114,14 +118,10 @@ export class StateStore implements StateService {
|
|
|
114
118
|
target: workspaces.name,
|
|
115
119
|
set: {
|
|
116
120
|
id: workspace.id,
|
|
117
|
-
providerId: workspace.providerId,
|
|
118
121
|
workflow: workspace.workflow,
|
|
119
|
-
|
|
120
|
-
snapshotId: workspace.snapshotId,
|
|
121
|
-
sourceRef: workspace.sourceRef,
|
|
122
|
-
context: workspace.context,
|
|
122
|
+
workflowCtx: workspace.workflowCtx,
|
|
123
123
|
updatedAt: workspace.updatedAt,
|
|
124
|
-
|
|
124
|
+
ctx: workspace.ctx,
|
|
125
125
|
},
|
|
126
126
|
})
|
|
127
127
|
.run();
|
|
@@ -174,6 +174,42 @@ export class StateStore implements StateService {
|
|
|
174
174
|
this.db.insert(workflowNodeRuns).values(run).run();
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
invalidateNodeRuns(input: {
|
|
178
|
+
workflow: string;
|
|
179
|
+
nodePaths: readonly string[];
|
|
180
|
+
}): string[] {
|
|
181
|
+
const targetPaths = new Set(input.nodePaths);
|
|
182
|
+
if (targetPaths.size === 0) return [];
|
|
183
|
+
|
|
184
|
+
const rows = this.db
|
|
185
|
+
.select()
|
|
186
|
+
.from(workflowNodeRuns)
|
|
187
|
+
.where(eq(workflowNodeRuns.workflow, input.workflow))
|
|
188
|
+
.orderBy(asc(workflowNodeRuns.createdAt))
|
|
189
|
+
.all()
|
|
190
|
+
.map(toNodeRunRecord);
|
|
191
|
+
|
|
192
|
+
const invalidatedIds = new Set<string>();
|
|
193
|
+
let changed = true;
|
|
194
|
+
while (changed) {
|
|
195
|
+
changed = false;
|
|
196
|
+
for (const row of rows) {
|
|
197
|
+
if (row.invalidated || invalidatedIds.has(row.id)) continue;
|
|
198
|
+
const isTarget = targetPaths.has(row.nodePath);
|
|
199
|
+
const dependsOnInvalidated = row.upstreamRunIds.some((id) => invalidatedIds.has(id));
|
|
200
|
+
if (!isTarget && !dependsOnInvalidated) continue;
|
|
201
|
+
invalidatedIds.add(row.id);
|
|
202
|
+
changed = true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const id of invalidatedIds) {
|
|
207
|
+
this.db.update(workflowNodeRuns).set({ invalidated: true }).where(eq(workflowNodeRuns.id, id)).run();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return [...invalidatedIds];
|
|
211
|
+
}
|
|
212
|
+
|
|
177
213
|
providerStorage(providerId: string): ProviderStorage {
|
|
178
214
|
return new StateProviderStorage(this.db, providerId);
|
|
179
215
|
}
|
|
@@ -218,15 +254,11 @@ function toWorkspaceRecord(row: typeof workspaces.$inferSelect): WorkspaceRecord
|
|
|
218
254
|
return {
|
|
219
255
|
id: row.id,
|
|
220
256
|
name: row.name,
|
|
221
|
-
providerId: row.providerId,
|
|
222
257
|
workflow: row.workflow,
|
|
223
|
-
|
|
224
|
-
snapshotId: row.snapshotId ?? undefined,
|
|
225
|
-
sourceRef: row.sourceRef,
|
|
226
|
-
context: row.context,
|
|
258
|
+
workflowCtx: row.workflowCtx,
|
|
227
259
|
createdAt: row.createdAt,
|
|
228
260
|
updatedAt: row.updatedAt,
|
|
229
|
-
|
|
261
|
+
ctx: row.ctx,
|
|
230
262
|
};
|
|
231
263
|
}
|
|
232
264
|
|