@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.
- package/.tsbuild/test/workflow/harness.d.ts +45 -0
- package/.tsbuild/test/workflow/harness.d.ts.map +1 -0
- package/.tsbuild/test/workflow/resolvers.test.d.ts +2 -0
- package/.tsbuild/test/workflow/resolvers.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-dedupe.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-dedupe.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-signed.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-signed.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-unsigned.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-unsigned.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow-runtime.test.d.ts +2 -0
- package/.tsbuild/test/workflow-runtime.test.d.ts.map +1 -0
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +151 -0
- package/dist/index.mjs +1 -1
- package/dist/{server-B2r6AQ4Q.mjs → server-C6VtW18k.mjs} +560 -8
- package/dist/server-C6VtW18k.mjs.map +1 -0
- package/dist/server.d.mts +20 -2
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +3 -3
- package/package.json +17 -13
- package/test/workflow/harness.ts +237 -0
- package/test/workflow/resolvers.test.ts +105 -0
- package/test/workflow/webhook-dedupe.test.ts +295 -0
- package/test/workflow/webhook-signed.test.ts +509 -0
- package/test/workflow/webhook-unsigned.test.ts +332 -0
- package/test/workflow-runtime.test.ts +472 -0
- package/tsconfig.json +6 -0
- package/dist/server-B2r6AQ4Q.mjs.map +0 -1
|
@@ -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]="
|
|
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(
|
|
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
|
|
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-
|
|
1553
|
-
//# debugId=
|
|
2104
|
+
//# sourceMappingURL=server-C6VtW18k.mjs.map
|
|
2105
|
+
//# debugId=75db5742-026a-585f-b76c-c4e4981bb421
|