@codemation/host 0.1.0 → 0.1.2
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/CHANGELOG.md +24 -0
- package/dist/{AppConfigFactory-Ciz9YKWx.js → AppConfigFactory-ByT1D8dM.js} +253 -15
- package/dist/{AppConfigFactory-Ciz9YKWx.js.map → AppConfigFactory-ByT1D8dM.js.map} +1 -1
- package/dist/{AppConfigFactory-CiBPHleh.d.ts → AppConfigFactory-_fqSok1J.d.ts} +6565 -177
- package/dist/{AppContainerFactory-DH88oxpg.js → AppContainerFactory-BRU02PTm.js} +838 -145
- package/dist/AppContainerFactory-BRU02PTm.js.map +1 -0
- package/dist/{CodemationConfig-DfK1KLvO.d.ts → CodemationConfig-CNfytKR6.d.ts} +2 -2
- package/dist/{CodemationConfigNormalizer-BKgIOeLm.d.ts → CodemationConfigNormalizer-BuKWVNEq.d.ts} +2 -2
- package/dist/{CodemationConsumerConfigLoader-bdhJsBKt.d.ts → CodemationConsumerConfigLoader-Mv4cywWu.d.ts} +2 -2
- package/dist/{CodemationPluginListMerger-BFZeO0WG.d.ts → CodemationPluginListMerger-BD5mR6gK.d.ts} +11 -5
- package/dist/{CredentialServices-aKIwHEhf.d.ts → CredentialServices-BQsEtctT.d.ts} +8 -7
- package/dist/{CredentialServices-DNb3CZwW.js → CredentialServices-xVxVA9Tq.js} +60 -114
- package/dist/CredentialServices-xVxVA9Tq.js.map +1 -0
- package/dist/{PublicFrontendBootstrapFactory-6ahaU0XM.d.ts → PublicFrontendBootstrapFactory-kTyAJdHI.d.ts} +2 -2
- package/dist/consumer.d.ts +4 -4
- package/dist/credentials.d.ts +3 -3
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +1 -1
- package/dist/{index-BYbzmUwS.d.ts → index-CX752QE9.d.ts} +68 -4
- package/dist/index.d.ts +10 -10
- package/dist/index.js +5 -5
- package/dist/nextServer.d.ts +20 -15
- package/dist/nextServer.js +35 -58
- package/dist/nextServer.js.map +1 -1
- package/dist/{persistenceServer-DL8yBGDU.d.ts → persistenceServer-CLY4qtMo.d.ts} +2 -2
- package/dist/{persistenceServer-CuAqL_fF.js → persistenceServer-DMvIOGW8.js} +2 -2
- package/dist/{persistenceServer-CuAqL_fF.js.map → persistenceServer-DMvIOGW8.js.map} +1 -1
- package/dist/persistenceServer.d.ts +5 -5
- package/dist/persistenceServer.js +2 -2
- package/dist/{server-EbxQft_X.js → server-ChTCEc6R.js} +4 -4
- package/dist/{server-EbxQft_X.js.map → server-ChTCEc6R.js.map} +1 -1
- package/dist/{server-BceIfIJf.d.ts → server-DwpcwzFb.d.ts} +6 -5
- package/dist/server.d.ts +8 -8
- package/dist/server.js +5 -5
- package/package.json +5 -5
- package/prisma/migrations/20260407140000_run_normalized_persistence/migration.sql +327 -0
- package/prisma/migrations/20260407193000_rename_run_projection_to_run_slot_projection/migration.sql +10 -0
- package/prisma/migrations.sqlite/20260407140000_run_normalized_persistence/migration.sql +326 -0
- package/prisma/migrations.sqlite/20260407193000_rename_run_projection_to_run_slot_projection/migration.sql +38 -0
- package/prisma/schema.postgresql.prisma +100 -1
- package/prisma/schema.sqlite.prisma +101 -1
- package/scripts/integration-database-global-setup.mjs +0 -3
- package/src/application/mapping/WorkflowDefinitionMapper.ts +95 -56
- package/src/application/mapping/WorkflowPolicyUiPresentationFactory.ts +1 -1
- package/src/application/queries/GetWorkflowRunDetailQuery.ts +8 -0
- package/src/application/queries/GetWorkflowRunDetailQueryHandler.ts +24 -0
- package/src/application/queries/WorkflowQueryHandlers.ts +1 -0
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +52 -27
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +113 -158
- package/src/domain/runs/WorkflowRunRepository.ts +7 -1
- package/src/infrastructure/persistence/InMemoryWorkflowRunRepository.ts +123 -1
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +226 -6
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +796 -109
- package/src/infrastructure/persistence/generated/prisma-postgresql-client/edge.js +85 -5
- package/src/infrastructure/persistence/generated/prisma-postgresql-client/index-browser.js +81 -1
- package/src/infrastructure/persistence/generated/prisma-postgresql-client/index.d.ts +7107 -237
- package/src/infrastructure/persistence/generated/prisma-postgresql-client/index.js +85 -5
- package/src/infrastructure/persistence/generated/prisma-postgresql-client/package.json +1 -1
- package/src/infrastructure/persistence/generated/prisma-postgresql-client/schema.prisma +101 -1
- package/src/infrastructure/persistence/generated/prisma-sqlite-client/edge.js +85 -5
- package/src/infrastructure/persistence/generated/prisma-sqlite-client/index-browser.js +81 -1
- package/src/infrastructure/persistence/generated/prisma-sqlite-client/index.d.ts +7104 -242
- package/src/infrastructure/persistence/generated/prisma-sqlite-client/index.js +85 -5
- package/src/infrastructure/persistence/generated/prisma-sqlite-client/package.json +1 -1
- package/src/infrastructure/persistence/generated/prisma-sqlite-client/schema.prisma +101 -1
- package/src/presentation/http/ApiPaths.ts +4 -0
- package/src/presentation/http/hono/registrars/RunHonoApiRouteRegistrar.ts +1 -0
- package/src/presentation/http/routeHandlers/RunHttpRouteHandler.ts +13 -0
- package/dist/AppContainerFactory-DH88oxpg.js.map +0 -1
- package/dist/CredentialServices-DNb3CZwW.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createClient, type Client } from "@libsql/client";
|
|
1
2
|
import { injectable } from "@codemation/core";
|
|
2
3
|
import { spawn } from "node:child_process";
|
|
3
4
|
import { existsSync } from "node:fs";
|
|
@@ -12,6 +13,7 @@ import type { AppPersistenceConfig } from "../../presentation/config/AppConfig";
|
|
|
12
13
|
*/
|
|
13
14
|
@injectable()
|
|
14
15
|
export class PrismaMigrationDeployer {
|
|
16
|
+
private static readonly normalizedRuntimeMigrationName = "20260407140000_run_normalized_persistence";
|
|
15
17
|
private readonly require = createRequire(import.meta.url);
|
|
16
18
|
|
|
17
19
|
async deployPersistence(persistence: AppPersistenceConfig, env?: Readonly<NodeJS.ProcessEnv>): Promise<void> {
|
|
@@ -33,11 +35,25 @@ export class PrismaMigrationDeployer {
|
|
|
33
35
|
args: Readonly<{ databaseFilePath: string; env?: Readonly<NodeJS.ProcessEnv> }>,
|
|
34
36
|
): Promise<void> {
|
|
35
37
|
await this.ensureSqliteParentDirectoryExists(args.databaseFilePath);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const databaseUrl = this.sqliteFilePathToDatabaseUrl(args.databaseFilePath);
|
|
39
|
+
try {
|
|
40
|
+
await this.deployWithProvider({
|
|
41
|
+
provider: "sqlite",
|
|
42
|
+
databaseUrl,
|
|
43
|
+
env: args.env,
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const recovered = await this.tryRecoverPartiallyAppliedNormalizedRuntimeMigration({
|
|
47
|
+
databaseFilePath: args.databaseFilePath,
|
|
48
|
+
databaseUrl,
|
|
49
|
+
env: args.env,
|
|
50
|
+
error,
|
|
51
|
+
});
|
|
52
|
+
if (!recovered) {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
await this.cleanupNormalizedRuntimeLegacyArtifacts(args.databaseFilePath);
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
private async deployPostgres(
|
|
@@ -56,12 +72,44 @@ export class PrismaMigrationDeployer {
|
|
|
56
72
|
databaseUrl: string;
|
|
57
73
|
env?: Readonly<NodeJS.ProcessEnv>;
|
|
58
74
|
}>,
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
await this.runPrismaCommand({
|
|
77
|
+
prismaArgs: ["migrate", "deploy"],
|
|
78
|
+
provider: args.provider,
|
|
79
|
+
databaseUrl: args.databaseUrl,
|
|
80
|
+
env: args.env,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async resolveAppliedMigration(
|
|
85
|
+
args: Readonly<{
|
|
86
|
+
provider: "postgresql" | "sqlite";
|
|
87
|
+
databaseUrl: string;
|
|
88
|
+
migrationName: string;
|
|
89
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
90
|
+
}>,
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
await this.runPrismaCommand({
|
|
93
|
+
prismaArgs: ["migrate", "resolve", "--applied", args.migrationName],
|
|
94
|
+
provider: args.provider,
|
|
95
|
+
databaseUrl: args.databaseUrl,
|
|
96
|
+
env: args.env,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async runPrismaCommand(
|
|
101
|
+
args: Readonly<{
|
|
102
|
+
prismaArgs: string[];
|
|
103
|
+
provider: "postgresql" | "sqlite";
|
|
104
|
+
databaseUrl: string;
|
|
105
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
106
|
+
}>,
|
|
59
107
|
): Promise<void> {
|
|
60
108
|
const prismaConfigPath = this.resolveAbsolutePrismaConfigPath();
|
|
61
109
|
await new Promise<void>((resolve, reject) => {
|
|
62
110
|
const command = spawn(
|
|
63
111
|
process.execPath,
|
|
64
|
-
[this.resolvePrismaCliPath(),
|
|
112
|
+
[...[this.resolvePrismaCliPath(), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
|
|
65
113
|
{
|
|
66
114
|
cwd: path.dirname(prismaConfigPath),
|
|
67
115
|
env: this.createProcessEnvironment(args.databaseUrl, args.provider, args.env),
|
|
@@ -89,6 +137,178 @@ export class PrismaMigrationDeployer {
|
|
|
89
137
|
});
|
|
90
138
|
}
|
|
91
139
|
|
|
140
|
+
private async tryRecoverPartiallyAppliedNormalizedRuntimeMigration(
|
|
141
|
+
args: Readonly<{
|
|
142
|
+
databaseFilePath: string;
|
|
143
|
+
databaseUrl: string;
|
|
144
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
145
|
+
error: unknown;
|
|
146
|
+
}>,
|
|
147
|
+
): Promise<boolean> {
|
|
148
|
+
if (!this.isRecoverableNormalizedRuntimeMigrationError(args.error)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const repaired = await this.repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(args.databaseFilePath);
|
|
152
|
+
if (!repaired) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
await this.resolveAppliedMigration({
|
|
156
|
+
provider: "sqlite",
|
|
157
|
+
databaseUrl: args.databaseUrl,
|
|
158
|
+
migrationName: PrismaMigrationDeployer.normalizedRuntimeMigrationName,
|
|
159
|
+
env: args.env,
|
|
160
|
+
});
|
|
161
|
+
await this.deployWithProvider({
|
|
162
|
+
provider: "sqlite",
|
|
163
|
+
databaseUrl: args.databaseUrl,
|
|
164
|
+
env: args.env,
|
|
165
|
+
});
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private isRecoverableNormalizedRuntimeMigrationError(error: unknown): boolean {
|
|
170
|
+
if (!(error instanceof Error)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
return (
|
|
174
|
+
error.message.includes("Error: P3009") &&
|
|
175
|
+
error.message.includes(PrismaMigrationDeployer.normalizedRuntimeMigrationName)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private async repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(databaseFilePath: string): Promise<boolean> {
|
|
180
|
+
const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
|
|
181
|
+
try {
|
|
182
|
+
const failedMigration = await this.hasActiveFailedMigrationRecord(
|
|
183
|
+
client,
|
|
184
|
+
PrismaMigrationDeployer.normalizedRuntimeMigrationName,
|
|
185
|
+
);
|
|
186
|
+
if (!failedMigration) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const runColumns = await this.readSqliteTableColumns(client, "Run");
|
|
190
|
+
const hasNormalizedRunShape =
|
|
191
|
+
runColumns.has("finished_at") &&
|
|
192
|
+
runColumns.has("revision") &&
|
|
193
|
+
runColumns.has("outputs_by_node_json") &&
|
|
194
|
+
!runColumns.has("state_json");
|
|
195
|
+
if (!hasNormalizedRunShape) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
await this.ensureNormalizedRuntimeRepairArtifacts(client);
|
|
199
|
+
return true;
|
|
200
|
+
} finally {
|
|
201
|
+
client.close();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private async hasActiveFailedMigrationRecord(client: Client, migrationName: string): Promise<boolean> {
|
|
206
|
+
const result = await client.execute({
|
|
207
|
+
sql: [
|
|
208
|
+
'SELECT 1 AS "has_failed"',
|
|
209
|
+
'FROM "_prisma_migrations"',
|
|
210
|
+
'WHERE "migration_name" = ?',
|
|
211
|
+
' AND "finished_at" IS NULL',
|
|
212
|
+
' AND "rolled_back_at" IS NULL',
|
|
213
|
+
"LIMIT 1",
|
|
214
|
+
].join(" "),
|
|
215
|
+
args: [migrationName],
|
|
216
|
+
});
|
|
217
|
+
return result.rows.length > 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private async readSqliteTableColumns(client: Client, tableName: string): Promise<Set<string>> {
|
|
221
|
+
const result = await client.execute(`PRAGMA table_info("${tableName}")`);
|
|
222
|
+
return new Set(result.rows.map((row) => String(row.name)));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private async ensureNormalizedRuntimeRepairArtifacts(client: Client): Promise<void> {
|
|
226
|
+
await client.execute(`
|
|
227
|
+
CREATE TABLE IF NOT EXISTS "RunWorkItem" (
|
|
228
|
+
"work_item_id" TEXT NOT NULL PRIMARY KEY,
|
|
229
|
+
"run_id" TEXT NOT NULL,
|
|
230
|
+
"workflow_id" TEXT NOT NULL,
|
|
231
|
+
"status" TEXT NOT NULL,
|
|
232
|
+
"target_node_id" TEXT NOT NULL,
|
|
233
|
+
"batch_id" TEXT NOT NULL,
|
|
234
|
+
"queue_name" TEXT,
|
|
235
|
+
"claim_token" TEXT,
|
|
236
|
+
"claimed_by" TEXT,
|
|
237
|
+
"claimed_at" TEXT,
|
|
238
|
+
"available_at" TEXT NOT NULL,
|
|
239
|
+
"enqueued_at" TEXT NOT NULL,
|
|
240
|
+
"completed_at" TEXT,
|
|
241
|
+
"failed_at" TEXT,
|
|
242
|
+
"source_instance_id" TEXT,
|
|
243
|
+
"parent_instance_id" TEXT,
|
|
244
|
+
"items_in" INTEGER NOT NULL,
|
|
245
|
+
"inputs_by_port_json" TEXT NOT NULL,
|
|
246
|
+
"error_json" TEXT,
|
|
247
|
+
CONSTRAINT "RunWorkItem_run_id_fkey" FOREIGN KEY ("run_id") REFERENCES "Run"("run_id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
248
|
+
)
|
|
249
|
+
`);
|
|
250
|
+
await client.execute(`
|
|
251
|
+
CREATE INDEX IF NOT EXISTS "RunWorkItem_run_id_status_available_at_idx"
|
|
252
|
+
ON "RunWorkItem"("run_id", "status", "available_at")
|
|
253
|
+
`);
|
|
254
|
+
await client.execute(`
|
|
255
|
+
CREATE INDEX IF NOT EXISTS "RunWorkItem_run_id_target_node_id_batch_id_idx"
|
|
256
|
+
ON "RunWorkItem"("run_id", "target_node_id", "batch_id")
|
|
257
|
+
`);
|
|
258
|
+
await client.execute(`
|
|
259
|
+
CREATE TABLE IF NOT EXISTS "RunSlotProjection" (
|
|
260
|
+
"run_id" TEXT NOT NULL PRIMARY KEY,
|
|
261
|
+
"workflow_id" TEXT NOT NULL,
|
|
262
|
+
"revision" INTEGER NOT NULL,
|
|
263
|
+
"updated_at" TEXT NOT NULL,
|
|
264
|
+
"slot_states_json" TEXT NOT NULL,
|
|
265
|
+
CONSTRAINT "RunSlotProjection_run_id_fkey" FOREIGN KEY ("run_id") REFERENCES "Run"("run_id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
266
|
+
)
|
|
267
|
+
`);
|
|
268
|
+
await client.execute(`
|
|
269
|
+
CREATE INDEX IF NOT EXISTS "RunSlotProjection_workflow_id_updated_at_idx"
|
|
270
|
+
ON "RunSlotProjection"("workflow_id", "updated_at")
|
|
271
|
+
`);
|
|
272
|
+
await client.execute(`
|
|
273
|
+
INSERT OR IGNORE INTO "RunSlotProjection" (
|
|
274
|
+
"run_id",
|
|
275
|
+
"workflow_id",
|
|
276
|
+
"revision",
|
|
277
|
+
"updated_at",
|
|
278
|
+
"slot_states_json"
|
|
279
|
+
)
|
|
280
|
+
SELECT
|
|
281
|
+
"run_id",
|
|
282
|
+
"workflow_id",
|
|
283
|
+
"revision",
|
|
284
|
+
"updated_at",
|
|
285
|
+
json_object('slotStatesByNodeId', json('{}'))
|
|
286
|
+
FROM "Run"
|
|
287
|
+
`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private async cleanupNormalizedRuntimeLegacyArtifacts(databaseFilePath: string): Promise<void> {
|
|
291
|
+
const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
|
|
292
|
+
try {
|
|
293
|
+
const runColumns = await this.readSqliteTableColumns(client, "Run");
|
|
294
|
+
const hasNormalizedRunShape =
|
|
295
|
+
runColumns.has("finished_at") &&
|
|
296
|
+
runColumns.has("revision") &&
|
|
297
|
+
runColumns.has("outputs_by_node_json") &&
|
|
298
|
+
!runColumns.has("state_json");
|
|
299
|
+
if (!hasNormalizedRunShape) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const runSlotProjectionColumns = await this.readSqliteTableColumns(client, "RunSlotProjection");
|
|
303
|
+
await client.execute('DROP TABLE IF EXISTS "Run_legacy"');
|
|
304
|
+
if (runSlotProjectionColumns.size > 0) {
|
|
305
|
+
await client.execute('DROP TABLE IF EXISTS "RunProjection"');
|
|
306
|
+
}
|
|
307
|
+
} finally {
|
|
308
|
+
client.close();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
92
312
|
private sqliteFilePathToDatabaseUrl(databaseFilePath: string): string {
|
|
93
313
|
return `file:${path.resolve(databaseFilePath)}`;
|
|
94
314
|
}
|