@codemation/host 0.6.0 → 0.8.0
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 +483 -0
- package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
- package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
- package/dist/{AppConfigFactory-YnveXE9k.d.ts → AppConfigFactory-BT0y0LVC.d.ts} +8490 -5548
- package/dist/{AppConfigFactory-C6q-CSKb.js → AppConfigFactory-Cx4qQvRk.js} +112 -52
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
- package/dist/{AppContainerFactory-qaqc-R1D.js → AppContainerFactory-DRTjG7nG.js} +7298 -1732
- package/dist/AppContainerFactory-DRTjG7nG.js.map +1 -0
- package/dist/{CodemationAppContext-DRu1Dpri.d.ts → CodemationAppContext-CGFYVcSb.d.ts} +14 -4
- package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-BteaR3Dc.js} +19 -6
- package/dist/CodemationAuthoring.types-BteaR3Dc.js.map +1 -0
- package/dist/{CodemationAuthoring.types-fBRppnmi.d.ts → CodemationAuthoring.types-DiKKogum.d.ts} +30 -5
- package/dist/{CodemationConfigNormalizer-DVko3cVN.d.ts → CodemationConfigNormalizer-48f-T66P.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-By-6tuGc.js} +81 -10
- package/dist/CodemationConsumerConfigLoader-By-6tuGc.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-DJWr86f-.d.ts → CodemationConsumerConfigLoader-_PIYqwVx.d.ts} +18 -2
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
- package/dist/{CodemationPluginListMerger-DGc-jfa2.d.ts → CodemationPluginListMerger-DP7djJ9S.d.ts} +151 -19
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
- package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
- package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
- package/dist/{CredentialContractsRegistry-DrMIDSw8.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
- package/dist/{CredentialServices-UfvHB-rN.d.ts → CredentialServices-BLloBztI.d.ts} +65 -20
- package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
- package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
- package/dist/InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts +17 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
- package/dist/{ItemsInputNormalizer-C-KHg9Mo.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +89 -25
- package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
- package/dist/PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts +82 -0
- package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
- package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
- package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
- package/dist/{TelemetryContracts-DbaNomrH.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
- package/dist/{WorkflowPolicyUiPresentationFactory-DQEY-h_S.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
- package/dist/{WorkflowViewContracts-CzK2KFuz.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +10 -1
- package/dist/authoring.d.ts +5 -5
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +6 -6
- package/dist/consumer.js +2 -2
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/devServerSidecar.js +1 -94
- package/dist/devServerSidecar.js.map +1 -1
- package/dist/dto.d.ts +6 -6
- package/dist/{index-BbBk26m0.d.ts → index-DilAYwnH.d.ts} +49 -3
- package/dist/index.d.ts +141 -21
- package/dist/index.js +109 -14
- package/dist/index.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +42 -113
- package/dist/nextServer.js +9 -7
- package/dist/pairing.d.ts +93 -0
- package/dist/pairing.js +5 -0
- package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
- package/dist/persistenceServer-B71RGvSj.d.ts +30 -0
- package/dist/{persistenceServer-CmsIKnO9.js → persistenceServer-C-hH4z6l.js} +2 -2
- package/dist/{persistenceServer-CmsIKnO9.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
- package/dist/persistenceServer.d.ts +8 -8
- package/dist/persistenceServer.js +3 -3
- package/dist/{server-MUNGsBYK.d.ts → server-09PKasWR.d.ts} +21 -6
- package/dist/{server-CJFfY67o.js → server-vtRCPgRJ.js} +7 -6
- package/dist/{server-CJFfY67o.js.map → server-vtRCPgRJ.js.map} +1 -1
- package/dist/server.d.ts +14 -14
- package/dist/server.js +13 -11
- package/package.json +47 -58
- package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
- package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
- package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
- package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
- package/prisma/schema.postgresql.prisma +55 -17
- package/prisma/schema.sqlite.prisma +55 -17
- package/prisma-generated/prisma-postgresql-client/edge.js +33 -5
- package/prisma-generated/prisma-postgresql-client/index-browser.js +29 -1
- package/prisma-generated/prisma-postgresql-client/index.d.ts +8933 -5716
- package/prisma-generated/prisma-postgresql-client/index.js +33 -5
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +38 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +33 -5
- package/prisma-generated/prisma-sqlite-client/index-browser.js +29 -1
- package/prisma-generated/prisma-sqlite-client/index.d.ts +8925 -5713
- package/prisma-generated/prisma-sqlite-client/index.js +33 -5
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +38 -0
- package/scripts/check-collections.mjs +18 -0
- package/scripts/generate-prisma-clients.mjs +20 -11
- package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
- package/src/application/contracts/WorkflowViewContracts.ts +6 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
- package/src/application/mapping/WorkflowDefinitionMapper.ts +40 -1
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
- package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
- package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
- package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
- package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
- package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
- package/src/applicationTokens.ts +20 -1
- package/src/audit/IAuditEmitter.ts +32 -0
- package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
- package/src/audit/WorkflowAuditLogWriter.ts +125 -0
- package/src/auth/managed/ManagedAuthConfig.ts +29 -0
- package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
- package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
- package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
- package/src/auth/managed/index.ts +5 -0
- package/src/bootstrap/AppContainerFactory.ts +295 -29
- package/src/bootstrap/AppContainerLifecycle.ts +31 -0
- package/src/bootstrap/perf/BootTimer.ts +168 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
- package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
- package/src/bootstrap/runtime/HeadlessApiRuntime.ts +47 -0
- package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
- package/src/credentials/BrokerClient.ts +49 -0
- package/src/credentials/BrokerRefreshError.ts +12 -0
- package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
- package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
- package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
- package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
- package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
- package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/catalogTypes.ts +4 -0
- package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
- package/src/domain/credentials/CredentialBindingService.ts +54 -2
- package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
- package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
- package/src/domain/telemetry/TelemetryContracts.ts +7 -1
- package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
- package/src/index.ts +9 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
- package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
- package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
- package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
- package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
- package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +26 -3
- package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
- package/src/mcp/McpClientFactory.ts +29 -0
- package/src/mcp/McpConnectionPool.ts +184 -0
- package/src/mcp/McpConnectionPool.types.ts +12 -0
- package/src/mcp/McpServerCatalog.ts +104 -0
- package/src/mcp/index.ts +5 -0
- package/src/pairing/HmacRequestSigner.ts +32 -0
- package/src/pairing/IncomingHmacVerifier.ts +82 -0
- package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
- package/src/pairing/InternalPingRegistrar.ts +25 -0
- package/src/pairing/PairedFetch.ts +33 -0
- package/src/pairing/PairingConfigFactory.ts +35 -0
- package/src/pairing/PairingConfigToken.ts +6 -0
- package/src/pairing/index.ts +14 -0
- package/src/pairing/pairing.types.ts +18 -0
- package/src/pairing.ts +17 -0
- package/src/persistenceServer.ts +1 -0
- package/src/presentation/config/AppConfig.ts +7 -1
- package/src/presentation/config/CodemationAuthConfig.ts +1 -1
- package/src/presentation/config/CodemationAuthoring.types.ts +60 -5
- package/src/presentation/config/CodemationConfig.ts +9 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
- package/src/presentation/config/CodemationPlugin.ts +2 -1
- package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
- package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
- package/src/presentation/http/ApiPaths.ts +4 -4
- package/src/presentation/http/HeadlessHttpServerFactory.ts +56 -0
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
- package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
- package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +59 -7
- package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
- package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
- package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
- package/src/presentation/websocket/WorkflowWebsocketServerFactory.ts +16 -0
- package/src/process/ExecaProcessRunner.ts +41 -0
- package/src/process/ProcessRunner.types.ts +39 -0
- package/src/server.ts +2 -0
- package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
- package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
- package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
- package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
- package/tsconfig.json +2 -0
- package/vitest.shared.ts +5 -0
- package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
- package/dist/AppConfigFactory-C6q-CSKb.js.map +0 -1
- package/dist/AppContainerFactory-qaqc-R1D.js.map +0 -1
- package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
- package/dist/CredentialServices-CgxwguAv.js.map +0 -1
- package/dist/PublicFrontendBootstrapFactory-Cb2pLmDd.d.ts +0 -45
- package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
- package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
- package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
- package/dist/persistenceServer-vtJAGDat.d.ts +0 -9
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCredentialType, CollectionDefinition, Container } from "@codemation/core";
|
|
1
|
+
import type { AnyCredentialType, CollectionDefinition, Container, McpServerDeclaration } from "@codemation/core";
|
|
2
2
|
import type { LoggerFactory } from "../../application/logging/Logger";
|
|
3
3
|
import type { AppConfig } from "../../presentation/config/AppConfig";
|
|
4
4
|
import type { CodemationPlugin } from "../../presentation/config/CodemationPlugin";
|
|
@@ -11,6 +11,7 @@ export class CodemationPluginRegistrar {
|
|
|
11
11
|
appConfig: AppConfig;
|
|
12
12
|
registerCredentialType: (type: AnyCredentialType) => void;
|
|
13
13
|
registerCollection: (definition: CollectionDefinition) => void;
|
|
14
|
+
mergeMcpServers: (declarations: ReadonlyArray<McpServerDeclaration>) => void;
|
|
14
15
|
loggerFactory: LoggerFactory;
|
|
15
16
|
}>,
|
|
16
17
|
): Promise<void> {
|
|
@@ -18,6 +19,7 @@ export class CodemationPluginRegistrar {
|
|
|
18
19
|
for (const credentialType of plugin.credentialTypes ?? []) {
|
|
19
20
|
args.registerCredentialType(credentialType);
|
|
20
21
|
}
|
|
22
|
+
args.mergeMcpServers(plugin.mcpServers ?? []);
|
|
21
23
|
if (!plugin.register) {
|
|
22
24
|
continue;
|
|
23
25
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type { AppPersistenceConfig } from "../../presentation/config/AppConfig";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parses `CODEMATION_DATABASE_URL` into an {@link AppPersistenceConfig}.
|
|
6
|
+
*
|
|
7
|
+
* Supported schemes (case-insensitive):
|
|
8
|
+
* - `sqlite://relative/path/to/file.db` → resolved relative to consumerRoot
|
|
9
|
+
* - `sqlite:///absolute/path/to/file.db` → leading slash = POSIX absolute
|
|
10
|
+
* - `sqlite://C:/path/file.db` → Windows-style absolute (path.isAbsolute()
|
|
11
|
+
* returns true for these)
|
|
12
|
+
* - `pgsql://user:pass@host:5432/dbname` → normalised to postgresql://
|
|
13
|
+
* - `postgresql://user:pass@host:5432/db` → pass-through (Prisma's expected scheme)
|
|
14
|
+
* - `postgres://user:pass@host:5432/db` → pass-through (common alias)
|
|
15
|
+
*
|
|
16
|
+
* Throws on any other scheme. Empty / whitespace input is also an error — callers
|
|
17
|
+
* should default before calling parse().
|
|
18
|
+
*/
|
|
19
|
+
export class CodemationDatabaseUrlParser {
|
|
20
|
+
parse(url: string, consumerRoot: string): AppPersistenceConfig {
|
|
21
|
+
const trimmed = url.trim();
|
|
22
|
+
if (trimmed.length === 0) {
|
|
23
|
+
throw new Error("CODEMATION_DATABASE_URL is empty.");
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.toLowerCase().startsWith("sqlite://")) {
|
|
26
|
+
const remainder = trimmed.slice("sqlite://".length);
|
|
27
|
+
const filePath = path.isAbsolute(remainder) ? remainder : path.resolve(consumerRoot, remainder);
|
|
28
|
+
return { kind: "sqlite", databaseFilePath: filePath };
|
|
29
|
+
}
|
|
30
|
+
if (trimmed.toLowerCase().startsWith("pgsql://")) {
|
|
31
|
+
return { kind: "postgresql", databaseUrl: `postgresql://${trimmed.slice("pgsql://".length)}` };
|
|
32
|
+
}
|
|
33
|
+
if (trimmed.toLowerCase().startsWith("postgresql://") || trimmed.toLowerCase().startsWith("postgres://")) {
|
|
34
|
+
return { kind: "postgresql", databaseUrl: trimmed };
|
|
35
|
+
}
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Unsupported CODEMATION_DATABASE_URL scheme: "${trimmed}". ` +
|
|
38
|
+
`Use sqlite://, pgsql://, postgresql://, or postgres://.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
TelemetryArtifactRecord,
|
|
5
5
|
TelemetryArtifactStore,
|
|
6
6
|
TelemetryArtifactWrite,
|
|
7
|
+
TelemetryPruneArgs,
|
|
7
8
|
} from "../../domain/telemetry/TelemetryContracts";
|
|
8
9
|
|
|
9
10
|
@injectable()
|
|
@@ -43,14 +44,18 @@ export class InMemoryTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
43
44
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
async pruneExpired(args:
|
|
47
|
+
async pruneExpired(args: TelemetryPruneArgs): Promise<{ count: number; storageKeys: ReadonlyArray<string> }> {
|
|
47
48
|
const candidates = [...this.rows.entries()]
|
|
48
49
|
.filter(([, row]) => row.retentionExpiresAt !== undefined && row.retentionExpiresAt <= args.nowIso)
|
|
49
50
|
.sort((left, right) => (left[1].retentionExpiresAt ?? "").localeCompare(right[1].retentionExpiresAt ?? ""))
|
|
50
51
|
.slice(0, args.limit ?? Number.MAX_SAFE_INTEGER);
|
|
51
|
-
|
|
52
|
+
const storageKeys: string[] = [];
|
|
53
|
+
for (const [key, row] of candidates) {
|
|
54
|
+
if (row.payloadStorageKey) {
|
|
55
|
+
storageKeys.push(row.payloadStorageKey);
|
|
56
|
+
}
|
|
52
57
|
this.rows.delete(key);
|
|
53
58
|
}
|
|
54
|
-
return candidates.length;
|
|
59
|
+
return { count: candidates.length, storageKeys };
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Client } from "@libsql/client";
|
|
2
2
|
import { injectable } from "@codemation/core";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
@@ -105,11 +105,12 @@ export class PrismaMigrationDeployer {
|
|
|
105
105
|
env?: Readonly<NodeJS.ProcessEnv>;
|
|
106
106
|
}>,
|
|
107
107
|
): Promise<void> {
|
|
108
|
-
const
|
|
108
|
+
const resolverEnv = { ...process.env, ...(args.env ?? {}) };
|
|
109
|
+
const prismaConfigPath = this.resolveAbsolutePrismaConfigPath(resolverEnv);
|
|
109
110
|
await new Promise<void>((resolve, reject) => {
|
|
110
111
|
const command = spawn(
|
|
111
112
|
process.execPath,
|
|
112
|
-
[...[this.resolvePrismaCliPath(), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
|
|
113
|
+
[...[this.resolvePrismaCliPath(resolverEnv), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
|
|
113
114
|
{
|
|
114
115
|
cwd: path.dirname(prismaConfigPath),
|
|
115
116
|
env: this.createProcessEnvironment(args.databaseUrl, args.provider, args.env),
|
|
@@ -177,6 +178,10 @@ export class PrismaMigrationDeployer {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
private async repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(databaseFilePath: string): Promise<boolean> {
|
|
181
|
+
// Lazy import: @libsql/client pulls in platform-specific native bindings that confuse the
|
|
182
|
+
// Next.js / Turbopack module tracer (forcing the whole project to be traced via NFT). This
|
|
183
|
+
// recovery path is rarely needed, so defer the load until it's actually invoked.
|
|
184
|
+
const { createClient } = await import("@libsql/client");
|
|
180
185
|
const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
|
|
181
186
|
try {
|
|
182
187
|
const failedMigration = await this.hasActiveFailedMigrationRecord(
|
|
@@ -288,6 +293,7 @@ export class PrismaMigrationDeployer {
|
|
|
288
293
|
}
|
|
289
294
|
|
|
290
295
|
private async cleanupNormalizedRuntimeLegacyArtifacts(databaseFilePath: string): Promise<void> {
|
|
296
|
+
const { createClient } = await import("@libsql/client");
|
|
291
297
|
const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
|
|
292
298
|
try {
|
|
293
299
|
const runColumns = await this.readSqliteTableColumns(client, "Run");
|
|
@@ -326,14 +332,15 @@ export class PrismaMigrationDeployer {
|
|
|
326
332
|
};
|
|
327
333
|
}
|
|
328
334
|
|
|
329
|
-
private resolvePrismaCliPath(): string {
|
|
330
|
-
const configuredPath =
|
|
335
|
+
private resolvePrismaCliPath(env: Readonly<NodeJS.ProcessEnv>): string {
|
|
336
|
+
const configuredPath = env.CODEMATION_PRISMA_CLI_PATH;
|
|
331
337
|
if (configuredPath && existsSync(configuredPath)) {
|
|
332
338
|
return configuredPath;
|
|
333
339
|
}
|
|
340
|
+
const packageRoot = this.resolvePackageRoot(env);
|
|
334
341
|
const packageManagerCandidates = [
|
|
335
342
|
path.resolve(process.cwd(), "node_modules", "prisma", "build", "index.js"),
|
|
336
|
-
path.resolve(
|
|
343
|
+
path.resolve(packageRoot, "node_modules", "prisma", "build", "index.js"),
|
|
337
344
|
];
|
|
338
345
|
for (const candidate of packageManagerCandidates) {
|
|
339
346
|
if (existsSync(candidate)) {
|
|
@@ -342,7 +349,7 @@ export class PrismaMigrationDeployer {
|
|
|
342
349
|
}
|
|
343
350
|
try {
|
|
344
351
|
return this.require.resolve("prisma/build/index.js", {
|
|
345
|
-
paths: [process.cwd(),
|
|
352
|
+
paths: [process.cwd(), packageRoot],
|
|
346
353
|
});
|
|
347
354
|
} catch {
|
|
348
355
|
throw new Error(
|
|
@@ -351,16 +358,17 @@ export class PrismaMigrationDeployer {
|
|
|
351
358
|
}
|
|
352
359
|
}
|
|
353
360
|
|
|
354
|
-
private resolveAbsolutePrismaConfigPath(): string {
|
|
355
|
-
const configuredPath =
|
|
361
|
+
private resolveAbsolutePrismaConfigPath(env: Readonly<NodeJS.ProcessEnv>): string {
|
|
362
|
+
const configuredPath = env.CODEMATION_PRISMA_CONFIG_PATH;
|
|
363
|
+
const packageRoot = this.resolvePackageRoot(env);
|
|
356
364
|
if (configuredPath) {
|
|
357
|
-
return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(
|
|
365
|
+
return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(packageRoot, configuredPath);
|
|
358
366
|
}
|
|
359
|
-
return path.resolve(
|
|
367
|
+
return path.resolve(packageRoot, "prisma.config.ts");
|
|
360
368
|
}
|
|
361
369
|
|
|
362
|
-
resolvePackageRoot(): string {
|
|
363
|
-
const configuredRoot =
|
|
370
|
+
resolvePackageRoot(env: Readonly<NodeJS.ProcessEnv> = process.env): string {
|
|
371
|
+
const configuredRoot = env.CODEMATION_HOST_PACKAGE_ROOT;
|
|
364
372
|
if (configuredRoot) {
|
|
365
373
|
return configuredRoot;
|
|
366
374
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BinaryBody, BinaryStorage } from "@codemation/core";
|
|
2
|
+
import { CoreTokens, inject, injectable } from "@codemation/core";
|
|
2
3
|
import { OtelIdentityFactory } from "../../application/telemetry/OtelIdentityFactory";
|
|
3
4
|
import type {
|
|
4
5
|
TelemetryArtifactRecord,
|
|
5
6
|
TelemetryArtifactStore,
|
|
6
7
|
TelemetryArtifactWrite,
|
|
8
|
+
TelemetryPruneArgs,
|
|
7
9
|
} from "../../domain/telemetry/TelemetryContracts";
|
|
8
10
|
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
9
11
|
|
|
12
|
+
/** Payloads larger than this byte threshold are offloaded to BinaryStorage. */
|
|
13
|
+
const PAYLOAD_OFFLOAD_THRESHOLD_BYTES = 64_000;
|
|
14
|
+
|
|
10
15
|
@injectable()
|
|
11
16
|
export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
12
17
|
constructor(
|
|
@@ -14,11 +19,36 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
14
19
|
private readonly prisma: PrismaDatabaseClient,
|
|
15
20
|
@inject(OtelIdentityFactory)
|
|
16
21
|
private readonly otelIdentityFactory: OtelIdentityFactory,
|
|
22
|
+
@inject(CoreTokens.BinaryStorage)
|
|
23
|
+
private readonly binaryStorage: BinaryStorage,
|
|
17
24
|
) {}
|
|
18
25
|
|
|
19
26
|
async save(record: TelemetryArtifactWrite): Promise<TelemetryArtifactRecord> {
|
|
20
27
|
const artifactId = this.otelIdentityFactory.createArtifactId();
|
|
21
28
|
const createdAt = new Date().toISOString();
|
|
29
|
+
|
|
30
|
+
// Resolve inline vs offloaded payload
|
|
31
|
+
let payloadText: string | null = record.payloadText ?? null;
|
|
32
|
+
let payloadJson: string | null = record.payloadJson !== undefined ? JSON.stringify(record.payloadJson) : null;
|
|
33
|
+
let payloadStorageKey: string | null = null;
|
|
34
|
+
|
|
35
|
+
const payloadTextBytes = payloadText ? Buffer.byteLength(payloadText, "utf8") : 0;
|
|
36
|
+
const payloadJsonBytes = payloadJson ? Buffer.byteLength(payloadJson, "utf8") : 0;
|
|
37
|
+
|
|
38
|
+
if (payloadTextBytes > PAYLOAD_OFFLOAD_THRESHOLD_BYTES) {
|
|
39
|
+
const storageKey = `telemetry-artifacts/${artifactId}.txt`;
|
|
40
|
+
const body: BinaryBody = Buffer.from(payloadText!, "utf8");
|
|
41
|
+
await this.binaryStorage.write({ storageKey, body });
|
|
42
|
+
payloadStorageKey = storageKey;
|
|
43
|
+
payloadText = null;
|
|
44
|
+
} else if (payloadJsonBytes > PAYLOAD_OFFLOAD_THRESHOLD_BYTES) {
|
|
45
|
+
const storageKey = `telemetry-artifacts/${artifactId}.json`;
|
|
46
|
+
const body: BinaryBody = Buffer.from(payloadJson!, "utf8");
|
|
47
|
+
await this.binaryStorage.write({ storageKey, body });
|
|
48
|
+
payloadStorageKey = storageKey;
|
|
49
|
+
payloadJson = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
await this.prisma.telemetryArtifact.create({
|
|
23
53
|
data: {
|
|
24
54
|
artifactId,
|
|
@@ -32,8 +62,9 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
32
62
|
contentType: record.contentType,
|
|
33
63
|
previewText: record.previewText ?? null,
|
|
34
64
|
previewJson: record.previewJson !== undefined ? JSON.stringify(record.previewJson) : null,
|
|
35
|
-
payloadText
|
|
36
|
-
payloadJson
|
|
65
|
+
payloadText,
|
|
66
|
+
payloadJson,
|
|
67
|
+
payloadStorageKey,
|
|
37
68
|
bytes: record.bytes ?? null,
|
|
38
69
|
truncated: record.truncated ?? null,
|
|
39
70
|
createdAt,
|
|
@@ -53,8 +84,9 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
53
84
|
contentType: record.contentType,
|
|
54
85
|
previewText: record.previewText,
|
|
55
86
|
previewJson: record.previewJson,
|
|
56
|
-
payloadText:
|
|
57
|
-
payloadJson:
|
|
87
|
+
payloadText: payloadText ?? undefined,
|
|
88
|
+
payloadJson: payloadJson !== null ? JSON.parse(payloadJson) : undefined,
|
|
89
|
+
payloadStorageKey: payloadStorageKey ?? undefined,
|
|
58
90
|
bytes: record.bytes,
|
|
59
91
|
truncated: record.truncated,
|
|
60
92
|
createdAt,
|
|
@@ -82,6 +114,7 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
82
114
|
previewJson: this.parseJson(row.previewJson),
|
|
83
115
|
payloadText: row.payloadText ?? undefined,
|
|
84
116
|
payloadJson: this.parseJson(row.payloadJson),
|
|
117
|
+
payloadStorageKey: row.payloadStorageKey ?? undefined,
|
|
85
118
|
bytes: row.bytes ?? undefined,
|
|
86
119
|
truncated: row.truncated ?? undefined,
|
|
87
120
|
createdAt: row.createdAt,
|
|
@@ -90,7 +123,7 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
90
123
|
}));
|
|
91
124
|
}
|
|
92
125
|
|
|
93
|
-
async pruneExpired(args:
|
|
126
|
+
async pruneExpired(args: TelemetryPruneArgs): Promise<{ count: number; storageKeys: ReadonlyArray<string> }> {
|
|
94
127
|
const rows = await this.prisma.telemetryArtifact.findMany({
|
|
95
128
|
where: {
|
|
96
129
|
retentionExpiresAt: {
|
|
@@ -99,13 +132,15 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
99
132
|
},
|
|
100
133
|
select: {
|
|
101
134
|
artifactId: true,
|
|
135
|
+
payloadStorageKey: true,
|
|
102
136
|
},
|
|
103
137
|
orderBy: [{ retentionExpiresAt: "asc" }, { artifactId: "asc" }],
|
|
104
138
|
...(args.limit ? { take: args.limit } : {}),
|
|
105
139
|
});
|
|
106
140
|
if (rows.length === 0) {
|
|
107
|
-
return 0;
|
|
141
|
+
return { count: 0, storageKeys: [] };
|
|
108
142
|
}
|
|
143
|
+
const storageKeys = rows.flatMap((row) => (row.payloadStorageKey ? [row.payloadStorageKey] : []));
|
|
109
144
|
const result = await this.prisma.telemetryArtifact.deleteMany({
|
|
110
145
|
where: {
|
|
111
146
|
artifactId: {
|
|
@@ -113,7 +148,7 @@ export class PrismaTelemetryArtifactStore implements TelemetryArtifactStore {
|
|
|
113
148
|
},
|
|
114
149
|
},
|
|
115
150
|
});
|
|
116
|
-
return result.count;
|
|
151
|
+
return { count: result.count, storageKeys };
|
|
117
152
|
}
|
|
118
153
|
|
|
119
154
|
private parseJson(value: string | null): unknown {
|
|
@@ -22,6 +22,8 @@ import { inject, injectable } from "@codemation/core";
|
|
|
22
22
|
import type { WorkflowRunRepository } from "../../domain/runs/WorkflowRunRepository";
|
|
23
23
|
import type { Prisma } from "../../../prisma-generated/prisma-postgresql-client/client.js";
|
|
24
24
|
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
25
|
+
import type { WorkflowSnapshotRepository } from "./PrismaWorkflowSnapshotRepository";
|
|
26
|
+
import { PrismaWorkflowSnapshotRepository } from "./PrismaWorkflowSnapshotRepository";
|
|
25
27
|
|
|
26
28
|
type ExecutionInstanceRow = {
|
|
27
29
|
instanceId: string;
|
|
@@ -80,7 +82,10 @@ type RunSlotProjectionRow = {
|
|
|
80
82
|
|
|
81
83
|
@injectable()
|
|
82
84
|
export class PrismaWorkflowRunRepository implements WorkflowRunRepository, WorkflowExecutionRepository {
|
|
83
|
-
constructor(
|
|
85
|
+
constructor(
|
|
86
|
+
@inject(PrismaDatabaseClientToken) private readonly prisma: PrismaDatabaseClient,
|
|
87
|
+
@inject(PrismaWorkflowSnapshotRepository) private readonly snapshotRepo: WorkflowSnapshotRepository,
|
|
88
|
+
) {}
|
|
84
89
|
|
|
85
90
|
async createRun(args: {
|
|
86
91
|
runId: RunId;
|
|
@@ -96,6 +101,14 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
96
101
|
}): Promise<void> {
|
|
97
102
|
const now = new Date().toISOString();
|
|
98
103
|
const testContext = args.executionOptions?.testContext;
|
|
104
|
+
const snapshotJson = args.workflowSnapshot ? JSON.stringify(args.workflowSnapshot) : null;
|
|
105
|
+
const workflowSnapshotId =
|
|
106
|
+
snapshotJson !== null
|
|
107
|
+
? await this.snapshotRepo.findOrCreate({
|
|
108
|
+
workflowId: args.workflowId,
|
|
109
|
+
snapshotJson,
|
|
110
|
+
})
|
|
111
|
+
: null;
|
|
99
112
|
await this.prisma.run.create({
|
|
100
113
|
data: {
|
|
101
114
|
runId: args.runId,
|
|
@@ -108,7 +121,8 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
108
121
|
revision: 0,
|
|
109
122
|
outputsByNodeJson: JSON.stringify({}),
|
|
110
123
|
controlJson: args.control ? JSON.stringify(args.control) : null,
|
|
111
|
-
workflowSnapshotJson:
|
|
124
|
+
workflowSnapshotJson: snapshotJson,
|
|
125
|
+
workflowSnapshotId,
|
|
112
126
|
policySnapshotJson: args.policySnapshot ? JSON.stringify(args.policySnapshot) : null,
|
|
113
127
|
engineCountersJson: args.engineCounters ? JSON.stringify(args.engineCounters) : null,
|
|
114
128
|
mutableStateJson: args.mutableState ? JSON.stringify(args.mutableState) : null,
|
|
@@ -290,6 +304,14 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
290
304
|
const workItems = this.buildWorkItems(state, now);
|
|
291
305
|
const instances = this.buildExecutionInstances(state);
|
|
292
306
|
const projectionJson = this.buildProjectionSlotStatesJson(state);
|
|
307
|
+
const snapshotJson = state.workflowSnapshot ? JSON.stringify(state.workflowSnapshot) : null;
|
|
308
|
+
const workflowSnapshotId =
|
|
309
|
+
snapshotJson !== null
|
|
310
|
+
? await this.snapshotRepo.findOrCreate({
|
|
311
|
+
workflowId: state.workflowId,
|
|
312
|
+
snapshotJson,
|
|
313
|
+
})
|
|
314
|
+
: null;
|
|
293
315
|
|
|
294
316
|
await this.prisma.$transaction(async (tx) => {
|
|
295
317
|
await tx.runWorkItem.deleteMany({ where: { runId: state.runId } });
|
|
@@ -389,7 +411,8 @@ export class PrismaWorkflowRunRepository implements WorkflowRunRepository, Workf
|
|
|
389
411
|
parentJson: state.parent ? JSON.stringify(state.parent) : null,
|
|
390
412
|
executionOptionsJson: state.executionOptions ? JSON.stringify(state.executionOptions) : null,
|
|
391
413
|
controlJson: state.control ? JSON.stringify(state.control) : null,
|
|
392
|
-
workflowSnapshotJson:
|
|
414
|
+
workflowSnapshotJson: snapshotJson,
|
|
415
|
+
workflowSnapshotId,
|
|
393
416
|
policySnapshotJson: state.policySnapshot ? JSON.stringify(state.policySnapshot) : null,
|
|
394
417
|
engineCountersJson: state.engineCounters ? JSON.stringify(state.engineCounters) : null,
|
|
395
418
|
mutableStateJson: state.mutableState ? JSON.stringify(state.mutableState) : null,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { inject, injectable } from "@codemation/core";
|
|
3
|
+
import { PrismaDatabaseClientToken, type PrismaDatabaseClient } from "./PrismaDatabaseClient";
|
|
4
|
+
|
|
5
|
+
export interface WorkflowSnapshotRepository {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the id of an existing snapshot matching (workflowId, snapshotHash), or creates
|
|
8
|
+
* a new one from the provided snapshotJson. Deduplication is by content hash.
|
|
9
|
+
*/
|
|
10
|
+
findOrCreate(args: Readonly<{ workflowId: string; snapshotJson: string }>): Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@injectable()
|
|
14
|
+
export class PrismaWorkflowSnapshotRepository implements WorkflowSnapshotRepository {
|
|
15
|
+
constructor(
|
|
16
|
+
@inject(PrismaDatabaseClientToken)
|
|
17
|
+
private readonly prisma: PrismaDatabaseClient,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
async findOrCreate(args: Readonly<{ workflowId: string; snapshotJson: string }>): Promise<string> {
|
|
21
|
+
const snapshotHash = createHash("sha256").update(args.snapshotJson, "utf8").digest("hex");
|
|
22
|
+
const existing = await this.prisma.workflowSnapshot.findUnique({
|
|
23
|
+
where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
|
|
24
|
+
select: { id: true },
|
|
25
|
+
});
|
|
26
|
+
if (existing) {
|
|
27
|
+
return existing.id;
|
|
28
|
+
}
|
|
29
|
+
const id = crypto.randomUUID();
|
|
30
|
+
await this.prisma.workflowSnapshot.upsert({
|
|
31
|
+
where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
|
|
32
|
+
create: {
|
|
33
|
+
id,
|
|
34
|
+
workflowId: args.workflowId,
|
|
35
|
+
snapshotHash,
|
|
36
|
+
snapshotJson: args.snapshotJson,
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
},
|
|
39
|
+
update: {},
|
|
40
|
+
});
|
|
41
|
+
// Re-fetch so the returned id is the winner under concurrent inserts
|
|
42
|
+
const row = await this.prisma.workflowSnapshot.findUniqueOrThrow({
|
|
43
|
+
where: { workflowId_snapshotHash: { workflowId: args.workflowId, snapshotHash } },
|
|
44
|
+
select: { id: true },
|
|
45
|
+
});
|
|
46
|
+
return row.id;
|
|
47
|
+
}
|
|
48
|
+
}
|