@powerhousedao/switchboard 6.2.3-dev.2 → 6.2.3-dev.21

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.
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0a17ca6d-016f-53aa-9705-cef103da6f09")}catch(e){}}();
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="75db5742-026a-585f-b76c-c4e4981bb421")}catch(e){}}();
3
3
  import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-Baw7rThP.mjs";
4
4
  import { register } from "node:module";
5
5
  import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
@@ -11,9 +11,10 @@ import { existsSync, promises, realpathSync } from "node:fs";
11
11
  import path from "node:path";
12
12
  import { AtomicNodeFs } from "@powerhousedao/pglite-fs";
13
13
  import { ChannelScheme, DriveCollectionId, EventBus, REACTOR_SCHEMA, ReactorBuilder, ReactorClientBuilder, createHybridProjectionCoordinatorFactory, instrumentPgPool, parseDriveUrl, supportsLiveReadModelRegistration } from "@powerhousedao/reactor";
14
- import { HttpPackageLoader, ImportPackageLoader, PGLITE_UTC_PARSERS, PackageManagementService, PackagesSubgraph, getUniqueDocumentModels, initializeAndStartAPI, resolveRenownConfig } from "@powerhousedao/reactor-api";
14
+ import { AuthorizationPolicy, BaseSubgraph, ForbiddenError, HttpPackageLoader, ImportPackageLoader, PGLITE_UTC_PARSERS, PackageManagementService, PackagesSubgraph, createCanonicalDocumentIdResolver, getUniqueDocumentModels, initializeAndStartAPI, resolveRenownConfig } from "@powerhousedao/reactor-api";
15
15
  import { httpsHooksPath } from "@powerhousedao/reactor-api/https-hooks";
16
- import { AttachmentAlreadyExists, AttachmentNotFound, AttachmentPending, AttachmentReferenceReadModel, AttachmentSchemaCompiler, HashMismatch, InvalidAttachmentRef, ReservationNotFound, SizeMismatch, UploadTooLarge, createRef, createRemoteAttachmentService, parseAttachmentDownloadTarget } from "@powerhousedao/reactor-attachments";
16
+ import { AttachmentAlreadyExists, AttachmentNotFound, AttachmentPending, AttachmentReferenceReadModel, AttachmentSchemaCompiler, HashMismatch, InvalidAttachmentRef, ReservationNotFound, SizeMismatch, UploadTooLarge, createRef, createRemoteAttachmentService, parseAttachmentDownloadTarget, parseRef } from "@powerhousedao/reactor-attachments";
17
+ import { createAttachmentClient } from "@powerhousedao/reactor-attachments/client";
17
18
  import { DriveNodeView, NodeProcessor, ReactorDriveClient, createReactorDriveResolvers, reactorDriveDocumentModelModule, reactorDriveSubgraphTypeDefs } from "@powerhousedao/reactor-drive";
18
19
  import { DEFAULT_RENOWN_URL, NodeKeyStorage, RENOWN_READ_MODEL_SUBGRAPH, RenownBuilder, RenownCryptoBuilder, createLocalCredentialVerifier, createSignatureVerifier } from "@renown/sdk/node";
19
20
  import { Kysely, PostgresDialect } from "kysely";
@@ -28,6 +29,8 @@ import os from "node:os";
28
29
  import { fileURLToPath } from "node:url";
29
30
  import { EnvVarProvider } from "@openfeature/env-var-provider";
30
31
  import { OpenFeature } from "@openfeature/server-sdk";
32
+ import { GraphQLError } from "graphql";
33
+ import { gql } from "graphql-tag";
31
34
  import { PGliteDialect } from "kysely-pglite-dialect";
32
35
  //#region src/pglite-version.ts
33
36
  const SUPPORTED_PG_MAJORS = [16, 17];
@@ -865,6 +868,495 @@ async function initFeatureFlags() {
865
868
  return OpenFeature.getClient();
866
869
  }
867
870
  //#endregion
871
+ //#region src/workflow/resolvers.ts
872
+ function requireAdmin(authorizationService, ctx) {
873
+ if (!authorizationService.isSupremeAdmin(ctx.user?.address)) throw new GraphQLError("Admin access required");
874
+ }
875
+ function parseJson(value) {
876
+ if (value === null) return null;
877
+ try {
878
+ return JSON.parse(value);
879
+ } catch {
880
+ return value;
881
+ }
882
+ }
883
+ function toStepRecord(row) {
884
+ return {
885
+ stepId: row.step_id,
886
+ stepKey: row.step_key,
887
+ blockType: row.block_type,
888
+ status: row.status,
889
+ input: parseJson(row.input),
890
+ output: parseJson(row.output),
891
+ port: row.port,
892
+ error: row.error
893
+ };
894
+ }
895
+ function toRunRecord(row, steps) {
896
+ return {
897
+ id: row.id,
898
+ workflowId: row.workflow_id,
899
+ workflowName: row.workflow_name,
900
+ workflowVersion: row.workflow_version,
901
+ triggerKind: row.trigger_kind,
902
+ triggerPayload: parseJson(row.trigger_payload),
903
+ status: row.status,
904
+ error: row.error,
905
+ startedAt: row.started_at,
906
+ endedAt: row.ended_at,
907
+ rerunOf: row.rerun_of,
908
+ steps: steps.map(toStepRecord)
909
+ };
910
+ }
911
+ const getResolvers = (runtime, authorizationService) => {
912
+ return {
913
+ Query: { workflowRuntime: () => ({}) },
914
+ WorkflowRuntimeQueries: {
915
+ health: () => "ok",
916
+ blockDescriptor: (_parent, args) => runtime.blockDescriptor(args.blockType),
917
+ blockOptions: (_parent, args, ctx) => runtime.blockOptions(args.blockType, args.propName, args.input, args.connectionId ?? void 0, ctx),
918
+ pieceCatalog: () => runtime.pieceCatalog(),
919
+ pieceActions: (_parent, args) => runtime.pieceActions(args.packageName),
920
+ pieceTriggers: (_parent, args) => runtime.pieceTriggers(args.packageName),
921
+ blockOutputTree: (_parent, args) => runtime.blockOutputTree(args.blockType, args.config),
922
+ pieceDetail: (_parent, args) => runtime.pieceDetail(args.packageName),
923
+ searchBlocks: (_parent, args) => runtime.searchBlocks(args.query, args.limit ?? void 0),
924
+ connections: (_parent, _args, ctx) => runtime.connections(ctx),
925
+ webhookEndpoint: (_parent, args, ctx) => runtime.webhookEndpoint(args.workflowId, ctx),
926
+ secret: async (_parent, args) => {
927
+ try {
928
+ return await (await runtime.secrets()).stat(args.ref);
929
+ } catch {
930
+ return null;
931
+ }
932
+ },
933
+ secrets: async () => (await runtime.secrets()).list(),
934
+ triggerStates: async (_parent, _args, ctx) => (await runtime.triggerStates(ctx)).map((row) => ({
935
+ workflowId: row.workflow_id,
936
+ blockType: row.block_type,
937
+ status: row.status,
938
+ intervalMs: row.interval_ms,
939
+ nextPollAt: row.next_poll_at,
940
+ lastPollAt: row.last_poll_at,
941
+ lastError: row.last_error,
942
+ consecutiveFailures: row.consecutive_failures
943
+ })),
944
+ runs: async (_parent, args, ctx) => (await runtime.runs(args, ctx)).map((record) => toRunRecord(record.row, record.steps)),
945
+ run: async (_parent, args, ctx) => {
946
+ const record = await runtime.run(args.id, ctx);
947
+ return record ? toRunRecord(record.row, record.steps) : null;
948
+ }
949
+ },
950
+ Mutation: { workflowRuntime: () => ({}) },
951
+ WorkflowRuntimeMutations: {
952
+ fire: (_parent, args, ctx) => runtime.fire(args.workflowId, args.payload, "manual", void 0, ctx),
953
+ testTrigger: (_parent, args, ctx) => runtime.testTrigger(args.workflowId, ctx),
954
+ rerun: (_parent, args, ctx) => runtime.rerun(args.runId, ctx),
955
+ createSecret: async (_parent, args, ctx) => {
956
+ requireAdmin(authorizationService, ctx);
957
+ return (await runtime.secrets()).create({
958
+ value: args.value,
959
+ label: args.label ?? void 0
960
+ });
961
+ },
962
+ rotateSecret: async (_parent, args, ctx) => {
963
+ requireAdmin(authorizationService, ctx);
964
+ return (await runtime.secrets()).rotate(args.ref, args.value);
965
+ },
966
+ deleteSecret: async (_parent, args, ctx) => {
967
+ requireAdmin(authorizationService, ctx);
968
+ await (await runtime.secrets()).delete(args.ref);
969
+ return true;
970
+ },
971
+ checkConnection: (_parent, args, ctx) => runtime.checkConnection(args.connectionId, ctx)
972
+ }
973
+ };
974
+ };
975
+ //#endregion
976
+ //#region src/workflow/schema.ts
977
+ const schema = gql`
978
+ """
979
+ WorkflowRuntime Queries
980
+ """
981
+ type WorkflowRuntimeQueries {
982
+ health: String!
983
+ """
984
+ Persisted runs, newest first. Scope them to one workflow, or to every
985
+ workflow a drive holds; workflowId wins when both are given.
986
+ """
987
+ runs(workflowId: String, driveId: String, limit: Int): [WorkflowRunRecord!]!
988
+ run(id: String!): WorkflowRunRecord
989
+ """
990
+ Action descriptor (props, auth) for a piece block type; null for core blocks.
991
+ """
992
+ blockDescriptor(blockType: String!): Unknown
993
+ """
994
+ Resolves a dynamic prop against the current config values: a DROPDOWN
995
+ yields { options, placeholder, disabled }, a DYNAMIC prop yields the
996
+ resolved sub-property descriptor list.
997
+ """
998
+ blockOptions(
999
+ blockType: String!
1000
+ propName: String!
1001
+ input: Unknown
1002
+ connectionId: String
1003
+ ): Unknown
1004
+ """
1005
+ All published Activepieces pieces with at least one action.
1006
+ """
1007
+ pieceCatalog: Unknown
1008
+ """
1009
+ A piece's actions, each with a ready-to-use blockType.
1010
+ """
1011
+ pieceActions(packageName: String!): Unknown
1012
+ """
1013
+ A piece's triggers, each with a ready-to-use "#trigger:" blockType.
1014
+ """
1015
+ pieceTriggers(packageName: String!): Unknown
1016
+ """
1017
+ Full piece detail (PieceMetadataModel-shaped), verbatim from the cloud API.
1018
+ """
1019
+ pieceDetail(packageName: String!): Unknown
1020
+ """
1021
+ Action/trigger name search across the catalog. The index builds lazily on
1022
+ first use; poll while status is "indexing".
1023
+ """
1024
+ searchBlocks(query: String!, limit: Int): BlockSearchResult!
1025
+ """
1026
+ Health of every registered piece trigger (poll schedule, errors).
1027
+ """
1028
+ triggerStates: [TriggerStateRecord!]!
1029
+ """
1030
+ Authored output shape of a block (SDL / outputSchema / sampleData).
1031
+ """
1032
+ blockOutputTree(blockType: String!, config: Unknown): Unknown
1033
+ """
1034
+ Every powerhouse/connection document, for connection pickers.
1035
+ """
1036
+ connections: [ConnectionRecord!]!
1037
+ """
1038
+ The webhook endpoint for a workflow: the URL to hand the provider. Minted
1039
+ on first ask, whether or not the workflow is armed — an author needs the
1040
+ URL before enabling, and the armed field carries that difference. Null
1041
+ only when the host has no webhook service.
1042
+ """
1043
+ webhookEndpoint(workflowId: String!): WebhookEndpointRecord
1044
+ """
1045
+ Secret metadata (label, version, status). Never the value.
1046
+ """
1047
+ secret(ref: String!): SecretRecord
1048
+ secrets: [SecretRecord!]!
1049
+ }
1050
+
1051
+ type BlockSearchHit {
1052
+ blockType: String!
1053
+ pieceName: String!
1054
+ pieceDisplayName: String!
1055
+ logoUrl: String!
1056
+ displayName: String!
1057
+ description: String!
1058
+ "action | trigger"
1059
+ kind: String!
1060
+ "Triggers only: POLLING | WEBHOOK | APP_WEBHOOK"
1061
+ strategy: String
1062
+ }
1063
+
1064
+ type BlockSearchResult {
1065
+ "ready | indexing | error"
1066
+ status: String!
1067
+ hits: [BlockSearchHit!]!
1068
+ indexedPieces: Int!
1069
+ error: String
1070
+ }
1071
+
1072
+ type SecretRecord {
1073
+ ref: String!
1074
+ label: String
1075
+ version: Int!
1076
+ status: String!
1077
+ createdAt: String!
1078
+ updatedAt: String!
1079
+ }
1080
+
1081
+ type ConnectionRecord {
1082
+ id: String!
1083
+ name: String!
1084
+ connectorId: String!
1085
+ authType: String!
1086
+ status: String!
1087
+ accountLabel: String
1088
+ }
1089
+
1090
+ type ConnectionCheckResult {
1091
+ ok: Boolean!
1092
+ detail: String
1093
+ accountLabel: String
1094
+ }
1095
+
1096
+ type WebhookEndpointRecord {
1097
+ workflowId: String!
1098
+ url: String!
1099
+ """
1100
+ False when the reactor does not know its own public origin, so the url
1101
+ above is a bare path no provider can call: the author supplies the origin.
1102
+ """
1103
+ absoluteUrl: Boolean!
1104
+ "True while the workflow is ENABLED with a valid webhook trigger"
1105
+ armed: Boolean!
1106
+ createdAt: String!
1107
+ }
1108
+
1109
+ type TriggerStateRecord {
1110
+ workflowId: String!
1111
+ blockType: String!
1112
+ status: String!
1113
+ intervalMs: Int!
1114
+ nextPollAt: String
1115
+ lastPollAt: String
1116
+ lastError: String
1117
+ consecutiveFailures: Int!
1118
+ }
1119
+
1120
+ type Query {
1121
+ workflowRuntime: WorkflowRuntimeQueries!
1122
+ }
1123
+
1124
+ type WorkflowStepRunRecord {
1125
+ stepId: String!
1126
+ stepKey: String!
1127
+ blockType: String!
1128
+ status: String!
1129
+ input: Unknown
1130
+ output: Unknown
1131
+ port: String
1132
+ error: String
1133
+ }
1134
+
1135
+ type WorkflowRunRecord {
1136
+ id: String!
1137
+ workflowId: String!
1138
+ workflowName: String!
1139
+ workflowVersion: Int!
1140
+ triggerKind: String!
1141
+ triggerPayload: Unknown
1142
+ status: String!
1143
+ error: String
1144
+ startedAt: String!
1145
+ endedAt: String
1146
+ rerunOf: String
1147
+ steps: [WorkflowStepRunRecord!]!
1148
+ }
1149
+
1150
+ type WorkflowStepRun {
1151
+ stepId: String!
1152
+ key: String!
1153
+ blockType: String!
1154
+ status: String!
1155
+ input: Unknown
1156
+ output: Unknown
1157
+ port: String
1158
+ error: String
1159
+ }
1160
+
1161
+ type WorkflowRunPayload {
1162
+ runId: String
1163
+ status: String!
1164
+ error: String
1165
+ steps: [WorkflowStepRun!]!
1166
+ }
1167
+
1168
+ """
1169
+ WorkflowRuntime Mutations
1170
+ """
1171
+ type WorkflowRuntimeMutations {
1172
+ """
1173
+ Fires a workflow's core#manual trigger and runs it to completion.
1174
+ """
1175
+ fire(workflowId: String!, payload: Unknown): WorkflowRunPayload!
1176
+ """
1177
+ Runs a piece trigger's test hook; sample items, no cursor changes.
1178
+ """
1179
+ testTrigger(workflowId: String!): Unknown
1180
+ """
1181
+ Resumes a FAILED run: succeeded steps replay from the journal,
1182
+ execution restarts at the failure. Produces a new run.
1183
+ """
1184
+ rerun(runId: String!): WorkflowRunPayload!
1185
+ """
1186
+ Mints a managed secret and returns its ref; the value is stored
1187
+ encrypted and is never readable back over any API.
1188
+ """
1189
+ createSecret(value: String!, label: String): SecretRecord!
1190
+ """
1191
+ Replaces the value behind an existing ref; documents stay untouched.
1192
+ """
1193
+ rotateSecret(ref: String!, value: String!): SecretRecord!
1194
+ """
1195
+ Tombstones a secret; its value becomes unrecoverable.
1196
+ """
1197
+ deleteSecret(ref: String!): Boolean!
1198
+ """
1199
+ Runs the piece's app.checkConnection against the connection's
1200
+ credentials and records the outcome on the connection document.
1201
+ """
1202
+ checkConnection(connectionId: String!): ConnectionCheckResult!
1203
+ }
1204
+
1205
+ type Mutation {
1206
+ workflowRuntime: WorkflowRuntimeMutations!
1207
+ }
1208
+ `;
1209
+ //#endregion
1210
+ //#region src/workflow/subgraph.ts
1211
+ /** The runtime's read/write surface. The runtime itself is composed by the
1212
+ * host, so the subgraph only serves what it is handed. */
1213
+ function createWorkflowRuntimeSubgraph(runtime) {
1214
+ return class WorkflowRuntimeSubgraph extends BaseSubgraph {
1215
+ name = "workflow-runtime";
1216
+ typeDefs = schema;
1217
+ resolvers = getResolvers(runtime, this.authorizationService);
1218
+ additionalContextFields = {};
1219
+ };
1220
+ }
1221
+ //#endregion
1222
+ //#region src/workflow-runtime.mts
1223
+ /** The npm name the workflow package owns: its HTTP namespace and its models. */
1224
+ const WORKFLOW_PACKAGE_NAME = "@powerhousedao/workflow";
1225
+ /** The env var and OpenFeature flag key that turns workflows on. */
1226
+ const PH_WORKFLOWS_ENABLED = "PH_WORKFLOWS_ENABLED";
1227
+ /** Precedence, unchanged from the reactor-api resolver this replaces: the
1228
+ * host's option, then PH_WORKFLOWS_ENABLED, then the config file, then off. */
1229
+ async function resolveWorkflowsEnabled({ featureFlags, override, configEnabled = false, env = process.env }) {
1230
+ if (override !== void 0) return override;
1231
+ const raw = env[PH_WORKFLOWS_ENABLED]?.trim();
1232
+ if (raw === "1") return true;
1233
+ if (raw === "0") return false;
1234
+ return featureFlags.getBooleanValue(PH_WORKFLOWS_ENABLED, configEnabled);
1235
+ }
1236
+ async function assertWorkflowPackageLoadable(load = () => import("@powerhousedao/workflow/document-models")) {
1237
+ try {
1238
+ await load();
1239
+ } catch (error) {
1240
+ throw new Error(`Workflows are enabled but ${WORKFLOW_PACKAGE_NAME} could not be loaded`, { cause: error });
1241
+ }
1242
+ }
1243
+ function readAssertion(authorizationService, reactorClient) {
1244
+ const resolveCanonical = createCanonicalDocumentIdResolver(reactorClient);
1245
+ return async (identifier, caller) => {
1246
+ const ctx = caller;
1247
+ if (authorizationService.isSupremeAdmin(ctx.user?.address)) return;
1248
+ if (authorizationService.config.policy !== AuthorizationPolicy.DOCUMENT_PERMISSIONS) throw new ForbiddenError();
1249
+ let documentId;
1250
+ try {
1251
+ documentId = await resolveCanonical(identifier);
1252
+ } catch {
1253
+ throw new ForbiddenError();
1254
+ }
1255
+ if (!await authorizationService.canRead(documentId, ctx.user?.address)) throw new ForbiddenError("to read this document");
1256
+ };
1257
+ }
1258
+ /** The same, for a design-time call that writes what it names. */
1259
+ function writeAssertion(authorizationService, reactorClient) {
1260
+ const resolveCanonical = createCanonicalDocumentIdResolver(reactorClient);
1261
+ return async (identifier, caller) => {
1262
+ const ctx = caller;
1263
+ if (authorizationService.isSupremeAdmin(ctx.user?.address)) return;
1264
+ if (authorizationService.config.policy !== AuthorizationPolicy.DOCUMENT_PERMISSIONS) throw new ForbiddenError();
1265
+ let documentId;
1266
+ try {
1267
+ documentId = await resolveCanonical(identifier);
1268
+ } catch {
1269
+ throw new ForbiddenError();
1270
+ }
1271
+ if (!await authorizationService.canWrite(documentId, ctx.user?.address)) throw new ForbiddenError("to write this document");
1272
+ };
1273
+ }
1274
+ /** Whether the workflow document really references the attachment. A step
1275
+ * carries no caller, so the relationship is the whole check. */
1276
+ function attachmentRefCheck(deps) {
1277
+ const resolveCanonical = createCanonicalDocumentIdResolver(deps.reactorClient);
1278
+ const references = deps.attachmentReferences;
1279
+ const projection = deps.attachmentReferenceProjection;
1280
+ return async (documentId, ref) => {
1281
+ if (!references || projection?.status !== "available") return false;
1282
+ let parsed;
1283
+ try {
1284
+ parsed = parseRef(ref);
1285
+ } catch {
1286
+ return false;
1287
+ }
1288
+ if (parsed.version !== 1) return false;
1289
+ const canonicalRef = createRef(parsed.hash.toLowerCase());
1290
+ try {
1291
+ return await references.hasReference(await resolveCanonical(documentId), canonicalRef);
1292
+ } catch {
1293
+ return false;
1294
+ }
1295
+ };
1296
+ }
1297
+ async function registerWorkflowTriggersReadModel(engine, runtime, clientModule) {
1298
+ const reactorModule = clientModule?.reactorModule;
1299
+ if (!reactorModule) return {
1300
+ status: "unavailable",
1301
+ reason: "in-process-reactor-module-unavailable"
1302
+ };
1303
+ const coordinator = reactorModule.readModelCoordinator;
1304
+ if (!supportsLiveReadModelRegistration(coordinator)) return {
1305
+ status: "unavailable",
1306
+ reason: "live-read-model-registration-unsupported"
1307
+ };
1308
+ const readModel = new engine.WorkflowTriggersReadModel(reactorModule.database.withSchema(REACTOR_SCHEMA), reactorModule.operationIndex, reactorModule.writeCache, reactorModule.processorManagerConsistencyTracker, runtime);
1309
+ await readModel.init();
1310
+ coordinator.addReadModel(readModel, engine.WORKFLOW_TRIGGERS_READ_MODEL_STAGE);
1311
+ return { status: "available" };
1312
+ }
1313
+ function bindPackagePieces(registry, source) {
1314
+ const apply = (byPackage) => {
1315
+ registry.setPieces([...byPackage.values()].flat());
1316
+ };
1317
+ apply(source.getPieces());
1318
+ source.onPiecesChange(apply);
1319
+ }
1320
+ async function composeWorkflowRuntime(deps) {
1321
+ const load = deps.load ?? (() => import("@powerhousedao/reactor-workflow"));
1322
+ let engine;
1323
+ try {
1324
+ engine = await load();
1325
+ } catch (error) {
1326
+ throw new Error("Workflows are enabled but @powerhousedao/reactor-workflow could not be loaded", { cause: error });
1327
+ }
1328
+ engine.setPieceRegistryUrl(deps.pieceRegistryUrl);
1329
+ if (deps.pieces) bindPackagePieces(engine.packagePieces, deps.pieces);
1330
+ const runtime = engine.createWorkflowRuntime({
1331
+ relationalDb: deps.relationalDb,
1332
+ reactorClient: deps.reactorClient,
1333
+ assertCanRead: readAssertion(deps.authorizationService, deps.reactorClient),
1334
+ assertCanWrite: writeAssertion(deps.authorizationService, deps.reactorClient),
1335
+ webhooks: deps.webhooks,
1336
+ attachments: deps.attachments,
1337
+ canReadAttachmentRef: attachmentRefCheck(deps),
1338
+ logger: deps.logger
1339
+ });
1340
+ const triggers = await registerWorkflowTriggersReadModel(engine, runtime, deps.clientModule);
1341
+ if (triggers.status === "available") deps.logger.info(`Workflow trigger read model registered (${engine.WORKFLOW_TRIGGERS_READ_MODEL}, ${engine.WORKFLOW_TRIGGERS_READ_MODEL_STAGE})`);
1342
+ else deps.logger.error("Workflow document triggers are NOT armed (@reason): this reactor's read-model coordinator takes no live registration, so no document operation reaches the runtime. Webhook and schedule triggers are unaffected.", triggers.reason);
1343
+ let stopped = false;
1344
+ return {
1345
+ subgraph: createWorkflowRuntimeSubgraph(runtime),
1346
+ triggers,
1347
+ async start() {
1348
+ await runtime.registerWebhookEndpoint();
1349
+ runtime.startTriggerSupervisor();
1350
+ },
1351
+ stop() {
1352
+ if (stopped) return Promise.resolve();
1353
+ stopped = true;
1354
+ runtime.shutdown();
1355
+ return Promise.resolve();
1356
+ }
1357
+ };
1358
+ }
1359
+ //#endregion
868
1360
  //#region src/pglite-dialect.ts
869
1361
  var ClosablePGliteDialect = class extends PGliteDialect {
870
1362
  #pglite;
@@ -1077,6 +1569,13 @@ function isPortAvailable(port) {
1077
1569
  function resolveConfigPath(configFile) {
1078
1570
  return configFile ?? path$1.join(process.cwd(), "powerhouse.config.json");
1079
1571
  }
1572
+ function readConfigWorkflowsEnabled(configPath) {
1573
+ try {
1574
+ return getConfig(configPath).workflows?.enabled ?? false;
1575
+ } catch {
1576
+ return false;
1577
+ }
1578
+ }
1080
1579
  async function resolveServerPort(requested, strictPort, logger) {
1081
1580
  if (strictPort) return requested;
1082
1581
  for (let i = 0; i < PORT_FALLBACK_ATTEMPTS; i++) {
@@ -1224,6 +1723,11 @@ async function initServer(serverPort, options, renown, renownConfig) {
1224
1723
  reactorLogger.error(`Projection worker ${shardId} died; shutting down so the supervisor restarts a healthy process`, reason);
1225
1724
  process.kill(process.pid, "SIGTERM");
1226
1725
  };
1726
+ const workflowsEnabled = options.workflows?.enabled === true;
1727
+ if (workflowsEnabled) {
1728
+ await assertWorkflowPackageLoadable();
1729
+ if (!packages.includes("@powerhousedao/workflow")) packages.push(WORKFLOW_PACKAGE_NAME);
1730
+ }
1227
1731
  let ownedReactorModule;
1228
1732
  const initializeClient = async (documentModels, { attachmentReferenceWriter, upgradeManifests }) => {
1229
1733
  if (options.reactor) {
@@ -1409,6 +1913,43 @@ async function initServer(serverPort, options, renown, renownConfig) {
1409
1913
  if (process.env.SENTRY_DSN) api.httpAdapter.setupSentryErrorHandler(Sentry);
1410
1914
  const { client, graphqlManager, documentModelRegistry } = api;
1411
1915
  const lateSubgraphs = [];
1916
+ let workflows;
1917
+ if (workflowsEnabled) {
1918
+ workflows = await composeWorkflowRuntime({
1919
+ reactorClient: client,
1920
+ clientModule: options.reactor ?? ownedReactorModule,
1921
+ relationalDb: api.relationalDb,
1922
+ attachments: createAttachmentClient(api.attachments.service),
1923
+ attachmentReferences: api.attachmentReferenceIndex.store,
1924
+ attachmentReferenceProjection: api.attachmentReferenceProjection,
1925
+ webhooks: api.httpRoutes.scopeFor(WORKFLOW_PACKAGE_NAME).webhooks,
1926
+ authorizationService: api.authorizationService,
1927
+ pieces: api.packageManager,
1928
+ pieceRegistryUrl: registryUrl,
1929
+ logger: logger.child(["workflow-runtime"])
1930
+ });
1931
+ const WorkflowRuntimeSubgraph = workflows.subgraph;
1932
+ const workflowSubgraph = new WorkflowRuntimeSubgraph({
1933
+ reactorClient: client,
1934
+ http: graphqlManager.scopeForPackage(WORKFLOW_PACKAGE_NAME),
1935
+ relationalDb: api.relationalDb,
1936
+ analyticsStore: void 0,
1937
+ graphqlManager,
1938
+ syncManager: api.syncManager,
1939
+ authorizationService: graphqlManager.getAuthorizationService(),
1940
+ path: graphqlManager.getBasePath()
1941
+ });
1942
+ lateSubgraphs.push(graphqlManager.registerSubgraphInstance(workflowSubgraph, "graphql", false).catch((error) => {
1943
+ logger.error("Failed to register workflow-runtime subgraph: @error", error);
1944
+ }));
1945
+ await workflows.start();
1946
+ logger.info("Workflow runtime started");
1947
+ }
1948
+ const shutdown = async () => {
1949
+ await workflows?.stop();
1950
+ await api.dispose();
1951
+ };
1952
+ apiRef.current = { dispose: shutdown };
1412
1953
  if (httpLoader) {
1413
1954
  const packageManagementService = new PackageManagementService({
1414
1955
  defaultRegistryUrl: registryUrl,
@@ -1417,7 +1958,9 @@ async function initServer(serverPort, options, renown, renownConfig) {
1417
1958
  packageManager: api.packageManager
1418
1959
  });
1419
1960
  packageManagementService.setOnModelsChanged(() => {
1420
- graphqlManager.regenerateDocumentModelSubgraphs().catch(logger.error);
1961
+ graphqlManager.regenerateDocumentModelSubgraphs().catch((error) => {
1962
+ logger.error("Failed to regenerate document model subgraphs: @error", error);
1963
+ });
1421
1964
  });
1422
1965
  const packagesSubgraph = new PackagesSubgraph({
1423
1966
  relationalDb: void 0,
@@ -1490,9 +2033,10 @@ async function initServer(serverPort, options, renown, renownConfig) {
1490
2033
  reactor: client,
1491
2034
  attachmentService,
1492
2035
  attachmentReferenceProjection: api.attachmentReferenceProjection,
2036
+ workflowTriggers: workflows?.triggers,
1493
2037
  renown,
1494
2038
  port: serverPort,
1495
- shutdown: () => api.dispose()
2039
+ shutdown
1496
2040
  };
1497
2041
  }
1498
2042
  /**
@@ -1520,6 +2064,13 @@ const startSwitchboard = async (options = {}) => {
1520
2064
  const enableDocumentModelSubgraphs = await featureFlags.getBooleanValue(DOCUMENT_MODEL_SUBGRAPHS_ENABLED, options.enableDocumentModelSubgraphs ?? DOCUMENT_MODEL_SUBGRAPHS_ENABLED_DEFAULT);
1521
2065
  options.enableDocumentModelSubgraphs = enableDocumentModelSubgraphs;
1522
2066
  const requireSignatures = options.identity?.requireSignatures ?? await featureFlags.getBooleanValue(REQUIRE_SIGNATURES, REQUIRE_SIGNATURES_DEFAULT);
2067
+ const configPathForFlags = resolveConfigPath(options.configFile);
2068
+ const workflowsEnabled = await resolveWorkflowsEnabled({
2069
+ featureFlags,
2070
+ override: options.workflows?.enabled,
2071
+ configEnabled: readConfigWorkflowsEnabled(configPathForFlags)
2072
+ });
2073
+ options.workflows = { enabled: workflowsEnabled };
1523
2074
  const renownConfig = resolveRenownConfig(getConfig(resolveConfigPath(options.configFile)).auth?.renown, process.env, logger);
1524
2075
  options.identity = {
1525
2076
  ...options.identity,
@@ -1528,7 +2079,8 @@ const startSwitchboard = async (options = {}) => {
1528
2079
  };
1529
2080
  logger.info("Feature flags: @flags", JSON.stringify({
1530
2081
  DOCUMENT_MODEL_SUBGRAPHS_ENABLED: enableDocumentModelSubgraphs,
1531
- REQUIRE_SIGNATURES: requireSignatures
2082
+ REQUIRE_SIGNATURES: requireSignatures,
2083
+ PH_WORKFLOWS_ENABLED: workflowsEnabled
1532
2084
  }, null, 2));
1533
2085
  let renown = null;
1534
2086
  try {
@@ -1549,5 +2101,5 @@ if (import.meta.main) await startSwitchboard();
1549
2101
  //#endregion
1550
2102
  export { parseForcePgVersion as a, applySwitchboardReactorDefaults as i, isPortAvailable as n, startSwitchboard as r, deriveAttachmentServiceConfig as t };
1551
2103
 
1552
- //# sourceMappingURL=server-B2r6AQ4Q.mjs.map
1553
- //# debugId=0a17ca6d-016f-53aa-9705-cef103da6f09
2104
+ //# sourceMappingURL=server-C6VtW18k.mjs.map
2105
+ //# debugId=75db5742-026a-585f-b76c-c4e4981bb421