primitive-admin 1.1.0-alpha.41 → 1.1.0-alpha.42

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.
@@ -18,19 +18,11 @@
18
18
  * 3. a SECOND `syncCallable` PATCH — issued AFTER the new steps are active,
19
19
  * so it validates against the new (compatible) steps.
20
20
  */
21
- /**
22
- * Safely parse a JSON field that may be a string or already an object. TOML can
23
- * parse inline tables as objects, so we accept both. (Same semantics as the
24
- * `safeJsonParse` in sync.ts kept here so the shared apply helper owns its
25
- * own parse decision without importing back into a command module.)
26
- */
27
- export function safeJsonParse(value) {
28
- if (value === undefined || value === null)
29
- return undefined;
30
- if (typeof value === "string")
31
- return JSON.parse(value);
32
- return value; // Already an object (TOML parsed it as a table)
33
- }
21
+ import { buildWorkflowPayloadFromToml, safeJsonParse } from "./workflow-payload.js";
22
+ // #1177: `safeJsonParse` now lives in the shared `workflow-payload` lib module
23
+ // (the builder needs it too); re-exported here so existing importers of this
24
+ // module keep working. Both `cli/src/lib` modules; no dep-direction problem.
25
+ export { safeJsonParse };
34
26
  /**
35
27
  * Apply a parsed workflow body (metadata + steps) to an existing workflow,
36
28
  * preserving the key/id. `workflow` is the TOML's `[workflow]` table (already
@@ -40,27 +32,15 @@ export function safeJsonParse(value) {
40
32
  */
41
33
  export async function applyWorkflowBody(client, appId, workflowId, workflow, steps, opts = {}) {
42
34
  const { expectedModifiedAt, activeConfigId, force } = opts;
43
- // 1. Metadata PATCH — note: NO `syncCallable` here (deferred to step 3).
44
- // `requiresClientApply` has no step-dependent validation, so it stays.
35
+ // 1. Metadata PATCH — note: NO `syncCallable` here (deferred to step 3). The
36
+ // shared builder (#1177, `mode: "update"`) omits `syncCallable` for exactly
37
+ // this reason and applies the presence-aware clear-vs-leave semantics for
38
+ // `accessRule` / `runAs` / `capabilities` / schemas. `requiresClientApply`
39
+ // has no step-dependent validation, so the builder keeps it here. The
40
+ // `metadataManifest` declared-access manifest (#1304) is call-site-specific
41
+ // and attached below, not owned by the shared builder.
45
42
  const updated = await client.updateWorkflow(appId, workflowId, {
46
- name: workflow.name,
47
- description: workflow.description,
48
- status: workflow.status,
49
- accessRule: workflow.accessRule !== undefined ? workflow.accessRule || null : undefined,
50
- // #1081 — workflow principal mode. Absent key → undefined (server leaves
51
- // it untouched); explicit value/empty is forwarded so it round-trips.
52
- runAs: workflow.runAs !== undefined ? workflow.runAs || null : undefined,
53
- // #1233 — opt-in capability grants. Absent key → undefined (server's
54
- // hasOwnProperty guard leaves it untouched); an explicit array is
55
- // forwarded so a pull → push round-trips it. Mirrors `runAs` above so
56
- // both `sync push` and `workflows update --from-file` apply it the same.
57
- capabilities: workflow.capabilities !== undefined ? workflow.capabilities : undefined,
58
- perUserMaxRunning: workflow.perUserMaxRunning,
59
- perUserMaxQueued: workflow.perUserMaxQueued,
60
- dequeueOrder: workflow.dequeueOrder,
61
- requiresClientApply: workflow.requiresClientApply,
62
- inputSchema: workflow.inputSchema ? safeJsonParse(workflow.inputSchema) : null,
63
- outputSchema: workflow.outputSchema ? safeJsonParse(workflow.outputSchema) : null,
43
+ ...buildWorkflowPayloadFromToml(workflow, { mode: "update" }),
64
44
  // #1304 (P-C): declared-access manifest. Absent key → undefined (server's
65
45
  // hasOwnProperty guard leaves it untouched); a parsed value/null is
66
46
  // forwarded so a pull → push round-trips it (null clears). `sync push`
@@ -96,6 +76,36 @@ export async function applyWorkflowBody(client, appId, workflowId, workflow, ste
96
76
  const fullWorkflow = await client.getWorkflow(appId, workflowId);
97
77
  return { latestModifiedAt, updateSlotLabel, fullWorkflow };
98
78
  }
79
+ /**
80
+ * #1232 — parse the `workflows update --capabilities` flag into the array the
81
+ * server's update handler expects. This is the CLI fallback the issue asked
82
+ * for ("workflows update has no capability flag"): a grant/revoke channel for
83
+ * an existing workflow, since `sync push` create deliberately does not forward
84
+ * capabilities (#1233, Q2) and there was otherwise no way to set them on an
85
+ * existing workflow.
86
+ *
87
+ * Semantics (mirrors how the server normalizes the field):
88
+ * --capabilities membership → ["membership"] (grant)
89
+ * --capabilities membership,secrets → ["membership","secrets"]
90
+ * --capabilities " membership , x " → ["membership","x"] (trimmed)
91
+ * --capabilities "" → [] (revoke all;
92
+ * server stores empty as null)
93
+ *
94
+ * The set of grantable capability names ("membership" / "secrets") is validated
95
+ * server-side by `validateWorkflowIdentityConfig`; the CLI intentionally does
96
+ * NOT duplicate that allowlist so it can't drift from the server. An unknown
97
+ * name is surfaced via the server's structured `details[]` error.
98
+ *
99
+ * The caller applies presence-guarded plumbing (`options.capabilities !==
100
+ * undefined`) so an omitted flag never reaches the payload — the server's
101
+ * `hasOwnProperty` guard then leaves the existing grant untouched.
102
+ */
103
+ export function parseCapabilitiesFlag(value) {
104
+ return value
105
+ .split(",")
106
+ .map((cap) => cap.trim())
107
+ .filter((cap) => cap.length > 0);
108
+ }
99
109
  /**
100
110
  * Fork 2 (#1249): a TOML pushed at a specific `<id>` may also declare a `key` /
101
111
  * `workflowKey`. If it differs from the target's actual key, that's almost
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-apply.js","sourceRoot":"","sources":["../../../src/lib/workflow-apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAU;IACtC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxD,OAAO,KAAK,CAAC,CAAC,gDAAgD;AAChE,CAAC;AAwBD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAiB,EACjB,KAAa,EACb,UAAkB,EAClB,QAAa,EACb,KAAY,EACZ,OAAiC,EAAE;IAEnC,MAAM,EAAE,kBAAkB,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAE3D,yEAAyE;IACzE,0EAA0E;IAC1E,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,cAAc,CACzC,KAAK,EACL,UAAU,EACV;QACE,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EACR,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS;QAC7E,yEAAyE;QACzE,sEAAsE;QACtE,KAAK,EAAE,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS;QACxE,qEAAqE;QACrE,kEAAkE;QAClE,sEAAsE;QACtE,yEAAyE;QACzE,YAAY,EACV,QAAQ,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;QACzE,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;QAC7C,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;QAC3C,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB;QACjD,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;QAC9E,YAAY,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;QACjF,0EAA0E;QAC1E,oEAAoE;QACpE,uEAAuE;QACvE,oEAAoE;QACpE,gEAAgE;QAChE,gBAAgB,EACd,QAAQ,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS;KAClF,EACD,kBAAkB,CACnB,CAAC;IAEF,4EAA4E;IAC5E,4EAA4E;IAC5E,gBAAgB;IAChB,IAAI,gBAAgB,GAAuB,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAEzE,qEAAqE;IACrE,IAAI,eAAe,GAAG,eAAe,CAAC;IACtC,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,MAAM,CAAC,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,CAAC,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE;YAClD,KAAK;YACL,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;SAC/E,CAAC,CAAC;QACH,eAAe,GAAG,gBAAgB,CAAC;IACrC,CAAC;IAED,+EAA+E;IAC/E,2EAA2E;IAC3E,4EAA4E;IAC5E,IAAI,QAAQ,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACxC,MAAM,mBAAmB,GAAG,MAAM,MAAM,CAAC,cAAc,CACrD,KAAK,EACL,UAAU,EACV,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,EACvC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CACrC,CAAC;QACF,IAAI,mBAAmB,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;YAC9C,gBAAgB,GAAG,mBAAmB,CAAC,QAAQ,CAAC,UAAU,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACjE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACtC,YAAiB,EACjB,mBAAuC;IAEvC,MAAM,OAAO,GAAG,YAAY,EAAE,GAAG,IAAI,YAAY,EAAE,WAAW,CAAC;IAC/D,IAAI,OAAO,IAAI,mBAAmB,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QACtE,OAAO,CACL,sBAAsB,OAAO,6CAA6C;YAC1E,IAAI,mBAAmB,uDAAuD;YAC9E,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"workflow-apply.js","sourceRoot":"","sources":["../../../src/lib/workflow-apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared TOML → workflow-payload builder (#1177).
3
+ *
4
+ * Background: the CLI built the create/update workflow payload in THREE
5
+ * separate hand-enumerated literals — `workflows create --from-file`
6
+ * (`cli/src/commands/workflows.ts`), the `sync push` create branch
7
+ * (`cli/src/commands/sync.ts`), and the `sync push` update branch (the metadata
8
+ * PATCH in `cli/src/lib/workflow-apply.ts`). Because each site listed the
9
+ * `[workflow]` fields by hand, they drifted apart: a field added to one path was
10
+ * silently dropped by the others. `status` was the 4th field lost this way
11
+ * (after `syncCallable` #807, `accessRule` #571, `runAs` #1081); the audit also
12
+ * found `capabilities` (#1172) and `perAppMaxRunning` / `perAppMaxQueued` /
13
+ * `queueTtlSeconds` dropped from at least one path.
14
+ *
15
+ * The fix: one shared builder that owns the `[workflow]`-derived field list.
16
+ * All three call-sites spread its output; only the per-field presence coalescing
17
+ * and the create-only `syncCallable` inclusion differ by `mode`, so the field
18
+ * list itself lives in exactly one place and can't drift again. A unit
19
+ * drift-guard asserts the builder's key set (see
20
+ * `cli/tests/unit/workflow-payload-fields.test.ts`).
21
+ *
22
+ * What the builder does NOT own (structural / call-site-specific wiring the
23
+ * callers still attach): `workflowKey`, `steps`, and the `metadataManifest`
24
+ * declared-access manifest (#1304) — the sync paths always attach it, create
25
+ * --from-file never does.
26
+ *
27
+ * Presence semantics, by mode:
28
+ * - create: optional strings coalesce absent/empty → `undefined` (omit; the
29
+ * server default applies). `capabilities` forwards the raw array (absent →
30
+ * omit). Schemas omit when absent.
31
+ * - update: optional strings distinguish explicit-clear from leave-unset via
32
+ * `x !== undefined ? (x || null) : undefined`. `status` forwards the bare
33
+ * value (matching the server's hasOwnProperty path). Schemas send `null`
34
+ * when absent so a cleared schema round-trips.
35
+ * - booleans (`requiresClientApply`, and `syncCallable` on create) are raw
36
+ * pass-through so an explicit `false` is preserved and only a truly-absent
37
+ * key drops.
38
+ * - `syncCallable` is OMITTED in `mode: "update"`: the server re-validates
39
+ * `syncCallable: true` against the CURRENTLY-active steps, so the update
40
+ * path must send it as a deferred second PATCH after the new steps are live
41
+ * (#807). `applyWorkflowBody` owns that second PATCH.
42
+ */
43
+ export type WorkflowPayloadMode = "create" | "update";
44
+ /**
45
+ * Parse a JSON schema field that TOML may present as a string OR (for an inline
46
+ * table) as an already-parsed object. Kept here in `cli/src/lib` so the builder
47
+ * owns its own parse decision rather than importing back into a command module
48
+ * (#1177 dependency-direction decision).
49
+ */
50
+ export declare function safeJsonParse(value: any): any;
51
+ /**
52
+ * The `[workflow]`-derived keys the builder emits, per mode. Exported so the
53
+ * drift-guard unit test can assert the key set hasn't silently lost a field —
54
+ * the recurring bug class this builder closes. `update` omits `syncCallable`
55
+ * (deferred second PATCH, #807); everything else is shared.
56
+ */
57
+ export declare const WORKFLOW_PAYLOAD_FIELDS_CREATE: readonly ["name", "description", "status", "accessRule", "runAs", "capabilities", "perUserMaxRunning", "perUserMaxQueued", "perAppMaxRunning", "perAppMaxQueued", "queueTtlSeconds", "dequeueOrder", "requiresClientApply", "inputSchema", "outputSchema", "syncCallable"];
58
+ export declare const WORKFLOW_PAYLOAD_FIELDS_UPDATE: ReadonlyArray<string>;
59
+ /**
60
+ * Build the workflow payload's `[workflow]`-derived fields from a parsed TOML
61
+ * `[workflow]` table. `workflow` is `tomlData.workflow` (already merged with any
62
+ * explicit CLI flag overrides by the caller). Callers spread the result into
63
+ * the create/update payload and attach the structural fields themselves
64
+ * (`workflowKey`, `steps`, `metadataManifest`).
65
+ */
66
+ export declare function buildWorkflowPayloadFromToml(workflow: any, { mode }: {
67
+ mode: WorkflowPayloadMode;
68
+ }): Record<string, any>;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Shared TOML → workflow-payload builder (#1177).
3
+ *
4
+ * Background: the CLI built the create/update workflow payload in THREE
5
+ * separate hand-enumerated literals — `workflows create --from-file`
6
+ * (`cli/src/commands/workflows.ts`), the `sync push` create branch
7
+ * (`cli/src/commands/sync.ts`), and the `sync push` update branch (the metadata
8
+ * PATCH in `cli/src/lib/workflow-apply.ts`). Because each site listed the
9
+ * `[workflow]` fields by hand, they drifted apart: a field added to one path was
10
+ * silently dropped by the others. `status` was the 4th field lost this way
11
+ * (after `syncCallable` #807, `accessRule` #571, `runAs` #1081); the audit also
12
+ * found `capabilities` (#1172) and `perAppMaxRunning` / `perAppMaxQueued` /
13
+ * `queueTtlSeconds` dropped from at least one path.
14
+ *
15
+ * The fix: one shared builder that owns the `[workflow]`-derived field list.
16
+ * All three call-sites spread its output; only the per-field presence coalescing
17
+ * and the create-only `syncCallable` inclusion differ by `mode`, so the field
18
+ * list itself lives in exactly one place and can't drift again. A unit
19
+ * drift-guard asserts the builder's key set (see
20
+ * `cli/tests/unit/workflow-payload-fields.test.ts`).
21
+ *
22
+ * What the builder does NOT own (structural / call-site-specific wiring the
23
+ * callers still attach): `workflowKey`, `steps`, and the `metadataManifest`
24
+ * declared-access manifest (#1304) — the sync paths always attach it, create
25
+ * --from-file never does.
26
+ *
27
+ * Presence semantics, by mode:
28
+ * - create: optional strings coalesce absent/empty → `undefined` (omit; the
29
+ * server default applies). `capabilities` forwards the raw array (absent →
30
+ * omit). Schemas omit when absent.
31
+ * - update: optional strings distinguish explicit-clear from leave-unset via
32
+ * `x !== undefined ? (x || null) : undefined`. `status` forwards the bare
33
+ * value (matching the server's hasOwnProperty path). Schemas send `null`
34
+ * when absent so a cleared schema round-trips.
35
+ * - booleans (`requiresClientApply`, and `syncCallable` on create) are raw
36
+ * pass-through so an explicit `false` is preserved and only a truly-absent
37
+ * key drops.
38
+ * - `syncCallable` is OMITTED in `mode: "update"`: the server re-validates
39
+ * `syncCallable: true` against the CURRENTLY-active steps, so the update
40
+ * path must send it as a deferred second PATCH after the new steps are live
41
+ * (#807). `applyWorkflowBody` owns that second PATCH.
42
+ */
43
+ /**
44
+ * Parse a JSON schema field that TOML may present as a string OR (for an inline
45
+ * table) as an already-parsed object. Kept here in `cli/src/lib` so the builder
46
+ * owns its own parse decision rather than importing back into a command module
47
+ * (#1177 dependency-direction decision).
48
+ */
49
+ export function safeJsonParse(value) {
50
+ if (value === undefined || value === null)
51
+ return undefined;
52
+ if (typeof value === "string")
53
+ return JSON.parse(value);
54
+ return value; // Already an object (TOML parsed it as a table)
55
+ }
56
+ /**
57
+ * The `[workflow]`-derived keys the builder emits, per mode. Exported so the
58
+ * drift-guard unit test can assert the key set hasn't silently lost a field —
59
+ * the recurring bug class this builder closes. `update` omits `syncCallable`
60
+ * (deferred second PATCH, #807); everything else is shared.
61
+ */
62
+ export const WORKFLOW_PAYLOAD_FIELDS_CREATE = [
63
+ "name",
64
+ "description",
65
+ "status",
66
+ "accessRule",
67
+ "runAs",
68
+ "capabilities",
69
+ "perUserMaxRunning",
70
+ "perUserMaxQueued",
71
+ "perAppMaxRunning",
72
+ "perAppMaxQueued",
73
+ "queueTtlSeconds",
74
+ "dequeueOrder",
75
+ "requiresClientApply",
76
+ "inputSchema",
77
+ "outputSchema",
78
+ "syncCallable",
79
+ ];
80
+ export const WORKFLOW_PAYLOAD_FIELDS_UPDATE = WORKFLOW_PAYLOAD_FIELDS_CREATE.filter((f) => f !== "syncCallable");
81
+ /**
82
+ * Build the workflow payload's `[workflow]`-derived fields from a parsed TOML
83
+ * `[workflow]` table. `workflow` is `tomlData.workflow` (already merged with any
84
+ * explicit CLI flag overrides by the caller). Callers spread the result into
85
+ * the create/update payload and attach the structural fields themselves
86
+ * (`workflowKey`, `steps`, `metadataManifest`).
87
+ */
88
+ export function buildWorkflowPayloadFromToml(workflow, { mode }) {
89
+ const create = mode === "create";
90
+ const payload = {
91
+ name: workflow.name,
92
+ description: workflow.description,
93
+ // status — create coalesces absent/empty → omit (server default `draft`
94
+ // applies); update forwards the bare value so it round-trips and an
95
+ // explicit "" surfaces the server's validation error rather than a silent
96
+ // no-op (matches the pre-existing update path).
97
+ status: create ? workflow.status || undefined : workflow.status,
98
+ // accessRule / runAs — create coalesces absent/empty → omit; update
99
+ // distinguishes explicit clear ("" → null) from leave-unset (absent →
100
+ // undefined).
101
+ accessRule: create
102
+ ? workflow.accessRule || undefined
103
+ : workflow.accessRule !== undefined
104
+ ? workflow.accessRule || null
105
+ : undefined,
106
+ runAs: create
107
+ ? workflow.runAs || undefined
108
+ : workflow.runAs !== undefined
109
+ ? workflow.runAs || null
110
+ : undefined,
111
+ // capabilities — create forwards the raw array (absent → omit); update
112
+ // forwards presence-aware so an explicit array round-trips. (The server
113
+ // update PATCH persist for capabilities is tracked separately in #1232; the
114
+ // round-trip guard asserts capabilities on the create leg only.)
115
+ capabilities: create
116
+ ? workflow.capabilities
117
+ : workflow.capabilities !== undefined
118
+ ? workflow.capabilities
119
+ : undefined,
120
+ // Queue limits — raw numeric pass-through both modes (absent → omit).
121
+ perUserMaxRunning: workflow.perUserMaxRunning,
122
+ perUserMaxQueued: workflow.perUserMaxQueued,
123
+ perAppMaxRunning: workflow.perAppMaxRunning,
124
+ perAppMaxQueued: workflow.perAppMaxQueued,
125
+ queueTtlSeconds: workflow.queueTtlSeconds,
126
+ dequeueOrder: workflow.dequeueOrder,
127
+ // requiresClientApply — raw boolean pass-through so an explicit `false` is
128
+ // preserved and only a truly-absent key drops (server default applies).
129
+ requiresClientApply: workflow.requiresClientApply,
130
+ // Schemas — parse a JSON string (or accept an inline table). Create omits
131
+ // when absent; update sends null so a cleared schema round-trips.
132
+ inputSchema: workflow.inputSchema
133
+ ? safeJsonParse(workflow.inputSchema)
134
+ : create
135
+ ? undefined
136
+ : null,
137
+ outputSchema: workflow.outputSchema
138
+ ? safeJsonParse(workflow.outputSchema)
139
+ : create
140
+ ? undefined
141
+ : null,
142
+ };
143
+ // syncCallable — create includes it (raw boolean pass-through); update omits
144
+ // it (deferred second PATCH, #807 — see module doc).
145
+ if (create) {
146
+ payload.syncCallable = workflow.syncCallable;
147
+ }
148
+ return payload;
149
+ }
150
+ //# sourceMappingURL=workflow-payload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-payload.js","sourceRoot":"","sources":["../../../src/lib/workflow-payload.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primitive-admin",
3
- "version": "1.1.0-alpha.41",
3
+ "version": "1.1.0-alpha.42",
4
4
  "description": "CLI for administering Primitive applications",
5
5
  "type": "module",
6
6
  "bin": {