@lunnoa/client 2.4.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_...
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,6 +1832,11 @@ 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 ?? {},
@@ -2037,7 +2069,14 @@ function stepToUpsertInput(step) {
2037
2069
  ...step.input ?? {}
2038
2070
  },
2039
2071
  name: step.name ?? use.name,
2040
- 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
+ } : {}
2041
2080
  };
2042
2081
  }
2043
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
  }
@@ -603,32 +629,44 @@ function collectRefs(value, out) {
603
629
  Object.values(value).forEach((item) => collectRefs(item, out));
604
630
  }
605
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
+ }
606
657
  function assertStepRefs(config) {
607
658
  const triggerId = config.trigger?.id.trim() ?? MANUAL_TRIGGER_ID;
608
659
  const triggerFields = triggerId === MANUAL_TRIGGER_ID ? new Set((config.trigger?.inputs ?? []).map((f) => f.id.trim())) : void 0;
609
660
  const allKeys = /* @__PURE__ */ new Set();
610
- for (const step of config.steps) {
611
- if (step.key === void 0) continue;
612
- if (!STEP_KEY_PATTERN.test(step.key) || step.key === TRIGGER_KEY) {
613
- throw new Error(
614
- `Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "${TRIGGER_KEY}".`
615
- );
616
- }
617
- if (allKeys.has(step.key)) {
618
- throw new Error(`Step key "${step.key}" is used twice.`);
619
- }
620
- allKeys.add(step.key);
621
- }
622
- const earlier = /* @__PURE__ */ new Set([TRIGGER_KEY]);
623
- config.steps.forEach((step, index) => {
661
+ collectKeys(config.steps, allKeys);
662
+ const checkRefs = (value, earlier, where) => {
624
663
  const refs = [];
625
- collectRefs([step.use?.input, step.input], refs);
626
- const where = `Step "${step.key ?? step.name ?? index + 1}"`;
664
+ collectRefs(value, refs);
627
665
  for (const [key = "", ...path] of refs) {
628
666
  const shown = `={{step:${[key, ...path].join(",")}}}`;
629
667
  if (!earlier.has(key)) {
630
668
  throw new Error(
631
- allKeys.has(key) ? `${where}: ${shown} refers to itself or a later step. Use the trigger or an earlier step.` : `${where}: ${shown} names unknown key "${key}". Use "trigger" or an earlier step's key.`
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.`
632
670
  );
633
671
  }
634
672
  if (key === TRIGGER_KEY && triggerFields && !triggerFields.has(path[0] ?? "")) {
@@ -637,8 +675,54 @@ function assertStepRefs(config) {
637
675
  );
638
676
  }
639
677
  }
640
- if (step.key) earlier.add(step.key);
641
- });
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]));
642
726
  }
643
727
  function defineWorkflow(config) {
644
728
  if (!config.slug?.trim()) {
@@ -1937,6 +2021,11 @@ function stepFromDefinition(step) {
1937
2021
  ...step.name ? { name: step.name } : {}
1938
2022
  };
1939
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
+ }
1940
2029
  const use = step.use;
1941
2030
  const input = {
1942
2031
  ...use.input ?? {},
@@ -2169,7 +2258,14 @@ function stepToUpsertInput(step) {
2169
2258
  ...step.input ?? {}
2170
2259
  },
2171
2260
  name: step.name ?? use.name,
2172
- 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
+ } : {}
2173
2269
  };
2174
2270
  }
2175
2271
  function workflowDefinitionToUpsert(def) {
@@ -2397,6 +2493,7 @@ exports.HttpClient = HttpClient;
2397
2493
  exports.KnowledgeResource = KnowledgeResource;
2398
2494
  exports.LunnoaApiError = LunnoaApiError;
2399
2495
  exports.LunnoaClient = LunnoaClient;
2496
+ exports.PATH_CONDITIONS = PATH_CONDITIONS;
2400
2497
  exports.QueueItemsResource = QueueItemsResource;
2401
2498
  exports.QueuesResource = QueuesResource;
2402
2499
  exports.RunsResource = RunsResource;