@powerhousedao/switchboard 6.2.3-dev.30 → 6.2.3-dev.31

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]="49b28650-fe00-57f4-844b-978a35d80e91")}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]="ec5120f6-92b3-53b4-bab1-94d88478784d")}catch(e){}}();
3
3
  import { n as addDefaultReactorDrive, r as isPostgresUrl, t as addDefaultDrive } from "./utils-uaBlzyjS.mjs";
4
4
  import { createRequire, register } from "node:module";
5
5
  import { ReactorInstrumentation } from "@powerhousedao/opentelemetry-instrumentation-reactor";
@@ -31,7 +31,7 @@ import os from "node:os";
31
31
  import { fileURLToPath } from "node:url";
32
32
  import { EnvVarProvider } from "@openfeature/env-var-provider";
33
33
  import { OpenFeature } from "@openfeature/server-sdk";
34
- import { GraphQLError } from "graphql";
34
+ import { GraphQLError, Kind } from "graphql";
35
35
  import { gql } from "graphql-tag";
36
36
  import { PGliteDialect } from "kysely-pglite-dialect";
37
37
  //#region src/pglite-version.ts
@@ -1112,10 +1112,115 @@ async function initFeatureFlags() {
1112
1112
  return OpenFeature.getClient();
1113
1113
  }
1114
1114
  //#endregion
1115
+ //#region src/workflow/oauth-callback.ts
1116
+ const OAUTH_CALLBACK_PATH = "oauth/callback";
1117
+ function escapeHtml(text) {
1118
+ return text.replace(/[&<>"']/g, (char) => ({
1119
+ "&": "&amp;",
1120
+ "<": "&lt;",
1121
+ ">": "&gt;",
1122
+ "\"": "&quot;",
1123
+ "'": "&#39;"
1124
+ })[char]);
1125
+ }
1126
+ function resultPage(ok, detail) {
1127
+ const title = ok ? "Connected" : "Sign-in failed";
1128
+ return `<!doctype html>
1129
+ <html><head><meta charset="utf-8"><title>${title}</title>
1130
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1131
+ <style>body{font:15px system-ui,sans-serif;margin:0;display:grid;place-items:center;min-height:100vh;background:#fafafa;color:#222}main{max-width:28rem;padding:1.5rem;text-align:center}h1{font-size:1.1rem}</style>
1132
+ </head><body><main><h1>${title}</h1><p>${ok ? "You can close this window." : escapeHtml(detail ?? "The sign-in did not finish.")}</p></main>
1133
+ ${ok ? "<script>setTimeout(function(){window.close()},600)<\/script>" : ""}
1134
+ </body></html>`;
1135
+ }
1136
+ function registerOAuthCallback(http, runtime) {
1137
+ return http.get(OAUTH_CALLBACK_PATH, { auth: "public" }, async (request) => {
1138
+ const params = new URL(request.url).searchParams;
1139
+ const state = params.get("state");
1140
+ const result = state ? await runtime.completeOAuth({
1141
+ state,
1142
+ code: params.get("code") ?? void 0,
1143
+ error: params.get("error") ?? void 0,
1144
+ errorDescription: params.get("error_description") ?? void 0
1145
+ }) : {
1146
+ ok: false,
1147
+ detail: "The provider sent no state",
1148
+ returnUrl: null
1149
+ };
1150
+ if (result.returnUrl && state) {
1151
+ const back = new URL(result.returnUrl);
1152
+ back.searchParams.set("ph_oauth", state);
1153
+ return new Response(null, {
1154
+ status: 303,
1155
+ headers: {
1156
+ location: back.href,
1157
+ "cache-control": "no-store"
1158
+ }
1159
+ });
1160
+ }
1161
+ return new Response(resultPage(result.ok, result.detail), {
1162
+ status: result.ok ? 200 : 400,
1163
+ headers: {
1164
+ "content-type": "text/html; charset=utf-8",
1165
+ "cache-control": "no-store",
1166
+ "referrer-policy": "no-referrer"
1167
+ }
1168
+ });
1169
+ });
1170
+ }
1171
+ function callbackUrlOf(http) {
1172
+ return `${http.baseUrl.replace(/\/+$/, "")}/${OAUTH_CALLBACK_PATH}`;
1173
+ }
1174
+ //#endregion
1115
1175
  //#region src/workflow/resolvers.ts
1176
+ function blockRef(input) {
1177
+ if (input.kind !== "action" && input.kind !== "trigger") throw new GraphQLError(`A block's kind is "action" or "trigger", got "${input.kind}"`);
1178
+ return {
1179
+ pieceName: input.pieceName,
1180
+ pieceVersion: input.pieceVersion,
1181
+ name: input.name,
1182
+ kind: input.kind
1183
+ };
1184
+ }
1185
+ async function cursorAware(call) {
1186
+ try {
1187
+ return await call();
1188
+ } catch (error) {
1189
+ if (error instanceof Error && error.name === "InvalidRunCursorError") throw new GraphQLError(error.message, { extensions: { code: "BAD_USER_INPUT" } });
1190
+ throw error;
1191
+ }
1192
+ }
1193
+ function selectsStepData(selections, path) {
1194
+ let level = selections;
1195
+ for (const name of [...path, "steps"]) {
1196
+ const next = [];
1197
+ for (const set of level) for (const selection of set.selections) {
1198
+ if (selection.kind !== Kind.FIELD) return true;
1199
+ if (selection.name.value === name && selection.selectionSet) next.push(selection.selectionSet);
1200
+ }
1201
+ if (next.length === 0) return false;
1202
+ level = next;
1203
+ }
1204
+ return level.some((set) => set.selections.some((selection) => selection.kind !== Kind.FIELD || selection.name.value === "input" || selection.name.value === "output"));
1205
+ }
1206
+ function readsStepData(info, path) {
1207
+ if (!info) return true;
1208
+ return selectsStepData(info.fieldNodes.flatMap((node) => node.selectionSet ? [node.selectionSet] : []), path);
1209
+ }
1116
1210
  function requireAdmin(authorizationService, ctx) {
1117
1211
  if (!authorizationService.isSupremeAdmin(ctx.user?.address)) throw new GraphQLError("Admin access required");
1118
1212
  }
1213
+ async function syncAware(call) {
1214
+ try {
1215
+ return await call();
1216
+ } catch (error) {
1217
+ if (error instanceof Error && error.name === "WorkflowSyncingError") throw new GraphQLError(error.message, { extensions: {
1218
+ code: "WORKFLOW_SYNCING",
1219
+ retryable: true
1220
+ } });
1221
+ throw error;
1222
+ }
1223
+ }
1119
1224
  function parseJson(value) {
1120
1225
  if (value === null) return null;
1121
1226
  try {
@@ -1128,14 +1233,19 @@ function toStepRecord(row) {
1128
1233
  return {
1129
1234
  stepId: row.step_id,
1130
1235
  stepKey: row.step_key,
1131
- blockType: row.block_type,
1236
+ pieceName: row.piece_name,
1237
+ blockName: row.block_name,
1132
1238
  status: row.status,
1133
1239
  input: parseJson(row.input),
1134
1240
  output: parseJson(row.output),
1135
1241
  port: row.port,
1136
1242
  error: row.error,
1137
1243
  startedAt: row.started_at,
1138
- endedAt: row.ended_at
1244
+ endedAt: row.ended_at,
1245
+ pieceVersion: row.piece_version,
1246
+ pieceSource: row.piece_source,
1247
+ versionMatch: row.version_match,
1248
+ versionNote: row.version_note
1139
1249
  };
1140
1250
  }
1141
1251
  function toRunRecord(row, steps) {
@@ -1151,24 +1261,56 @@ function toRunRecord(row, steps) {
1151
1261
  startedAt: row.started_at,
1152
1262
  endedAt: row.ended_at,
1153
1263
  rerunOf: row.rerun_of,
1264
+ warnings: row.warnings,
1265
+ warningNotes: parseJson(row.warning_notes) ?? [],
1154
1266
  steps: steps.map(toStepRecord)
1155
1267
  };
1156
1268
  }
1157
- const getResolvers = (runtime, authorizationService) => {
1269
+ function isAbsolute(url) {
1270
+ return /^https?:\/\//i.test(url);
1271
+ }
1272
+ function redirectUriFor(routing, requested) {
1273
+ if (!routing) throw new GraphQLError("This host serves no OAuth2 callback");
1274
+ if (isAbsolute(routing.callbackUrl)) return routing.callbackUrl;
1275
+ if (!requested) throw new GraphQLError("This host does not know its public URL; pass redirectUri");
1276
+ let url;
1277
+ try {
1278
+ url = new URL(requested);
1279
+ } catch {
1280
+ throw new GraphQLError(`"${requested}" is not a URL`);
1281
+ }
1282
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.pathname !== routing.callbackUrl || url.search !== "" || url.hash !== "") throw new GraphQLError(`redirectUri must be this host's ${routing.callbackUrl}`);
1283
+ return url.href;
1284
+ }
1285
+ function returnUrlFor(requested, ctx) {
1286
+ if (!requested) return void 0;
1287
+ const origin = ctx.headers.origin;
1288
+ let url;
1289
+ try {
1290
+ url = new URL(requested);
1291
+ } catch {
1292
+ url = void 0;
1293
+ }
1294
+ if (!url || !origin || url.origin !== origin) throw new GraphQLError("returnUrl must be on the requesting page's origin");
1295
+ return url.href;
1296
+ }
1297
+ const getResolvers = (runtime, authorizationService, oauth) => {
1158
1298
  return {
1159
1299
  Query: { workflowRuntime: () => ({}) },
1160
1300
  WorkflowRuntimeQueries: {
1161
1301
  health: () => "ok",
1162
- blockDescriptor: (_parent, args) => runtime.blockDescriptor(args.blockType),
1163
- blockOptions: (_parent, args, ctx) => runtime.blockOptions(args.blockType, args.propName, args.input, args.connectionId ?? void 0, ctx, args.searchValue ?? void 0),
1302
+ blockDescriptor: (_parent, args) => runtime.blockDescriptor(blockRef(args.block)),
1303
+ blockOptions: (_parent, args, ctx) => runtime.blockOptions(blockRef(args.block), args.propName, args.input, args.connectionId ?? void 0, ctx, args.searchValue ?? void 0),
1164
1304
  pieceCatalog: () => runtime.pieceCatalog(),
1165
- pieceActions: (_parent, args) => runtime.pieceActions(args.packageName),
1166
- pieceTriggers: (_parent, args) => runtime.pieceTriggers(args.packageName),
1167
- blockOutputTree: (_parent, args) => runtime.blockOutputTree(args.blockType, args.config),
1168
- pieceDetail: (_parent, args) => runtime.pieceDetail(args.packageName),
1305
+ pieceActions: (_parent, args) => runtime.pieceActions(args.packageName, args.version ?? void 0),
1306
+ pieceTriggers: (_parent, args) => runtime.pieceTriggers(args.packageName, args.version ?? void 0),
1307
+ blockOutputTree: (_parent, args) => runtime.blockOutputTree(blockRef(args.block), args.config),
1308
+ stepOutputTree: (_parent, args, ctx) => runtime.stepOutputTree(args.workflowId, args.stepId, ctx),
1309
+ blockResolutions: (_parent, args, ctx) => runtime.blockResolutions(args.workflowId, ctx),
1310
+ pieceDetail: (_parent, args) => runtime.pieceDetail(args.packageName, args.version ?? void 0),
1169
1311
  searchBlocks: (_parent, args) => runtime.searchBlocks(args.query, args.limit ?? void 0),
1170
1312
  connections: (_parent, _args, ctx) => runtime.connections(ctx),
1171
- webhookEndpoint: (_parent, args, ctx) => runtime.webhookEndpoint(args.workflowId, ctx),
1313
+ webhookEndpoint: (_parent, args, ctx) => syncAware(() => runtime.webhookEndpoint(args.workflowId, ctx, { driveId: args.driveId ?? void 0 })),
1172
1314
  secret: async (_parent, args) => {
1173
1315
  try {
1174
1316
  return await (await runtime.secrets()).stat(args.ref);
@@ -1177,17 +1319,45 @@ const getResolvers = (runtime, authorizationService) => {
1177
1319
  }
1178
1320
  },
1179
1321
  secrets: async () => (await runtime.secrets()).list(),
1322
+ oauthRedirectUri: () => oauth?.callbackUrl ?? null,
1323
+ oauthAttempt: (_parent, args, ctx) => runtime.oauthAttempt(args.state, ctx),
1180
1324
  triggerStates: async (_parent, _args, ctx) => (await runtime.triggerStates(ctx)).map((row) => ({
1181
1325
  workflowId: row.workflow_id,
1182
- blockType: row.block_type,
1326
+ pieceName: row.piece_name,
1327
+ triggerName: row.trigger_name,
1183
1328
  status: row.status,
1184
1329
  intervalMs: row.interval_ms,
1185
1330
  nextPollAt: row.next_poll_at,
1186
1331
  lastPollAt: row.last_poll_at,
1187
1332
  lastError: row.last_error,
1188
- consecutiveFailures: row.consecutive_failures
1333
+ consecutiveFailures: row.consecutive_failures,
1334
+ nextRenewAt: row.next_renew_at,
1335
+ renewError: row.renew_error,
1336
+ renewFailures: row.renew_failures,
1337
+ pieceVersion: row.piece_version,
1338
+ pieceSource: row.piece_source,
1339
+ versionMatch: row.version_match,
1340
+ versionNote: row.version_note
1189
1341
  })),
1190
- runs: async (_parent, args, ctx) => (await runtime.runs(args, ctx)).map((record) => toRunRecord(record.row, record.steps)),
1342
+ runs: async (_parent, args, ctx, info) => (await runtime.runs({
1343
+ ...args,
1344
+ withStepData: readsStepData(info, [])
1345
+ }, ctx)).map((record) => toRunRecord(record.row, record.steps)),
1346
+ runsPage: async (_parent, args, ctx, info) => {
1347
+ const { paging, ...scope } = args;
1348
+ const page = await cursorAware(() => runtime.runsPage({
1349
+ ...scope,
1350
+ limit: paging?.limit ?? void 0,
1351
+ cursor: paging?.cursor ?? null,
1352
+ withStepData: readsStepData(info, ["items"])
1353
+ }, ctx));
1354
+ return {
1355
+ items: page.records.map((record) => toRunRecord(record.row, record.steps)),
1356
+ hasNextPage: page.hasNextPage,
1357
+ hasPreviousPage: Boolean(paging?.cursor),
1358
+ cursor: page.cursor
1359
+ };
1360
+ },
1191
1361
  run: async (_parent, args, ctx) => {
1192
1362
  const record = await runtime.run(args.id, ctx);
1193
1363
  return record ? toRunRecord(record.row, record.steps) : null;
@@ -1196,7 +1366,13 @@ const getResolvers = (runtime, authorizationService) => {
1196
1366
  Mutation: { workflowRuntime: () => ({}) },
1197
1367
  WorkflowRuntimeMutations: {
1198
1368
  fire: (_parent, args, ctx) => runtime.fire(args.workflowId, args.payload, "manual", void 0, ctx),
1199
- testTrigger: (_parent, args, ctx) => runtime.testTrigger(args.workflowId, ctx),
1369
+ testTrigger: (_parent, args, ctx) => syncAware(() => runtime.testTrigger(args.workflowId, ctx, {
1370
+ ...args.payload !== void 0 ? { payload: args.payload } : {},
1371
+ ...typeof args.timeoutSeconds === "number" ? { timeoutMs: args.timeoutSeconds * 1e3 } : {},
1372
+ driveId: args.driveId ?? void 0
1373
+ })),
1374
+ cancelTriggerTest: (_parent, args, ctx) => runtime.cancelTriggerTestFor(args.workflowId, ctx),
1375
+ testStep: (_parent, args, ctx) => syncAware(() => runtime.testStep(args.workflowId, args.stepId, ctx, { driveId: args.driveId ?? void 0 })),
1200
1376
  rerun: (_parent, args, ctx) => runtime.rerun(args.runId, ctx),
1201
1377
  createSecret: async (_parent, args, ctx) => {
1202
1378
  requireAdmin(authorizationService, ctx);
@@ -1214,7 +1390,15 @@ const getResolvers = (runtime, authorizationService) => {
1214
1390
  await (await runtime.secrets()).delete(args.ref);
1215
1391
  return true;
1216
1392
  },
1217
- checkConnection: (_parent, args, ctx) => runtime.checkConnection(args.connectionId, ctx)
1393
+ checkConnection: (_parent, args, ctx) => runtime.checkConnection(args.connectionId, ctx),
1394
+ startOAuth: (_parent, args, ctx) => {
1395
+ requireAdmin(authorizationService, ctx);
1396
+ const returnUrl = returnUrlFor(args.returnUrl, ctx);
1397
+ return runtime.startOAuth(args.connectionId, ctx, {
1398
+ redirectUri: redirectUriFor(oauth, args.redirectUri),
1399
+ ...returnUrl ? { returnUrl } : {}
1400
+ });
1401
+ }
1218
1402
  }
1219
1403
  };
1220
1404
  };
@@ -1230,19 +1414,38 @@ const schema = gql`
1230
1414
  Persisted runs, newest first. Scope them to one workflow, or to every
1231
1415
  workflow a drive holds; workflowId wins when both are given.
1232
1416
  """
1233
- runs(workflowId: String, driveId: String, limit: Int): [WorkflowRunRecord!]!
1417
+ runs(
1418
+ workflowId: String
1419
+ driveId: String
1420
+ limit: Int
1421
+ "Trigger kinds to leave out, e.g. test."
1422
+ excludeTriggerKinds: [String!]
1423
+ ): [WorkflowRunRecord!]!
1424
+ """
1425
+ The same listing a page at a time, newest journaled first. Pass a page's
1426
+ cursor back in paging to read the next; a page is short only when
1427
+ hasNextPage is false, or when the caller could read none of a long
1428
+ stretch of runs. A run that starts between pages keeps its place.
1429
+ """
1430
+ runsPage(
1431
+ workflowId: String
1432
+ driveId: String
1433
+ excludeTriggerKinds: [String!]
1434
+ paging: WorkflowRunsPagingInput
1435
+ ): WorkflowRunResultPage!
1234
1436
  run(id: String!): WorkflowRunRecord
1235
1437
  """
1236
- Action descriptor (props, auth) for a piece block type; null for core blocks.
1438
+ The descriptor (props, auth, ports) of one action or trigger, as the
1439
+ version that runs it describes it; triggers come back under "trigger".
1237
1440
  """
1238
- blockDescriptor(blockType: String!): Unknown
1441
+ blockDescriptor(block: BlockInput!): Unknown
1239
1442
  """
1240
1443
  Resolves a dynamic prop against the current config values: a DROPDOWN
1241
1444
  yields { options, placeholder, disabled }, a DYNAMIC prop yields the
1242
1445
  resolved sub-property descriptor list.
1243
1446
  """
1244
1447
  blockOptions(
1245
- blockType: String!
1448
+ block: BlockInput!
1246
1449
  propName: String!
1247
1450
  input: Unknown
1248
1451
  connectionId: String
@@ -1250,21 +1453,34 @@ const schema = gql`
1250
1453
  searchValue: String
1251
1454
  ): Unknown
1252
1455
  """
1253
- All published Activepieces pieces with at least one action.
1456
+ Every piece this reactor offers: the core piece, its package pieces, and
1457
+ the pieces the registry and Activepieces publish.
1254
1458
  """
1255
1459
  pieceCatalog: Unknown
1256
1460
  """
1257
- A piece's actions, each with a ready-to-use blockType.
1461
+ A piece's actions at one version; pin a step to that version.
1258
1462
  """
1259
- pieceActions(packageName: String!): Unknown
1463
+ pieceActions(
1464
+ packageName: String!
1465
+ "That version; the installed or latest one when omitted."
1466
+ version: String
1467
+ ): Unknown
1260
1468
  """
1261
- A piece's triggers, each with a ready-to-use "#trigger:" blockType.
1469
+ A piece's triggers at one version; pin a trigger to that version.
1262
1470
  """
1263
- pieceTriggers(packageName: String!): Unknown
1471
+ pieceTriggers(
1472
+ packageName: String!
1473
+ "That version; the installed or latest one when omitted."
1474
+ version: String
1475
+ ): Unknown
1264
1476
  """
1265
1477
  Full piece detail (PieceMetadataModel-shaped), verbatim from the cloud API.
1266
1478
  """
1267
- pieceDetail(packageName: String!): Unknown
1479
+ pieceDetail(
1480
+ packageName: String!
1481
+ "That version; the installed or latest one when omitted."
1482
+ version: String
1483
+ ): Unknown
1268
1484
  """
1269
1485
  Action/trigger name search across the catalog. The index builds lazily on
1270
1486
  first use; poll while status is "indexing".
@@ -1277,7 +1493,17 @@ const schema = gql`
1277
1493
  """
1278
1494
  Authored output shape of a block (SDL / outputSchema / sampleData).
1279
1495
  """
1280
- blockOutputTree(blockType: String!, config: Unknown): Unknown
1496
+ blockOutputTree(block: BlockInput!, config: Unknown): Unknown
1497
+ """
1498
+ Output shape of a draft step or trigger: its latest test output
1499
+ (source "test", with sample and testedAt) when tested, else as blockOutputTree.
1500
+ """
1501
+ stepOutputTree(workflowId: String!, stepId: String!): Unknown
1502
+ """
1503
+ Every block of the draft (trigger first) as this reactor resolves it:
1504
+ which piece version runs, from where, and how it matches the pin.
1505
+ """
1506
+ blockResolutions(workflowId: String!): [BlockResolutionRecord!]!
1281
1507
  """
1282
1508
  Every powerhouse/connection document, for connection pickers.
1283
1509
  """
@@ -1286,19 +1512,62 @@ const schema = gql`
1286
1512
  The webhook endpoint for a workflow: the URL to hand the provider. Minted
1287
1513
  on first ask, whether or not the workflow is armed — an author needs the
1288
1514
  URL before enabling, and the armed field carries that difference. Null
1289
- only when the host has no webhook service.
1515
+ only when the host has no webhook service. With driveId, a workflow not
1516
+ synced here yet is waited for briefly, then fails as WORKFLOW_SYNCING.
1290
1517
  """
1291
- webhookEndpoint(workflowId: String!): WebhookEndpointRecord
1518
+ webhookEndpoint(workflowId: String!, driveId: String): WebhookEndpointRecord
1292
1519
  """
1293
1520
  Secret metadata (label, version, status). Never the value.
1294
1521
  """
1295
1522
  secret(ref: String!): SecretRecord
1296
1523
  secrets: [SecretRecord!]!
1524
+ """
1525
+ The URL an OAuth2 app must register as its redirect. A bare path when
1526
+ this host does not know its public origin; resolve it against the URL
1527
+ this API is reached at. Null when the host serves no HTTP routes.
1528
+ """
1529
+ oauthRedirectUri: String
1530
+ """
1531
+ How an OAuth2 sign-in opened by startOAuth stands; null when unknown.
1532
+ """
1533
+ oauthAttempt(state: String!): OAuthAttempt
1534
+ }
1535
+
1536
+ type OAuthAttempt {
1537
+ connectionId: String!
1538
+ "PENDING | EXCHANGING | OK | ERROR"
1539
+ status: String!
1540
+ error: String
1541
+ }
1542
+
1543
+ type OAuthStart {
1544
+ state: String!
1545
+ "Where to send the user to sign in."
1546
+ authorizationUrl: String!
1547
+ redirectUri: String!
1548
+ expiresAt: String!
1549
+ }
1550
+
1551
+ """
1552
+ One action or trigger of a piece, as a workflow pins it. The engine's own
1553
+ blocks are the piece @powerhousedao/piece-core.
1554
+ """
1555
+ input BlockInput {
1556
+ pieceName: String!
1557
+ "Exact semver."
1558
+ pieceVersion: String!
1559
+ "The action or trigger name."
1560
+ name: String!
1561
+ "action | trigger"
1562
+ kind: String!
1297
1563
  }
1298
1564
 
1299
1565
  type BlockSearchHit {
1300
- blockType: String!
1301
1566
  pieceName: String!
1567
+ "The version the hit is listed at; pin to it when picking the block."
1568
+ pieceVersion: String!
1569
+ "The action or trigger name."
1570
+ name: String!
1302
1571
  pieceDisplayName: String!
1303
1572
  logoUrl: String!
1304
1573
  displayName: String!
@@ -1311,6 +1580,28 @@ const schema = gql`
1311
1580
  unsupported: String
1312
1581
  }
1313
1582
 
1583
+ type BlockResolutionRecord {
1584
+ "The trigger's or step's id."
1585
+ stepId: String!
1586
+ pieceName: String!
1587
+ "The version the block pins."
1588
+ pieceVersion: String!
1589
+ "The action or trigger name."
1590
+ name: String!
1591
+ "action | trigger"
1592
+ kind: String!
1593
+ "The version that runs; null when nothing resolves."
1594
+ resolvedVersion: String
1595
+ "local | registry | activepieces | npm; null when nothing resolves."
1596
+ source: String
1597
+ "exact | compatible | fallback | installed | missing"
1598
+ match: String!
1599
+ "Why the version that runs differs from the pin, or why nothing resolves."
1600
+ note: String
1601
+ "The newest version any source offers."
1602
+ latestVersion: String
1603
+ }
1604
+
1314
1605
  type BlockSearchResult {
1315
1606
  "ready | indexing | error"
1316
1607
  status: String!
@@ -1351,8 +1642,9 @@ const schema = gql`
1351
1642
  workflowId: String!
1352
1643
  url: String!
1353
1644
  """
1354
- False when the reactor does not know its own public origin, so the url
1355
- above is a bare path no provider can call: the author supplies the origin.
1645
+ False when the host gave the webhook service no public origin, so the url
1646
+ above is a bare path. Switchboard always gives one, falling back to
1647
+ http://localhost:<port> when PUBLIC_URL is unset.
1356
1648
  """
1357
1649
  absoluteUrl: Boolean!
1358
1650
  "True while the workflow is ENABLED with a valid webhook trigger"
@@ -1362,13 +1654,24 @@ const schema = gql`
1362
1654
 
1363
1655
  type TriggerStateRecord {
1364
1656
  workflowId: String!
1365
- blockType: String!
1657
+ pieceName: String!
1658
+ triggerName: String!
1366
1659
  status: String!
1367
1660
  intervalMs: Int!
1368
1661
  nextPollAt: String
1369
1662
  lastPollAt: String
1370
1663
  lastError: String
1371
1664
  consecutiveFailures: Int!
1665
+ "When a webhook trigger next renews its subscription; null if it never does."
1666
+ nextRenewAt: String
1667
+ "The last failed renewal, kept apart from lastError, the poll's."
1668
+ renewError: String
1669
+ renewFailures: Int!
1670
+ "The piece version the trigger armed with; null for a host-fed trigger."
1671
+ pieceVersion: String
1672
+ pieceSource: String
1673
+ versionMatch: String
1674
+ versionNote: String
1372
1675
  }
1373
1676
 
1374
1677
  type Query {
@@ -1378,7 +1681,9 @@ const schema = gql`
1378
1681
  type WorkflowStepRunRecord {
1379
1682
  stepId: String!
1380
1683
  stepKey: String!
1381
- blockType: String!
1684
+ pieceName: String!
1685
+ "The action the step ran, or the trigger a trigger test sampled."
1686
+ blockName: String!
1382
1687
  status: String!
1383
1688
  input: Unknown
1384
1689
  output: Unknown
@@ -1386,6 +1691,14 @@ const schema = gql`
1386
1691
  error: String
1387
1692
  startedAt: String
1388
1693
  endedAt: String
1694
+ "The piece version that ran; null for an unresolved block."
1695
+ pieceVersion: String
1696
+ "local | registry | activepieces | npm"
1697
+ pieceSource: String
1698
+ "exact | compatible | fallback | installed | missing"
1699
+ versionMatch: String
1700
+ "Why the version that ran differs from the pin."
1701
+ versionNote: String
1389
1702
  }
1390
1703
 
1391
1704
  type WorkflowRunRecord {
@@ -1397,16 +1710,38 @@ const schema = gql`
1397
1710
  triggerPayload: Unknown
1398
1711
  status: String!
1399
1712
  error: String
1713
+ "When the run began executing; a PENDING run's is when it was journaled."
1400
1714
  startedAt: String!
1401
1715
  endedAt: String
1402
1716
  rerunOf: String
1717
+ "How many warningNotes the run has; a run with any is not a plain success."
1718
+ warnings: Int!
1719
+ "Steps that ran a fallback piece version, edges on ports nothing emits."
1720
+ warningNotes: [String!]!
1403
1721
  steps: [WorkflowStepRunRecord!]!
1404
1722
  }
1405
1723
 
1724
+ input WorkflowRunsPagingInput {
1725
+ "Page size; default 25, at most 100."
1726
+ limit: Int
1727
+ "The cursor of the previous page."
1728
+ cursor: String
1729
+ }
1730
+
1731
+ type WorkflowRunResultPage {
1732
+ items: [WorkflowRunRecord!]!
1733
+ hasNextPage: Boolean!
1734
+ "True when the page was read after a cursor."
1735
+ hasPreviousPage: Boolean!
1736
+ "Resumes after this page's last run; null for an empty page."
1737
+ cursor: String
1738
+ }
1739
+
1406
1740
  type WorkflowStepRun {
1407
1741
  stepId: String!
1408
1742
  key: String!
1409
- blockType: String!
1743
+ pieceName: String!
1744
+ blockName: String!
1410
1745
  status: String!
1411
1746
  input: Unknown
1412
1747
  output: Unknown
@@ -1421,18 +1756,51 @@ const schema = gql`
1421
1756
  steps: [WorkflowStepRun!]!
1422
1757
  }
1423
1758
 
1759
+ type WorkflowStepTestResult {
1760
+ "Null when the test never started, e.g. an upstream block is untested."
1761
+ runId: String
1762
+ "SUCCEEDED | FAILED"
1763
+ status: String!
1764
+ output: Unknown
1765
+ error: String
1766
+ durationMs: Int!
1767
+ }
1768
+
1424
1769
  """
1425
1770
  WorkflowRuntime Mutations
1426
1771
  """
1427
1772
  type WorkflowRuntimeMutations {
1428
1773
  """
1429
- Fires a workflow's core#manual trigger and runs it to completion.
1774
+ Fires a workflow's manual trigger (the core piece's) and runs it to completion.
1430
1775
  """
1431
1776
  fire(workflowId: String!, payload: Unknown): WorkflowRunPayload!
1432
1777
  """
1433
- Runs a piece trigger's test hook; sample items, no cursor changes.
1778
+ Samples the draft trigger and saves it as the trigger's last test. A piece
1779
+ trigger runs its test hook (no cursor changes). Of the core piece's
1780
+ triggers, manual takes payload, schedule samples a fire now, and webhook
1781
+ waits for the next delivery to the workflow's endpoint, up to
1782
+ timeoutSeconds (default and cap 300), and that delivery runs nothing.
1783
+ """
1784
+ testTrigger(
1785
+ workflowId: String!
1786
+ payload: Unknown
1787
+ timeoutSeconds: Int
1788
+ driveId: String
1789
+ ): Unknown
1790
+ """
1791
+ Stops a webhook trigger test that is waiting; false when none was.
1792
+ """
1793
+ cancelTriggerTest(workflowId: String!): Boolean!
1434
1794
  """
1435
- testTrigger(workflowId: String!): Unknown
1795
+ Runs one draft step against the latest test outputs of the blocks it
1796
+ reads, journaled as a "test" run and noted as the step's lastTest.
1797
+ driveId waits for an unsynced workflow, as for webhookEndpoint.
1798
+ """
1799
+ testStep(
1800
+ workflowId: String!
1801
+ stepId: String!
1802
+ driveId: String
1803
+ ): WorkflowStepTestResult!
1436
1804
  """
1437
1805
  Resumes a FAILED run: succeeded steps replay from the journal,
1438
1806
  execution restarts at the failure. Produces a new run.
@@ -1457,6 +1825,18 @@ const schema = gql`
1457
1825
  outcome on the connection document.
1458
1826
  """
1459
1827
  checkConnection(connectionId: String!): ConnectionCheckResult!
1828
+ """
1829
+ Opens an OAuth2 sign-in with the connection's own app (client_id in its
1830
+ config, client_secret in its secretRefs). redirectUri is needed only when
1831
+ oauthRedirectUri is a bare path. With returnUrl, which must be on the
1832
+ caller's origin, the callback sends the browser back there with
1833
+ ?ph_oauth=<state> instead of closing the window.
1834
+ """
1835
+ startOAuth(
1836
+ connectionId: String!
1837
+ redirectUri: String
1838
+ returnUrl: String
1839
+ ): OAuthStart!
1460
1840
  }
1461
1841
 
1462
1842
  type Mutation {
@@ -1467,11 +1847,11 @@ const schema = gql`
1467
1847
  //#region src/workflow/subgraph.ts
1468
1848
  /** The runtime's read/write surface. The runtime itself is composed by the
1469
1849
  * host, so the subgraph only serves what it is handed. */
1470
- function createWorkflowRuntimeSubgraph(runtime) {
1850
+ function createWorkflowRuntimeSubgraph(runtime, oauth) {
1471
1851
  return class WorkflowRuntimeSubgraph extends BaseSubgraph {
1472
1852
  name = "workflow-runtime";
1473
1853
  typeDefs = schema;
1474
- resolvers = getResolvers(runtime, this.authorizationService);
1854
+ resolvers = getResolvers(runtime, this.authorizationService, oauth);
1475
1855
  additionalContextFields = {};
1476
1856
  };
1477
1857
  }
@@ -1579,7 +1959,7 @@ async function registerWorkflowTriggersReadModel(engine, runtime, clientModule)
1579
1959
  }
1580
1960
  function bindPackagePieces(registry, source) {
1581
1961
  const apply = (byPackage) => {
1582
- registry.setPieces([...byPackage.values()].flat());
1962
+ registry.setPieces([...byPackage.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).flatMap(([, pieces]) => pieces));
1583
1963
  };
1584
1964
  apply(source.getPieces());
1585
1965
  source.onPiecesChange(apply);
@@ -1610,8 +1990,9 @@ async function composeWorkflowRuntime(deps) {
1610
1990
  if (triggers.status === "available") deps.logger.info(`Workflow trigger read model registered (${engine.WORKFLOW_TRIGGERS_READ_MODEL}, ${engine.WORKFLOW_TRIGGERS_READ_MODEL_STAGE})`);
1611
1991
  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);
1612
1992
  let stopped = false;
1993
+ const oauthCallback = deps.http ? registerOAuthCallback(deps.http, runtime) : void 0;
1613
1994
  return {
1614
- subgraph: createWorkflowRuntimeSubgraph(runtime),
1995
+ subgraph: createWorkflowRuntimeSubgraph(runtime, deps.http ? { callbackUrl: callbackUrlOf(deps.http) } : void 0),
1615
1996
  triggers,
1616
1997
  async start() {
1617
1998
  await runtime.registerWebhookEndpoint();
@@ -1620,6 +2001,7 @@ async function composeWorkflowRuntime(deps) {
1620
2001
  stop() {
1621
2002
  if (stopped) return Promise.resolve();
1622
2003
  stopped = true;
2004
+ oauthCallback?.dispose();
1623
2005
  runtime.shutdown();
1624
2006
  return Promise.resolve();
1625
2007
  }
@@ -2314,6 +2696,7 @@ async function initServer(serverPort, options, renown, renownConfig) {
2314
2696
  attachmentReferences: api.attachmentReferenceIndex.store,
2315
2697
  attachmentReferenceProjection: api.attachmentReferenceProjection,
2316
2698
  webhooks: api.httpRoutes.scopeFor(WORKFLOW_PACKAGE_NAME).webhooks,
2699
+ http: api.httpRoutes.scopeFor(WORKFLOW_PACKAGE_NAME),
2317
2700
  authorizationService: api.authorizationService,
2318
2701
  pieces: api.packageManager,
2319
2702
  pieceRegistryUrl: registryUrl,
@@ -2430,6 +2813,7 @@ async function initServer(serverPort, options, renown, renownConfig) {
2430
2813
  attachmentService,
2431
2814
  attachmentReferenceProjection: api.attachmentReferenceProjection,
2432
2815
  workflowTriggers: workflows?.triggers,
2816
+ workflowsEnabled,
2433
2817
  renown,
2434
2818
  port: serverPort,
2435
2819
  shutdown
@@ -2494,5 +2878,5 @@ if (import.meta.main) await startSwitchboard({ fatalErrorShutdown: true });
2494
2878
  //#endregion
2495
2879
  export { parseForcePgVersion as a, applySwitchboardReactorDefaults as i, isPortAvailable as n, startSwitchboard as r, deriveAttachmentServiceConfig as t };
2496
2880
 
2497
- //# sourceMappingURL=server-Cf0DB8sW.mjs.map
2498
- //# debugId=49b28650-fe00-57f4-844b-978a35d80e91
2881
+ //# sourceMappingURL=server-CKKn0aXV.mjs.map
2882
+ //# debugId=ec5120f6-92b3-53b4-bab1-94d88478784d