@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.
@@ -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";
@@ -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 type WorkflowWorkspaceCreateResult = {
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, or } from "drizzle-orm";
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(or(eq(workspaces.name, nameOrResourceId), eq(workspaces.resourceId, nameOrResourceId)))
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
- resourceId: workspace.resourceId,
120
- snapshotId: workspace.snapshotId,
121
- sourceRef: workspace.sourceRef,
122
- context: workspace.context,
122
+ workflowCtx: workspace.workflowCtx,
123
123
  updatedAt: workspace.updatedAt,
124
- metadata: workspace.metadata,
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
- resourceId: row.resourceId,
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
- metadata: row.metadata,
261
+ ctx: row.ctx,
230
262
  };
231
263
  }
232
264