@lunnoa/client 2.3.0 → 2.5.0

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 CHANGED
@@ -249,7 +249,7 @@ Re-run codegen whenever entity types, workflows, or agents change on the deploym
249
249
 
250
250
  ## Define, run, and CLI deploy
251
251
 
252
- Author local definitions with `defineWorkflow` / `defineAgent` / `defineEntityType`, run linear chains with `lunnoa.runs.start`, and upsert with the CLI (prefer CLI over calling upsert from app code). Agent and workflow upsert is by slug inside the workspace on the API key. An entity type slug is unique on the deployment. Deploy grants that type to the API key's workspace when the slug is new, and will not rewrite a type already granted to other workspaces. Instance writes reject unknown attribute keys unless `allowUnknownAttributes` is true — use the reserved `extensions` object for flexible bags:
252
+ Author local definitions with `defineWorkflow` / `defineAgent` / `defineEntityType`, run linear chains with `lunnoa.runs.start`, branch deployed workflows with `paths` on Request Approval or Conditional Paths, and upsert with the CLI (prefer CLI over calling upsert from app code). Agent and workflow upsert is by slug inside the workspace on the API key. An entity type slug is unique on the deployment. Deploy grants that type to the API key's workspace when the slug is new, and will not rewrite a type already granted to other workspaces. Instance writes reject unknown attribute keys unless `allowUnknownAttributes` is true — use the reserved `extensions` object for flexible bags:
253
253
 
254
254
  ```bash
255
255
  npx @lunnoa/client entity-types deploy ./human.ts --url https://lunnoa.example --api-key lna_...
@@ -264,7 +264,7 @@ npx @lunnoa/client agents deploy ./agents/triage.ts \
264
264
  --url https://lunnoa.example --api-key lna_...
265
265
  ```
266
266
 
267
- `defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. `defineWorkflow` takes an optional `defineTrigger`. Omit it for Manually Run with no input fields. `runs.start` ignores the trigger and runs the steps only. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
267
+ `defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. `defineWorkflow` takes an optional `defineTrigger`. Omit it for Manually Run with no input fields. `runs.start` ignores the trigger and runs the steps only. Pass data into step settings with `ref.trigger('caseId')` or `ref.step('load', 'rows', 0)` (give the source step a `key`); the server resolves them on deploy and the builder shows them as mapped fields. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
268
268
 
269
269
  ### CLI discovery
270
270
 
package/dist/cli.js CHANGED
@@ -546,6 +546,33 @@ var DEFINE_KINDS = {
546
546
  agent: "agent",
547
547
  entityType: "entityType"
548
548
  };
549
+ var PATH_CONDITIONS = [
550
+ "contains",
551
+ "does_not_contain",
552
+ "equals",
553
+ "does_not_equal",
554
+ "exists",
555
+ "does_not_exist",
556
+ "starts_with",
557
+ "ends_with",
558
+ "is_empty",
559
+ "is_not_empty",
560
+ "comes_before",
561
+ "comes_after",
562
+ "is_in_the_past",
563
+ "is_in_the_future",
564
+ "is_true",
565
+ "is_false",
566
+ "is_truthy",
567
+ "is_falsey",
568
+ "is_greater_than",
569
+ "is_less_than",
570
+ "is_greater_than_or_equal_to",
571
+ "is_less_than_or_equal_to",
572
+ "matches_regex",
573
+ "does_not_match_regex"
574
+ ];
575
+ var PATH_CONDITION_SET = new Set(PATH_CONDITIONS);
549
576
  function resolveActionAppId(action) {
550
577
  if (action.appId?.trim()) {
551
578
  return action.appId.trim();
@@ -1805,12 +1832,19 @@ function stepFromDefinition(step) {
1805
1832
  ...step.name ? { name: step.name } : {}
1806
1833
  };
1807
1834
  }
1835
+ if (step.paths?.length) {
1836
+ throw new Error(
1837
+ "runs.start runs straight lines only. Deploy workflows with paths, then execute them."
1838
+ );
1839
+ }
1808
1840
  const use = step.use;
1809
1841
  const input = {
1810
1842
  ...use.input ?? {},
1811
1843
  ...step.input ?? {}
1812
1844
  };
1845
+ const key = step.key;
1813
1846
  return {
1847
+ ...key ? { key } : {},
1814
1848
  appId: resolveActionAppId(use),
1815
1849
  actionId: resolveActionId(use),
1816
1850
  ...use.connection?.id ? { connectionId: use.connection.id } : {},
@@ -2026,6 +2060,7 @@ function triggerToUpsertInput(trigger) {
2026
2060
  function stepToUpsertInput(step) {
2027
2061
  const use = step.use;
2028
2062
  return {
2063
+ ...step.key ? { key: step.key } : {},
2029
2064
  appId: resolveActionAppId(use),
2030
2065
  actionId: resolveActionId(use),
2031
2066
  connectionId: use.connection?.id,
@@ -2034,7 +2069,14 @@ function stepToUpsertInput(step) {
2034
2069
  ...step.input ?? {}
2035
2070
  },
2036
2071
  name: step.name ?? use.name,
2037
- description: use.description
2072
+ description: use.description,
2073
+ ...step.paths?.length ? {
2074
+ paths: step.paths.map((path) => ({
2075
+ label: path.label,
2076
+ ...path.when ? { when: path.when } : {},
2077
+ steps: path.steps.map(stepToUpsertInput)
2078
+ }))
2079
+ } : {}
2038
2080
  };
2039
2081
  }
2040
2082
  function workflowDefinitionToUpsert(def) {
package/dist/index.cjs CHANGED
@@ -542,6 +542,32 @@ var DEFINE_KINDS = {
542
542
  agent: "agent",
543
543
  entityType: "entityType"
544
544
  };
545
+ var PATH_CONDITIONS = [
546
+ "contains",
547
+ "does_not_contain",
548
+ "equals",
549
+ "does_not_equal",
550
+ "exists",
551
+ "does_not_exist",
552
+ "starts_with",
553
+ "ends_with",
554
+ "is_empty",
555
+ "is_not_empty",
556
+ "comes_before",
557
+ "comes_after",
558
+ "is_in_the_past",
559
+ "is_in_the_future",
560
+ "is_true",
561
+ "is_false",
562
+ "is_truthy",
563
+ "is_falsey",
564
+ "is_greater_than",
565
+ "is_less_than",
566
+ "is_greater_than_or_equal_to",
567
+ "is_less_than_or_equal_to",
568
+ "matches_regex",
569
+ "does_not_match_regex"
570
+ ];
545
571
  function defineConnection(config = {}) {
546
572
  return { kind: DEFINE_KINDS.connection, ...config };
547
573
  }
@@ -563,7 +589,7 @@ function assertTriggerConfig(config) {
563
589
  throw new Error("defineTrigger inputs require id");
564
590
  }
565
591
  }
566
- if (config.inputs?.length && config.id.trim() !== "flow-control_trigger_manual") {
592
+ if (config.inputs?.length && config.id.trim() !== MANUAL_TRIGGER_ID) {
567
593
  throw new Error(
568
594
  "defineTrigger inputs are only valid on flow-control_trigger_manual"
569
595
  );
@@ -573,6 +599,131 @@ function defineTrigger(config) {
573
599
  assertTriggerConfig(config);
574
600
  return { kind: DEFINE_KINDS.trigger, ...config };
575
601
  }
602
+ var MANUAL_TRIGGER_ID = "flow-control_trigger_manual";
603
+ var TRIGGER_KEY = "trigger";
604
+ var STEP_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
605
+ var STEP_REF_PATTERN = /={{step:([^}]*)}}/g;
606
+ function refString(key, path) {
607
+ const parts = [key, ...path.map(String)];
608
+ for (const part of parts) {
609
+ if (!part || /[,{}]/.test(part)) {
610
+ throw new Error(`ref path parts must be non-empty and contain no , { or }: "${part}"`);
611
+ }
612
+ }
613
+ return `={{step:${parts.join(",")}}}`;
614
+ }
615
+ var ref = {
616
+ /** A trigger output field, e.g. a Manually Run input id. */
617
+ trigger: (field, ...path) => refString(TRIGGER_KEY, [field, ...path]),
618
+ /** An earlier step's output, by its `key`. Array indexes are numbers. */
619
+ step: (key, ...path) => refString(key, path)
620
+ };
621
+ function collectRefs(value, out) {
622
+ if (typeof value === "string") {
623
+ for (const match of value.matchAll(STEP_REF_PATTERN)) {
624
+ out.push(match[1].split(",").map((part) => part.trim()));
625
+ }
626
+ } else if (Array.isArray(value)) {
627
+ value.forEach((item) => collectRefs(item, out));
628
+ } else if (value && typeof value === "object") {
629
+ Object.values(value).forEach((item) => collectRefs(item, out));
630
+ }
631
+ }
632
+ var CONDITIONAL_PATHS_ID = "flow-control_action_conditional-paths";
633
+ var APPROVAL_ID = "flow-control_action_request-approval";
634
+ var BRANCHING_ACTION_IDS = /* @__PURE__ */ new Set([
635
+ CONDITIONAL_PATHS_ID,
636
+ "flow-control_action_manually-decide-paths",
637
+ APPROVAL_ID
638
+ ]);
639
+ var PATH_CONDITION_SET = new Set(PATH_CONDITIONS);
640
+ var SINGLE_REF_PATTERN = /^={{step:[^}]*}}$/;
641
+ function collectKeys(steps, seen) {
642
+ for (const step of steps) {
643
+ if (step.key !== void 0) {
644
+ if (!STEP_KEY_PATTERN.test(step.key) || step.key === TRIGGER_KEY) {
645
+ throw new Error(
646
+ `Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "${TRIGGER_KEY}".`
647
+ );
648
+ }
649
+ if (seen.has(step.key)) {
650
+ throw new Error(`Step key "${step.key}" is used twice.`);
651
+ }
652
+ seen.add(step.key);
653
+ }
654
+ for (const path of step.paths ?? []) collectKeys(path.steps ?? [], seen);
655
+ }
656
+ }
657
+ function assertStepRefs(config) {
658
+ const triggerId = config.trigger?.id.trim() ?? MANUAL_TRIGGER_ID;
659
+ const triggerFields = triggerId === MANUAL_TRIGGER_ID ? new Set((config.trigger?.inputs ?? []).map((f) => f.id.trim())) : void 0;
660
+ const allKeys = /* @__PURE__ */ new Set();
661
+ collectKeys(config.steps, allKeys);
662
+ const checkRefs = (value, earlier, where) => {
663
+ const refs = [];
664
+ collectRefs(value, refs);
665
+ for (const [key = "", ...path] of refs) {
666
+ const shown = `={{step:${[key, ...path].join(",")}}}`;
667
+ if (!earlier.has(key)) {
668
+ throw new Error(
669
+ allKeys.has(key) ? `${where}: ${shown} refers to itself, a later step, or a step on another path. Use the trigger or an earlier step on the same path.` : `${where}: ${shown} names unknown key "${key}". Use "trigger" or an earlier step's key.`
670
+ );
671
+ }
672
+ if (key === TRIGGER_KEY && triggerFields && !triggerFields.has(path[0] ?? "")) {
673
+ throw new Error(
674
+ `${where}: ${shown} uses trigger field "${path[0] ?? ""}", which is not declared in trigger inputs.`
675
+ );
676
+ }
677
+ }
678
+ };
679
+ const checkChain = (steps, visible) => {
680
+ const earlier = new Set(visible);
681
+ steps.forEach((step, index) => {
682
+ const where = `Step "${step.key ?? step.name ?? index + 1}"`;
683
+ checkRefs([step.use?.input, step.input], earlier, where);
684
+ if (step.key) earlier.add(step.key);
685
+ if (!step.paths?.length) return;
686
+ const actionId = step.use ? resolveActionId(step.use) : "";
687
+ if (!BRANCHING_ACTION_IDS.has(actionId)) {
688
+ throw new Error(`${where}: paths only work on ${[...BRANCHING_ACTION_IDS].join(", ")}.`);
689
+ }
690
+ if (index !== steps.length - 1) {
691
+ throw new Error(
692
+ `${where}: a step with paths must be the last step in its list. Put later steps inside each path.`
693
+ );
694
+ }
695
+ if (actionId === APPROVAL_ID && step.paths.length > 2) {
696
+ throw new Error(`${where}: Request Approval takes at most two paths (approved, then rejected).`);
697
+ }
698
+ const labels = /* @__PURE__ */ new Set();
699
+ for (const path of step.paths) {
700
+ const label = path?.label?.trim();
701
+ if (!label) throw new Error(`${where}: every path needs a label.`);
702
+ if (labels.has(label)) throw new Error(`${where}: path label "${label}" is used twice.`);
703
+ labels.add(label);
704
+ if (path.when !== void 0 && actionId !== CONDITIONAL_PATHS_ID) {
705
+ throw new Error(`${where}: "when" only works on Conditional Paths.`);
706
+ }
707
+ for (const condition of (path.when ?? []).flat()) {
708
+ if (!SINGLE_REF_PATTERN.test(condition?.field?.trim() ?? "")) {
709
+ throw new Error(
710
+ `${where}, path "${label}": a condition's field must be one ref.trigger(...) or ref.step(...).`
711
+ );
712
+ }
713
+ if (!PATH_CONDITION_SET.has(condition.condition)) {
714
+ throw new Error(
715
+ `${where}, path "${label}": condition "${condition.condition}" is not supported. Use one of: ${PATH_CONDITIONS.join(", ")}.`
716
+ );
717
+ }
718
+ }
719
+ checkRefs(path.when, earlier, `${where}, path "${label}"`);
720
+ if (!path.steps?.length) throw new Error(`${where}: path "${label}" needs at least one step.`);
721
+ checkChain(path.steps, earlier);
722
+ }
723
+ });
724
+ };
725
+ checkChain(config.steps, /* @__PURE__ */ new Set([TRIGGER_KEY]));
726
+ }
576
727
  function defineWorkflow(config) {
577
728
  if (!config.slug?.trim()) {
578
729
  throw new Error("defineWorkflow requires slug");
@@ -583,6 +734,7 @@ function defineWorkflow(config) {
583
734
  if (config.trigger) {
584
735
  assertTriggerConfig(config.trigger);
585
736
  }
737
+ assertStepRefs(config);
586
738
  return { kind: DEFINE_KINDS.workflow, ...config };
587
739
  }
588
740
  function defineAgent(config) {
@@ -1869,12 +2021,19 @@ function stepFromDefinition(step) {
1869
2021
  ...step.name ? { name: step.name } : {}
1870
2022
  };
1871
2023
  }
2024
+ if (step.paths?.length) {
2025
+ throw new Error(
2026
+ "runs.start runs straight lines only. Deploy workflows with paths, then execute them."
2027
+ );
2028
+ }
1872
2029
  const use = step.use;
1873
2030
  const input = {
1874
2031
  ...use.input ?? {},
1875
2032
  ...step.input ?? {}
1876
2033
  };
2034
+ const key = step.key;
1877
2035
  return {
2036
+ ...key ? { key } : {},
1878
2037
  appId: resolveActionAppId(use),
1879
2038
  actionId: resolveActionId(use),
1880
2039
  ...use.connection?.id ? { connectionId: use.connection.id } : {},
@@ -2090,6 +2249,7 @@ function triggerToUpsertInput(trigger) {
2090
2249
  function stepToUpsertInput(step) {
2091
2250
  const use = step.use;
2092
2251
  return {
2252
+ ...step.key ? { key: step.key } : {},
2093
2253
  appId: resolveActionAppId(use),
2094
2254
  actionId: resolveActionId(use),
2095
2255
  connectionId: use.connection?.id,
@@ -2098,7 +2258,14 @@ function stepToUpsertInput(step) {
2098
2258
  ...step.input ?? {}
2099
2259
  },
2100
2260
  name: step.name ?? use.name,
2101
- description: use.description
2261
+ description: use.description,
2262
+ ...step.paths?.length ? {
2263
+ paths: step.paths.map((path) => ({
2264
+ label: path.label,
2265
+ ...path.when ? { when: path.when } : {},
2266
+ steps: path.steps.map(stepToUpsertInput)
2267
+ }))
2268
+ } : {}
2102
2269
  };
2103
2270
  }
2104
2271
  function workflowDefinitionToUpsert(def) {
@@ -2326,6 +2493,7 @@ exports.HttpClient = HttpClient;
2326
2493
  exports.KnowledgeResource = KnowledgeResource;
2327
2494
  exports.LunnoaApiError = LunnoaApiError;
2328
2495
  exports.LunnoaClient = LunnoaClient;
2496
+ exports.PATH_CONDITIONS = PATH_CONDITIONS;
2329
2497
  exports.QueueItemsResource = QueueItemsResource;
2330
2498
  exports.QueuesResource = QueuesResource;
2331
2499
  exports.RunsResource = RunsResource;
@@ -2355,6 +2523,7 @@ exports.iterateOffset = iterateOffset;
2355
2523
  exports.iteratePages = iteratePages;
2356
2524
  exports.parseSseEvents = parseSseEvents;
2357
2525
  exports.parseSseStream = parseSseStream;
2526
+ exports.ref = ref;
2358
2527
  exports.resolveActionAppId = resolveActionAppId;
2359
2528
  exports.resolveActionId = resolveActionId;
2360
2529
  exports.resolveTriggerAppId = resolveTriggerAppId;