@powerhousedao/reactor-workflow 6.2.3-dev.11

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/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @powerhousedao/reactor-workflow
2
+
3
+ The workflow engine that runs on a reactor. It owns the runtime that turns a
4
+ `powerhouse/workflow` document into runs — triggers, the run journal, managed
5
+ secrets, connections — and, beneath it, the machinery that runs an Activepieces
6
+ piece in a child process.
7
+
8
+ The document models, their editors and Workflow Studio live in
9
+ [`@powerhousedao/workflow`](../workflow); the GraphQL subgraph that serves this
10
+ runtime lives in [`apps/switchboard`](../../apps/switchboard), the host that
11
+ composes the runtime.
12
+
13
+ ## The seam
14
+
15
+ ```
16
+ src/pieces/ loader, descriptor, worker pool, egress, executor, expressions
17
+ src/reactor/ trigger supervisor, coordinator, run journal, secret store, ports
18
+ ```
19
+
20
+ `src/pieces` runs a piece. It knows nothing about reactors, documents or
21
+ Powerhouse packages: give it a piece name, a config and a connection value and
22
+ it returns an output. The boundary is enforced by lint — nothing under
23
+ `src/pieces` may import `src/reactor`, nor any `@powerhousedao/*` package other
24
+ than `@powerhousedao/pieces-framework`, whose contract it implements — so the
25
+ piece layer stays something you can reason about on its own.
26
+
27
+ `src/reactor` is everything that only makes sense on a reactor: which workflow a
28
+ trigger belongs to, where a run is journaled, whose credentials a step may
29
+ resolve. It depends on the piece layer, never the other way round.
30
+
31
+ ## What comes from the piece framework
32
+
33
+ The contract this engine implements is declared once, in
34
+ [`@powerhousedao/pieces-framework`](../pieces-framework), and taken from there
35
+ rather than restated here.
36
+
37
+ - The **types**. `ApAction`, `ApTrigger`, `ApPiece` and `ApProperty` derive from
38
+ `ActionBase`, `TriggerBase`, `PieceBase` and the property schemas; the
39
+ contexts from `Store`, `ServerContext`, `FilesService`, `ConnectionsManager`,
40
+ `FlowsContext`, `RunContext`, `TriggerHookContext` and `SetScheduleRequest`;
41
+ the connection shapes from `AppConnectionType` and `AppConnectionValue`.
42
+ `PackagePiece`, `ReactorService` and `DEDUPE_KEY_PROPERTY` are the framework's
43
+ own Powerhouse half.
44
+ - The **enums stay strings here**. A piece bundle inlines its own copy of the
45
+ framework, so a `PropertyType` or `TriggerStrategy` read off one shares no
46
+ identity with ours. Every such value is compared as a string; nothing in
47
+ `src/pieces` uses `instanceof` or enum identity across that boundary.
48
+ - **Prop coercion**, from `@powerhousedao/pieces-framework/host`, which carries
49
+ the Activepieces engine's own property processors. `context/normalize.ts`
50
+ dispatches to them; only file props stay ours, because attachment and
51
+ `apfile://` refs, the size ceiling and a host-injected fetcher have no
52
+ upstream equivalent. An `ApFile` a processor builds is flattened to a plain
53
+ object at that boundary: a class instance does not survive the worker IPC.
54
+ - **The SSRF table**, likewise from `./host`. `worker/egress.ts` classifies an
55
+ address with `ssrfIpClassifier.isBlockedIp`; the connect-time socket and DNS
56
+ hooks, the per-request policy and the allow-lists are ours. The one range the
57
+ classifier reads as unicast and we still refuse is the deprecated
58
+ IPv4-compatible `::/96` block, which carries the metadata endpoint.
59
+ - **Error formatting**, again from `./host`. A thrown piece error passes through
60
+ `formatPieceError` before redaction, so the HTTP status, request, response and
61
+ the text of an HTML error page reach the run journal. Redaction runs last,
62
+ over the formatter's output as well.
63
+
64
+ ## How the host composes it
65
+
66
+ The engine names no host type. `WorkflowRuntimeHostDeps` (`src/reactor/host.ts`)
67
+ is what the runtime reads — a relational db, a reactor client, the read and
68
+ write checks, and optionally the HTTP scope its webhook endpoints live under.
69
+ `createWorkflowRuntime(deps)` returns a configured runtime; nothing here
70
+ constructs one by itself.
71
+
72
+ Switchboard is that host (`apps/switchboard/src/workflow-runtime.mts`). It
73
+ resolves the `workflows` flag, builds the runtime from what `startAPI` hands
74
+ back, and owns the GraphQL face in `apps/switchboard/src/workflow/`, registered
75
+ live with the GraphQL manager the way a package subgraph is. reactor-api knows
76
+ nothing about workflows.
77
+
78
+ Document operations reach the runtime through a **read model**, not a
79
+ processor: `WorkflowTriggersReadModel` (`WORKFLOW_TRIGGERS_READ_MODEL`) is
80
+ registered on the reactor's read-model coordinator at the
81
+ `WORKFLOW_TRIGGERS_READ_MODEL_STAGE` (`post_ready`) stage, and its
82
+ `indexOperations` is `runtime.onOperations`. The durable cursor `BaseReadModel`
83
+ gives it means an operation written while the runtime is down catches up on the
84
+ next boot instead of vanishing; a fresh registration starts at head, so history
85
+ is never replayed. `onOperations` journals a matched fire before it returns, so
86
+ the cursor never passes an event that is not yet durable.
87
+
88
+ The webhook endpoints are registered under the `@powerhousedao/workflow`
89
+ namespace, which this package exports as `WORKFLOW_PACKAGE_NAME`.
90
+
91
+ ## The worker entry
92
+
93
+ A piece runs in a forked node child, never in the reactor's process. The
94
+ transport finds that child's code by walking up to this package's own
95
+ `package.json` and reading `dist/worker-entry.js`, so `pnpm build` must have run
96
+ before anything executes a piece — including the suites here.
97
+
98
+ ## `./testing`
99
+
100
+ `@powerhousedao/reactor-workflow/testing` re-exports the piece layer: the
101
+ loader, the worker and its pool, the executor, the coordinator, and the
102
+ in-memory secret and connection resolvers. It is what a piece author's own
103
+ package uses to run its piece the way this reactor will, without standing up a
104
+ reactor to do it.
105
+
106
+ ## Running the tests
107
+
108
+ ```sh
109
+ pnpm build # dist/worker-entry.js, which the piece suites fork
110
+ pnpm test
111
+ ```
112
+
113
+ Bundles fetched from npm are cached in `node_modules/.cache/ap-bundles`; the
114
+ suites that need one skip when it cannot be fetched, so the offline run is
115
+ smaller but green. Two suites need the docling piece's own package checked out
116
+ beside this repo, and skip otherwise.
117
+
118
+ Environment:
119
+
120
+ - `PH_SECRETS_MASTER_KEY` — 32 bytes of hex for the managed secret store. Unset,
121
+ it generates and reuses a key file.
122
+ - `WORKFLOW_EGRESS_ALLOW_ADDRESSES` — comma-separated CIDRs a piece may reach.
123
+ The default policy refuses private and loopback addresses; a suite that talks
124
+ to a local mock service widens it.
125
+
126
+ ## Design documents
127
+
128
+ [`docs/plan`](./docs/plan) holds the specifications this engine was built from —
129
+ the automation spec (08), the piece-loading architecture (06), the secrets
130
+ service (09) and the HTTP routes (10) are the ones worth reading first.
@@ -0,0 +1,169 @@
1
+ import { R as getActions, z as getTriggers } from "./redact-C7LWgAyD.js";
2
+ import { createRequire } from "node:module";
3
+ import { readFileSync, realpathSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ //#region src/pieces/activepieces/loader.ts
7
+ function resolveEntry(pieceDir) {
8
+ const raw = readFileSync(path.join(pieceDir, "package.json"), "utf8");
9
+ const pkg = JSON.parse(raw);
10
+ const dotExport = pkg.exports?.["."];
11
+ const candidates = [];
12
+ if (typeof dotExport === "string") candidates.push(dotExport);
13
+ if (dotExport && typeof dotExport === "object") {
14
+ const cond = dotExport;
15
+ for (const key of [
16
+ "import",
17
+ "require",
18
+ "default"
19
+ ]) {
20
+ const value = cond[key];
21
+ if (typeof value === "string") candidates.push(value);
22
+ }
23
+ }
24
+ candidates.push(pkg.main, pkg.module, "src/index.js", "index.js", "main.js");
25
+ const root = realpathSync(pieceDir);
26
+ const rootWithSep = root + path.sep;
27
+ for (const candidate of candidates) {
28
+ if (!candidate) continue;
29
+ const abs = path.resolve(pieceDir, candidate);
30
+ let real;
31
+ try {
32
+ real = realpathSync(abs);
33
+ } catch {
34
+ continue;
35
+ }
36
+ if (real !== root && !real.startsWith(rootWithSep)) continue;
37
+ return abs;
38
+ }
39
+ throw new Error(`No entry file found for piece bundle at ${pieceDir}`);
40
+ }
41
+ function isRecord(value) {
42
+ return typeof value === "object" && value !== null;
43
+ }
44
+ function findPiece(mod) {
45
+ const candidates = [
46
+ ...Object.values(mod),
47
+ ...isRecord(mod.default) ? Object.values(mod.default) : [],
48
+ mod.default
49
+ ];
50
+ for (const candidate of candidates) if (isRecord(candidate) && candidate.constructor.name === "Piece") return {
51
+ piece: candidate,
52
+ check: "constructor-name"
53
+ };
54
+ for (const candidate of candidates) if (isRecord(candidate) && typeof candidate.displayName === "string" && (isRecord(candidate.actions) || typeof candidate.actions === "function" || typeof candidate.getAction === "function")) return {
55
+ piece: candidate,
56
+ check: "structural"
57
+ };
58
+ }
59
+ async function loadPiece(entryPath) {
60
+ let mod;
61
+ try {
62
+ mod = await import(
63
+ /* @vite-ignore */
64
+ pathToFileURL(entryPath).href
65
+ );
66
+ } catch {
67
+ mod = createRequire(import.meta.url)(entryPath);
68
+ }
69
+ const found = findPiece(mod);
70
+ if (!found) throw new Error(`No Piece export found in ${entryPath}. Export keys: ${Object.keys(mod).join(", ")}`);
71
+ return {
72
+ ...found,
73
+ entryPath
74
+ };
75
+ }
76
+ async function loadPieceFromDir(pieceDir) {
77
+ return loadPiece(resolveEntry(pieceDir));
78
+ }
79
+ //#endregion
80
+ //#region src/pieces/activepieces/descriptor.ts
81
+ function describeHandshake(trigger) {
82
+ const strategy = trigger.handshakeConfiguration?.strategy;
83
+ if (!strategy || strategy === "NONE") return void 0;
84
+ return {
85
+ strategy,
86
+ paramName: trigger.handshakeConfiguration?.paramName
87
+ };
88
+ }
89
+ function hasResolver(prop) {
90
+ return typeof prop.options === "function" || typeof prop.props === "function";
91
+ }
92
+ function toPropDescriptor(name, prop, resolverId) {
93
+ const dynamic = hasResolver(prop);
94
+ const descriptor = {
95
+ name,
96
+ displayName: prop.displayName ?? name,
97
+ type: prop.type ?? "UNKNOWN",
98
+ required: prop.required ?? false,
99
+ hasDynamicResolver: dynamic
100
+ };
101
+ if (typeof prop.description === "string" && prop.description !== "") descriptor.description = prop.description;
102
+ if (typeof prop.placeholder === "string" && prop.placeholder !== "") descriptor.placeholder = prop.placeholder;
103
+ if (prop.defaultValue !== void 0) descriptor.defaultValue = prop.defaultValue;
104
+ if (dynamic && resolverId) descriptor.dynamicResolverId = resolverId;
105
+ if (dynamic && Array.isArray(prop.refreshers)) descriptor.refreshers = prop.refreshers.filter((entry) => typeof entry === "string");
106
+ if (typeof prop.options === "object" && Array.isArray(prop.options.options)) descriptor.staticOptions = prop.options.options.map((o) => ({
107
+ label: o.label,
108
+ value: o.value
109
+ }));
110
+ if (prop.properties && typeof prop.properties === "object") {
111
+ const nested = describeProperties(prop.properties);
112
+ if (nested.length > 0) descriptor.properties = nested;
113
+ }
114
+ return descriptor;
115
+ }
116
+ function describeProperties(props, resolverIdFor) {
117
+ if (!props || typeof props !== "object") return [];
118
+ return Object.entries(props).filter((entry) => isPropertyObject(entry[1])).map(([propName, prop]) => toPropDescriptor(propName, prop, resolverIdFor?.(propName)));
119
+ }
120
+ function isPropertyObject(value) {
121
+ return value !== null && typeof value === "object";
122
+ }
123
+ function buildDescriptor(piece, source) {
124
+ const actions = Object.entries(getActions(piece)).map(([actionName, action]) => ({
125
+ name: action.name ?? actionName,
126
+ displayName: action.displayName ?? actionName,
127
+ description: action.description,
128
+ requireAuth: action.requireAuth ?? false,
129
+ props: describeProperties(action.props, (propName) => `activepieces:${source.packageName}#${actionName}.${propName}`)
130
+ }));
131
+ const triggers = Object.entries(getTriggers(piece)).map(([triggerName, trigger]) => ({
132
+ name: trigger.name ?? triggerName,
133
+ displayName: trigger.displayName ?? triggerName,
134
+ description: trigger.description,
135
+ strategy: trigger.type ?? "UNKNOWN",
136
+ testStrategy: trigger.testStrategy,
137
+ requireAuth: trigger.requireAuth ?? false,
138
+ props: describeProperties(trigger.props, (propName) => `activepieces:${source.packageName}#${triggerName}.${propName}`),
139
+ hasSampleData: trigger.sampleData !== void 0 && trigger.sampleData !== null,
140
+ handshake: describeHandshake(trigger)
141
+ }));
142
+ const descriptor = {
143
+ id: `activepieces:${source.packageName}`,
144
+ source,
145
+ displayName: piece.displayName,
146
+ description: piece.description,
147
+ logoUrl: piece.logoUrl,
148
+ categories: piece.categories,
149
+ minimumSupportedRelease: piece.minimumSupportedRelease,
150
+ maximumSupportedRelease: piece.maximumSupportedRelease,
151
+ actions,
152
+ triggers
153
+ };
154
+ if (piece.auth && typeof piece.auth === "object") {
155
+ const authProps = piece.auth.props && typeof piece.auth.props === "object" ? describeProperties(piece.auth.props) : [];
156
+ descriptor.auth = {
157
+ type: piece.auth.type ?? "UNKNOWN",
158
+ displayName: piece.auth.displayName,
159
+ description: piece.auth.description,
160
+ required: piece.auth.required,
161
+ ...authProps.length > 0 ? { props: authProps } : {}
162
+ };
163
+ }
164
+ return descriptor;
165
+ }
166
+ //#endregion
167
+ export { loadPieceFromDir as i, describeProperties as n, loadPiece as r, buildDescriptor as t };
168
+
169
+ //# sourceMappingURL=descriptor-DXbWuxhE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptor-DXbWuxhE.js","names":[],"sources":["../src/pieces/activepieces/loader.ts","../src/pieces/activepieces/descriptor.ts"],"sourcesContent":["import { readFileSync, realpathSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ApPiece } from \"./types.js\";\n\nexport interface LoadedPiece {\n piece: ApPiece;\n entryPath: string;\n // Which duck-type check identified the export.\n check: \"constructor-name\" | \"structural\";\n}\n\n// Resolves a bundle's entry file. Published bundles typically carry only\n// `main` (no `exports`, no `type` — they are CJS).\nexport function resolveEntry(pieceDir: string): string {\n const raw = readFileSync(path.join(pieceDir, \"package.json\"), \"utf8\");\n const pkg = JSON.parse(raw) as {\n main?: string;\n module?: string;\n exports?: Record<string, unknown>;\n };\n const dotExport = pkg.exports?.[\".\"];\n const candidates: (string | undefined)[] = [];\n if (typeof dotExport === \"string\") candidates.push(dotExport);\n if (dotExport && typeof dotExport === \"object\") {\n const cond = dotExport as Record<string, unknown>;\n for (const key of [\"import\", \"require\", \"default\"]) {\n const value = cond[key];\n if (typeof value === \"string\") candidates.push(value);\n }\n }\n candidates.push(pkg.main, pkg.module, \"src/index.js\", \"index.js\", \"main.js\");\n // A bundle's manifest (or a symlink inside it) must not point the entry\n // outside pieceDir; realpath so a symlink can't launder the escape.\n const root = realpathSync(pieceDir);\n const rootWithSep = root + path.sep;\n for (const candidate of candidates) {\n if (!candidate) continue;\n const abs = path.resolve(pieceDir, candidate);\n let real: string;\n try {\n real = realpathSync(abs);\n } catch {\n continue; // candidate doesn't exist; try the next one\n }\n if (real !== root && !real.startsWith(rootWithSep)) continue;\n return abs;\n }\n throw new Error(`No entry file found for piece bundle at ${pieceDir}`);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n// Identify the piece by constructor name (their own loader's check), falling\n// back to structure for bundles minified without keepNames. No `instanceof`.\nfunction findPiece(\n mod: Record<string, unknown>,\n): Pick<LoadedPiece, \"piece\" | \"check\"> | undefined {\n const candidates: unknown[] = [\n ...Object.values(mod),\n ...(isRecord(mod.default) ? Object.values(mod.default) : []),\n mod.default,\n ];\n for (const candidate of candidates) {\n if (isRecord(candidate) && candidate.constructor.name === \"Piece\") {\n return {\n piece: candidate as unknown as ApPiece,\n check: \"constructor-name\",\n };\n }\n }\n for (const candidate of candidates) {\n if (\n isRecord(candidate) &&\n typeof candidate.displayName === \"string\" &&\n (isRecord(candidate.actions) ||\n typeof candidate.actions === \"function\" ||\n typeof candidate.getAction === \"function\")\n ) {\n return { piece: candidate as unknown as ApPiece, check: \"structural\" };\n }\n }\n return undefined;\n}\n\n// ESM-first `import()` — Node's CJS interop handles the typical CJS bundle —\n// with a `require` fallback.\nexport async function loadPiece(entryPath: string): Promise<LoadedPiece> {\n let mod: Record<string, unknown>;\n try {\n mod = (await import(\n /* @vite-ignore */ pathToFileURL(entryPath).href\n )) as Record<string, unknown>;\n } catch {\n const require = createRequire(import.meta.url);\n mod = require(entryPath) as Record<string, unknown>;\n }\n const found = findPiece(mod);\n if (!found) {\n throw new Error(\n `No Piece export found in ${entryPath}. Export keys: ${Object.keys(mod).join(\", \")}`,\n );\n }\n return { ...found, entryPath };\n}\n\nexport async function loadPieceFromDir(pieceDir: string): Promise<LoadedPiece> {\n return loadPiece(resolveEntry(pieceDir));\n}\n","import {\n getActions,\n getTriggers,\n type ApPiece,\n type ApProperty,\n type ApPropertyType,\n type ApDropdownOption,\n type ApTrigger,\n type ApTriggerStrategy,\n} from \"./types.js\";\n\nexport interface PiecePropDescriptor {\n name: string;\n displayName: string;\n description?: string;\n placeholder?: string;\n type: ApPropertyType;\n required: boolean;\n defaultValue?: unknown;\n // STATIC_DROPDOWN choices, extracted so the editor needs no runtime call.\n staticOptions?: ApDropdownOption[];\n // True when the prop carries a design-time resolver (DROPDOWN options() / DYNAMIC props()).\n hasDynamicResolver: boolean;\n // Synthesised domain-provider id: activepieces:<pkg>#<action>.<prop> (doc 08 §6.3).\n // Only top-level props get one; nested resolvers are not addressable yet.\n dynamicResolverId?: string;\n // Sibling prop names whose values feed the resolver; the editor re-runs\n // it when any of them changes.\n refreshers?: string[];\n // Nested shape: an ARRAY item's fields, or the props a DYNAMIC resolver\n // produced (see describeProperties). OBJECT props are free-form and carry none.\n properties?: PiecePropDescriptor[];\n}\n\nexport interface PieceActionDescriptor {\n name: string;\n displayName: string;\n description?: string;\n // UI metadata only — not a credential contract (spike finding).\n requireAuth: boolean;\n props: PiecePropDescriptor[];\n}\n\nexport interface PieceTriggerDescriptor {\n name: string;\n displayName: string;\n description?: string;\n strategy: ApTriggerStrategy;\n testStrategy?: string;\n requireAuth: boolean;\n props: PiecePropDescriptor[];\n hasSampleData: boolean;\n // How the sender proves the endpoint exists before it will register it.\n // Absent when the trigger declares no handshake, or declares NONE.\n handshake?: { strategy: string; paramName?: string };\n}\n\nexport interface PieceAuthDescriptor {\n type: ApPropertyType;\n displayName?: string;\n description?: string;\n required?: boolean;\n // CUSTOM_AUTH's own fields. Without them a connection form has nothing to\n // ask for, which is what a piece read from a package rather than a published\n // listing would otherwise leave the editor with.\n props?: PiecePropDescriptor[];\n}\n\n// NONE is how the framework spells \"no handshake\", so it is not carried:\n// a caller checking the field would otherwise have to know that too.\nfunction describeHandshake(\n trigger: ApTrigger,\n): { strategy: string; paramName?: string } | undefined {\n const strategy = trigger.handshakeConfiguration?.strategy;\n if (!strategy || strategy === \"NONE\") return undefined;\n return { strategy, paramName: trigger.handshakeConfiguration?.paramName };\n}\n\nexport interface PieceSource {\n packageName: string;\n version: string;\n}\n\n// Serializable descriptor of an adapted piece; the engine never learns\n// Activepieces exists (doc 08 §6.2).\nexport interface PieceDescriptor {\n id: string;\n source: PieceSource;\n displayName: string;\n description?: string;\n logoUrl?: string;\n categories?: string[];\n auth?: PieceAuthDescriptor;\n minimumSupportedRelease?: string;\n maximumSupportedRelease?: string;\n actions: PieceActionDescriptor[];\n triggers: PieceTriggerDescriptor[];\n}\n\nfunction hasResolver(prop: ApProperty): boolean {\n return typeof prop.options === \"function\" || typeof prop.props === \"function\";\n}\n\nfunction toPropDescriptor(\n name: string,\n prop: ApProperty,\n resolverId?: string,\n): PiecePropDescriptor {\n const dynamic = hasResolver(prop);\n const descriptor: PiecePropDescriptor = {\n name,\n displayName: prop.displayName ?? name,\n type: prop.type ?? \"UNKNOWN\",\n required: prop.required ?? false,\n hasDynamicResolver: dynamic,\n };\n if (typeof prop.description === \"string\" && prop.description !== \"\") {\n descriptor.description = prop.description;\n }\n if (typeof prop.placeholder === \"string\" && prop.placeholder !== \"\") {\n descriptor.placeholder = prop.placeholder;\n }\n if (prop.defaultValue !== undefined) {\n descriptor.defaultValue = prop.defaultValue;\n }\n if (dynamic && resolverId) {\n descriptor.dynamicResolverId = resolverId;\n }\n if (dynamic && Array.isArray(prop.refreshers)) {\n descriptor.refreshers = prop.refreshers.filter(\n (entry): entry is string => typeof entry === \"string\",\n );\n }\n if (typeof prop.options === \"object\" && Array.isArray(prop.options.options)) {\n descriptor.staticOptions = prop.options.options.map((o) => ({\n label: o.label,\n value: o.value,\n }));\n }\n if (prop.properties && typeof prop.properties === \"object\") {\n const nested = describeProperties(prop.properties);\n if (nested.length > 0) descriptor.properties = nested;\n }\n return descriptor;\n}\n\n// Descriptor list for a props map: nested ARRAY items and what a DYNAMIC\n// resolver returns, so the editor never sees raw piece properties.\nexport function describeProperties(\n props: Record<string, ApProperty> | null | undefined,\n resolverIdFor?: (propName: string) => string,\n): PiecePropDescriptor[] {\n if (!props || typeof props !== \"object\") return [];\n return Object.entries(props)\n .filter((entry): entry is [string, ApProperty] =>\n isPropertyObject(entry[1]),\n )\n .map(([propName, prop]) =>\n toPropDescriptor(propName, prop, resolverIdFor?.(propName)),\n );\n}\n\n// Bundles are duck-typed; a props map may carry non-object junk.\nfunction isPropertyObject(value: unknown): value is ApProperty {\n return value !== null && typeof value === \"object\";\n}\n\n// Pure translation over a loaded piece; performs no I/O and never executes\n// piece code beyond the actions()/triggers() accessors.\nexport function buildDescriptor(\n piece: ApPiece,\n source: PieceSource,\n): PieceDescriptor {\n const actions = Object.entries(getActions(piece)).map(\n ([actionName, action]): PieceActionDescriptor => ({\n name: action.name ?? actionName,\n displayName: action.displayName ?? actionName,\n description: action.description,\n requireAuth: action.requireAuth ?? false,\n props: describeProperties(\n action.props,\n (propName) =>\n `activepieces:${source.packageName}#${actionName}.${propName}`,\n ),\n }),\n );\n\n const triggers = Object.entries(getTriggers(piece)).map(\n ([triggerName, trigger]): PieceTriggerDescriptor => ({\n name: trigger.name ?? triggerName,\n displayName: trigger.displayName ?? triggerName,\n description: trigger.description,\n strategy: trigger.type ?? \"UNKNOWN\",\n testStrategy: trigger.testStrategy,\n requireAuth: trigger.requireAuth ?? false,\n props: describeProperties(\n trigger.props,\n (propName) =>\n `activepieces:${source.packageName}#${triggerName}.${propName}`,\n ),\n hasSampleData:\n trigger.sampleData !== undefined && trigger.sampleData !== null,\n handshake: describeHandshake(trigger),\n }),\n );\n\n const descriptor: PieceDescriptor = {\n id: `activepieces:${source.packageName}`,\n source,\n displayName: piece.displayName,\n description: piece.description,\n logoUrl: piece.logoUrl,\n categories: piece.categories,\n minimumSupportedRelease: piece.minimumSupportedRelease,\n maximumSupportedRelease: piece.maximumSupportedRelease,\n actions,\n triggers,\n };\n if (piece.auth && typeof piece.auth === \"object\") {\n // CUSTOM_AUTH carries a record here; a DYNAMIC prop would carry a\n // resolver function, which is not an auth shape at all.\n const authProps =\n piece.auth.props && typeof piece.auth.props === \"object\"\n ? describeProperties(piece.auth.props)\n : [];\n descriptor.auth = {\n type: piece.auth.type ?? \"UNKNOWN\",\n displayName: piece.auth.displayName,\n description: piece.auth.description,\n required: piece.auth.required,\n ...(authProps.length > 0 ? { props: authProps } : {}),\n };\n }\n return descriptor;\n}\n"],"mappings":";;;;;;AAeA,SAAgB,aAAa,UAA0B;CACrD,MAAM,MAAM,aAAa,KAAK,KAAK,UAAU,eAAe,EAAE,OAAO;CACrE,MAAM,MAAM,KAAK,MAAM,IAAI;CAK3B,MAAM,YAAY,IAAI,UAAU;CAChC,MAAM,aAAqC,EAAE;AAC7C,KAAI,OAAO,cAAc,SAAU,YAAW,KAAK,UAAU;AAC7D,KAAI,aAAa,OAAO,cAAc,UAAU;EAC9C,MAAM,OAAO;AACb,OAAK,MAAM,OAAO;GAAC;GAAU;GAAW;GAAU,EAAE;GAClD,MAAM,QAAQ,KAAK;AACnB,OAAI,OAAO,UAAU,SAAU,YAAW,KAAK,MAAM;;;AAGzD,YAAW,KAAK,IAAI,MAAM,IAAI,QAAQ,gBAAgB,YAAY,UAAU;CAG5E,MAAM,OAAO,aAAa,SAAS;CACnC,MAAM,cAAc,OAAO,KAAK;AAChC,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,CAAC,UAAW;EAChB,MAAM,MAAM,KAAK,QAAQ,UAAU,UAAU;EAC7C,IAAI;AACJ,MAAI;AACF,UAAO,aAAa,IAAI;UAClB;AACN;;AAEF,MAAI,SAAS,QAAQ,CAAC,KAAK,WAAW,YAAY,CAAE;AACpD,SAAO;;AAET,OAAM,IAAI,MAAM,2CAA2C,WAAW;;AAGxE,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU;;AAKhD,SAAS,UACP,KACkD;CAClD,MAAM,aAAwB;EAC5B,GAAG,OAAO,OAAO,IAAI;EACrB,GAAI,SAAS,IAAI,QAAQ,GAAG,OAAO,OAAO,IAAI,QAAQ,GAAG,EAAE;EAC3D,IAAI;EACL;AACD,MAAK,MAAM,aAAa,WACtB,KAAI,SAAS,UAAU,IAAI,UAAU,YAAY,SAAS,QACxD,QAAO;EACL,OAAO;EACP,OAAO;EACR;AAGL,MAAK,MAAM,aAAa,WACtB,KACE,SAAS,UAAU,IACnB,OAAO,UAAU,gBAAgB,aAChC,SAAS,UAAU,QAAQ,IAC1B,OAAO,UAAU,YAAY,cAC7B,OAAO,UAAU,cAAc,YAEjC,QAAO;EAAE,OAAO;EAAiC,OAAO;EAAc;;AAQ5E,eAAsB,UAAU,WAAyC;CACvE,IAAI;AACJ,KAAI;AACF,QAAO,MAAM;;GACQ,cAAc,UAAU,CAAC;;SAExC;AAEN,QADgB,cAAc,OAAO,KAAK,IAAI,CAChC,UAAU;;CAE1B,MAAM,QAAQ,UAAU,IAAI;AAC5B,KAAI,CAAC,MACH,OAAM,IAAI,MACR,4BAA4B,UAAU,iBAAiB,OAAO,KAAK,IAAI,CAAC,KAAK,KAAK,GACnF;AAEH,QAAO;EAAE,GAAG;EAAO;EAAW;;AAGhC,eAAsB,iBAAiB,UAAwC;AAC7E,QAAO,UAAU,aAAa,SAAS,CAAC;;;;ACxC1C,SAAS,kBACP,SACsD;CACtD,MAAM,WAAW,QAAQ,wBAAwB;AACjD,KAAI,CAAC,YAAY,aAAa,OAAQ,QAAO,KAAA;AAC7C,QAAO;EAAE;EAAU,WAAW,QAAQ,wBAAwB;EAAW;;AAwB3E,SAAS,YAAY,MAA2B;AAC9C,QAAO,OAAO,KAAK,YAAY,cAAc,OAAO,KAAK,UAAU;;AAGrE,SAAS,iBACP,MACA,MACA,YACqB;CACrB,MAAM,UAAU,YAAY,KAAK;CACjC,MAAM,aAAkC;EACtC;EACA,aAAa,KAAK,eAAe;EACjC,MAAM,KAAK,QAAQ;EACnB,UAAU,KAAK,YAAY;EAC3B,oBAAoB;EACrB;AACD,KAAI,OAAO,KAAK,gBAAgB,YAAY,KAAK,gBAAgB,GAC/D,YAAW,cAAc,KAAK;AAEhC,KAAI,OAAO,KAAK,gBAAgB,YAAY,KAAK,gBAAgB,GAC/D,YAAW,cAAc,KAAK;AAEhC,KAAI,KAAK,iBAAiB,KAAA,EACxB,YAAW,eAAe,KAAK;AAEjC,KAAI,WAAW,WACb,YAAW,oBAAoB;AAEjC,KAAI,WAAW,MAAM,QAAQ,KAAK,WAAW,CAC3C,YAAW,aAAa,KAAK,WAAW,QACrC,UAA2B,OAAO,UAAU,SAC9C;AAEH,KAAI,OAAO,KAAK,YAAY,YAAY,MAAM,QAAQ,KAAK,QAAQ,QAAQ,CACzE,YAAW,gBAAgB,KAAK,QAAQ,QAAQ,KAAK,OAAO;EAC1D,OAAO,EAAE;EACT,OAAO,EAAE;EACV,EAAE;AAEL,KAAI,KAAK,cAAc,OAAO,KAAK,eAAe,UAAU;EAC1D,MAAM,SAAS,mBAAmB,KAAK,WAAW;AAClD,MAAI,OAAO,SAAS,EAAG,YAAW,aAAa;;AAEjD,QAAO;;AAKT,SAAgB,mBACd,OACA,eACuB;AACvB,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO,EAAE;AAClD,QAAO,OAAO,QAAQ,MAAM,CACzB,QAAQ,UACP,iBAAiB,MAAM,GAAG,CAC3B,CACA,KAAK,CAAC,UAAU,UACf,iBAAiB,UAAU,MAAM,gBAAgB,SAAS,CAAC,CAC5D;;AAIL,SAAS,iBAAiB,OAAqC;AAC7D,QAAO,UAAU,QAAQ,OAAO,UAAU;;AAK5C,SAAgB,gBACd,OACA,QACiB;CACjB,MAAM,UAAU,OAAO,QAAQ,WAAW,MAAM,CAAC,CAAC,KAC/C,CAAC,YAAY,aAAoC;EAChD,MAAM,OAAO,QAAQ;EACrB,aAAa,OAAO,eAAe;EACnC,aAAa,OAAO;EACpB,aAAa,OAAO,eAAe;EACnC,OAAO,mBACL,OAAO,QACN,aACC,gBAAgB,OAAO,YAAY,GAAG,WAAW,GAAG,WACvD;EACF,EACF;CAED,MAAM,WAAW,OAAO,QAAQ,YAAY,MAAM,CAAC,CAAC,KACjD,CAAC,aAAa,cAAsC;EACnD,MAAM,QAAQ,QAAQ;EACtB,aAAa,QAAQ,eAAe;EACpC,aAAa,QAAQ;EACrB,UAAU,QAAQ,QAAQ;EAC1B,cAAc,QAAQ;EACtB,aAAa,QAAQ,eAAe;EACpC,OAAO,mBACL,QAAQ,QACP,aACC,gBAAgB,OAAO,YAAY,GAAG,YAAY,GAAG,WACxD;EACD,eACE,QAAQ,eAAe,KAAA,KAAa,QAAQ,eAAe;EAC7D,WAAW,kBAAkB,QAAQ;EACtC,EACF;CAED,MAAM,aAA8B;EAClC,IAAI,gBAAgB,OAAO;EAC3B;EACA,aAAa,MAAM;EACnB,aAAa,MAAM;EACnB,SAAS,MAAM;EACf,YAAY,MAAM;EAClB,yBAAyB,MAAM;EAC/B,yBAAyB,MAAM;EAC/B;EACA;EACD;AACD,KAAI,MAAM,QAAQ,OAAO,MAAM,SAAS,UAAU;EAGhD,MAAM,YACJ,MAAM,KAAK,SAAS,OAAO,MAAM,KAAK,UAAU,WAC5C,mBAAmB,MAAM,KAAK,MAAM,GACpC,EAAE;AACR,aAAW,OAAO;GAChB,MAAM,MAAM,KAAK,QAAQ;GACzB,aAAa,MAAM,KAAK;GACxB,aAAa,MAAM,KAAK;GACxB,UAAU,MAAM,KAAK;GACrB,GAAI,UAAU,SAAS,IAAI,EAAE,OAAO,WAAW,GAAG,EAAE;GACrD;;AAEH,QAAO"}