@lotics/cli 0.187.1 → 0.188.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.
Files changed (2) hide show
  1. package/dist/src/cli.js +193 -212
  2. package/package.json +1 -1
package/dist/src/cli.js CHANGED
@@ -45875,7 +45875,7 @@ function resultSideEffects(result) {
45875
45875
  }
45876
45876
 
45877
45877
  // src/version.ts
45878
- var VERSION = "0.187.1";
45878
+ var VERSION = "0.188.0";
45879
45879
 
45880
45880
  // src/timezone.ts
45881
45881
  function machineTimezone() {
@@ -46584,7 +46584,7 @@ function buildStarterTemplate(args) {
46584
46584
  import { defineConfig } from "vite";
46585
46585
  import type { Plugin } from "vite";
46586
46586
  import react from "@vitejs/plugin-react";
46587
- import { loticsOptimizeDeps, loticsResolve } from "@lotics/ui/vite";
46587
+ import { loticsOptimizeDeps, loticsOptimizeExclude, loticsResolve } from "@lotics/ui/vite";
46588
46588
 
46589
46589
  // @lotics/ui/icon.tsx deep-imports \`lucide-react-native/dist/esm/icons/<name>\`.
46590
46590
  // lucide-react-native's \`exports\` map lists only "." and "./icons", so a strict
@@ -46646,6 +46646,11 @@ export default defineConfig({
46646
46646
  // require across a major bump \u2014 the per-family rationale lives in that module.
46647
46647
  // To add app-specific entries, spread: include: [...loticsOptimizeDeps, "my-dep"].
46648
46648
  include: loticsOptimizeDeps,
46649
+ // The kit is ONE module graph, so it must never be pre-bundled: the optimizer
46650
+ // auto-discovers each @lotics/ui subpath and inlines a private copy of every
46651
+ // context-owning module it reaches (locale, tooltip, portal, popover), leaving
46652
+ // the provider filling one context and the component reading another.
46653
+ exclude: loticsOptimizeExclude,
46649
46654
  // The dep optimizer pre-bundles deps with a SEPARATE esbuild pass that
46650
46655
  // top-level \`define\` doesn't always reach, so a pre-bundled RN dep can
46651
46656
  // still hit \`__DEV__ is not defined\` under \`lotics app dev\`. Define it
@@ -66481,10 +66486,9 @@ var pathSegmentSchema = zod_default.discriminatedUnion("at", [
66481
66486
  at: zod_default.literal("linked"),
66482
66487
  via: zod_default.string().min(1),
66483
66488
  /**
66484
- * `number` pick a single linked record by literal index.
66485
- * `"each"` fan out; legal only inside foreach.items (validator-enforced).
66486
- * `WorkflowExpression` computed index, resolved at runtime to pick a
66487
- * single linked record (same descent semantics as the numeric form).
66489
+ * Which linked rows the fetch yields. A literal or computed index picks
66490
+ * one row — `linked(P)[i]`, one fetch. `"each"` is the bare `linked(P)`:
66491
+ * every row in order, holding a `null` where one is deleted or unreadable.
66488
66492
  */
66489
66493
  index: zod_default.union([
66490
66494
  zod_default.number().int().nonnegative(),
@@ -68970,6 +68974,8 @@ var RESERVED_ROOTS = /* @__PURE__ */ new Set([
68970
68974
  "trigger",
68971
68975
  "index"
68972
68976
  ]);
68977
+ var LINKED_CALL = "linked";
68978
+ var GRAMMAR_NAME_SHAPE = `${LINKED_CALL}(record["fld_link"])[0] fetches one linked row`;
68973
68979
  var RUNTIME_KEYS = /* @__PURE__ */ new Set([
68974
68980
  "timezone",
68975
68981
  "workflow_id",
@@ -69159,6 +69165,30 @@ var RESERVED_CALLABLE_NAMES = /* @__PURE__ */ new Set([
69159
69165
  "Math",
69160
69166
  "Boolean"
69161
69167
  ]);
69168
+ function assertNotReservedName(node, name2, label, toolNames) {
69169
+ if (RESERVED_ROOTS.has(name2)) {
69170
+ fail(node, `${label} uses the reserved identifier "${name2}".`);
69171
+ }
69172
+ if (name2 === LINKED_CALL) {
69173
+ fail(node, `${label} names grammar: ${GRAMMAR_NAME_SHAPE}.`, "reserved_grammar_name");
69174
+ }
69175
+ if (toolNames?.has(name2)) {
69176
+ fail(node, `${label} collides with the tool "${name2}".`);
69177
+ }
69178
+ if (isHelper(name2)) {
69179
+ fail(node, `${label} collides with the built-in helper "${name2}".`);
69180
+ }
69181
+ if (RESERVED_CALLABLE_NAMES.has(name2)) {
69182
+ fail(node, `${label} collides with "${name2}", which the subset already gives a meaning.`);
69183
+ }
69184
+ }
69185
+ var LINKED_ARITY = `${LINKED_CALL}(...) takes exactly one argument \u2014 the read of the link field, e.g. ${LINKED_CALL}(record["fld_link"])[0] for one row or ${LINKED_CALL}(record["fld_link"]) for every row.`;
69186
+ var LINKED_ARGUMENT_SHAPE = `The argument of ${LINKED_CALL}(...) must END on the link field, because that field is what it follows \u2014 ${LINKED_CALL}(record["fld_link"])[0]. A value that names no field (a binding, a literal, a helper result) cannot say which link to follow; read the field on it: ${LINKED_CALL}(ids["fld_link"]).`;
69187
+ var LINKED_OPTIONAL_RESULT = `${LINKED_CALL}(...) always returns an array, never null, so \`?.\` on its result can never fire. The argument takes a guard \u2014 ${LINKED_CALL}(record?.["fld_link"])[0] \u2014 and so does the fetched row, which is null where the linked record is deleted or unreadable: ${LINKED_CALL}(record["fld_link"])[0]?.["fld_x"].`;
69188
+ var LINKED_METHOD_FORM = `${LINKED_CALL} has no method form \u2014 write ${LINKED_CALL}(record["fld_link"])[0], with the link read as the argument.`;
69189
+ var LINKED_ON_NON_RECORD = `${LINKED_CALL}(...) follows a link field on a record, and this value is not one. Read the record first, then follow the link: ${LINKED_CALL}(record["fld_link"])[0].`;
69190
+ var LINKED_ON_CHANGES = `A change diff is not a record, so ${LINKED_CALL}(...) cannot follow a link off it. Read the post-state with ${LINKED_CALL}(record["fld_link"]) or the pre-state with ${LINKED_CALL}(prev_record["fld_link"]).`;
69191
+ var OPTIONAL_CALL_MESSAGE = "Optional calls (`x?.()`) are not supported. Use `?.` on member access only.";
69162
69192
  var MAX_EXPRESSION_DEPTH = 500;
69163
69193
  function walkExpression(node, scope) {
69164
69194
  const depth = scope.depth + 1;
@@ -69405,11 +69435,10 @@ function walkHelperCall(node, scope) {
69405
69435
  fail(callee, LAMBDA_POSITION_HINT, "lambda_position");
69406
69436
  }
69407
69437
  if (node.type === "OptionalCallExpression" && node.optional === true) {
69408
- fail(
69409
- node,
69410
- "Optional calls (`x?.()`) are not supported. Use `?.` on member access only.",
69411
- "optional_call"
69412
- );
69438
+ fail(node, OPTIONAL_CALL_MESSAGE, "optional_call");
69439
+ }
69440
+ if (callee.type === "Identifier" && callee.name === LINKED_CALL) {
69441
+ return walkRead(node, scope);
69413
69442
  }
69414
69443
  if ((callee.type === "MemberExpression" || callee.type === "OptionalMemberExpression") && callee.object.type === "Identifier" && callee.object.name === "Math" && !callee.computed && callee.property.type === "Identifier") {
69415
69444
  const mathFn = callee.property.name;
@@ -69486,6 +69515,9 @@ function walkMethodForm(node, callee, scope) {
69486
69515
  }
69487
69516
  methodName = callee.property.name;
69488
69517
  }
69518
+ if (methodName === LINKED_CALL) {
69519
+ fail(callee, LINKED_METHOD_FORM, "linked_method_form");
69520
+ }
69489
69521
  const receiver = walkExpression(callee.object, scope);
69490
69522
  const methodOptional = callee.type === "OptionalMemberExpression" && callee.optional === true;
69491
69523
  const liftedGuards = [];
@@ -69612,12 +69644,7 @@ function walkLambda(node, scope) {
69612
69644
  if (p.type !== "Identifier") {
69613
69645
  fail(p, "Lambda parameters must be plain identifiers (no destructuring or defaults).");
69614
69646
  }
69615
- if (RESERVED_ROOTS.has(p.name)) {
69616
- fail(p, `${p.name} is reserved and cannot be a lambda parameter.`);
69617
- }
69618
- if (scope.toolNames.has(p.name) || isHelper(p.name)) {
69619
- fail(p, `Lambda parameter "${p.name}" collides with a tool or helper.`);
69620
- }
69647
+ assertNotReservedName(p, p.name, `Lambda parameter "${p.name}"`, scope.toolNames);
69621
69648
  if (scope.functions.has(p.name)) {
69622
69649
  fail(p, `Lambda parameter "${p.name}" collides with a function declared in this workflow.`);
69623
69650
  }
@@ -69690,7 +69717,7 @@ function collectFunctionDeclarations(body, toolNames) {
69690
69717
  failFunctionDeclaration(stmt, "A function declaration must be named.");
69691
69718
  }
69692
69719
  const name2 = stmt.id.name;
69693
- assertFunctionName(stmt.id, name2, `Function "${name2}"`, toolNames);
69720
+ assertNotReservedName(stmt.id, name2, `Function "${name2}"`, toolNames);
69694
69721
  if (functions.has(name2)) {
69695
69722
  fail(stmt.id, `Function "${name2}" is already declared in this workflow.`);
69696
69723
  }
@@ -69712,7 +69739,7 @@ function collectFunctionDeclarations(body, toolNames) {
69712
69739
  );
69713
69740
  }
69714
69741
  const paramName = p.left.name;
69715
- assertFunctionName(p.left, paramName, `Parameter "${paramName}"`, toolNames);
69742
+ assertNotReservedName(p.left, paramName, `Parameter "${paramName}"`, toolNames);
69716
69743
  if (seen.has(paramName)) {
69717
69744
  fail(p.left, `Parameter "${paramName}" of function "${name2}" is declared twice.`);
69718
69745
  }
@@ -69738,20 +69765,6 @@ function collectFunctionDeclarations(body, toolNames) {
69738
69765
  }
69739
69766
  return functions;
69740
69767
  }
69741
- function assertFunctionName(node, name2, label, toolNames) {
69742
- if (RESERVED_ROOTS.has(name2)) {
69743
- fail(node, `${label} uses the reserved identifier "${name2}".`);
69744
- }
69745
- if (toolNames.has(name2)) {
69746
- fail(node, `${label} collides with the tool "${name2}".`);
69747
- }
69748
- if (isHelper(name2)) {
69749
- fail(node, `${label} collides with the built-in helper "${name2}".`);
69750
- }
69751
- if (RESERVED_CALLABLE_NAMES.has(name2)) {
69752
- fail(node, `${label} collides with "${name2}", which the subset already gives a meaning.`);
69753
- }
69754
- }
69755
69768
  function inlineFunctionCall(node, decl, scope) {
69756
69769
  if (scope.inline.stack.includes(decl.name)) {
69757
69770
  fail(
@@ -69806,6 +69819,7 @@ function functionBodyScope(scope, name2, args) {
69806
69819
  depth: scope.depth
69807
69820
  };
69808
69821
  }
69822
+ var READ_ROOT_HINT = `Reads must start from a reserved identifier (record, prev_record, changes, runtime, trigger, index), a foreach bind name, a step-id, or ${LINKED_CALL}(record["fld_link"]).`;
69809
69823
  function flattenChain(node, scope) {
69810
69824
  let cur = node;
69811
69825
  const reverse2 = [];
@@ -69845,8 +69859,11 @@ function flattenChain(node, scope) {
69845
69859
  }
69846
69860
  cur = cur.object;
69847
69861
  }
69862
+ if (cur.type === "CallExpression" || cur.type === "OptionalCallExpression") {
69863
+ return flattenLinkedCall(cur, reverse2, scope);
69864
+ }
69848
69865
  if (cur.type !== "Identifier") {
69849
- fail(cur, "Reads must start from a reserved identifier (record, prev_record, changes, runtime, trigger, index), a foreach bind name, or a step-id.");
69866
+ fail(cur, READ_ROOT_HINT, "read_root");
69850
69867
  }
69851
69868
  return {
69852
69869
  rootName: cur.name,
@@ -69854,6 +69871,58 @@ function flattenChain(node, scope) {
69854
69871
  segments: reverse2.reverse()
69855
69872
  };
69856
69873
  }
69874
+ function flattenLinkedCall(node, reverse2, scope) {
69875
+ const callee = node.callee;
69876
+ if ((callee.type === "MemberExpression" || callee.type === "OptionalMemberExpression") && (!callee.computed && callee.property.type === "Identifier" && callee.property.name === LINKED_CALL || callee.computed && callee.property.type === "StringLiteral" && callee.property.value === LINKED_CALL)) {
69877
+ fail(callee, LINKED_METHOD_FORM, "linked_method_form");
69878
+ }
69879
+ if (callee.type !== "Identifier" || callee.name !== LINKED_CALL) {
69880
+ fail(node, READ_ROOT_HINT, "read_root");
69881
+ }
69882
+ if (node.type === "OptionalCallExpression" && node.optional === true) {
69883
+ fail(node, OPTIONAL_CALL_MESSAGE, "optional_call");
69884
+ }
69885
+ if (node.arguments.length !== 1) {
69886
+ fail(node, LINKED_ARITY, "linked_arity");
69887
+ }
69888
+ const arg = node.arguments[0];
69889
+ if (arg.type !== "MemberExpression" && arg.type !== "OptionalMemberExpression") {
69890
+ fail(arg, LINKED_ARGUMENT_SHAPE, "linked_argument_shape");
69891
+ }
69892
+ const onResult = reverse2[reverse2.length - 1];
69893
+ if (onResult?.optional === true) {
69894
+ fail(onResult.node, LINKED_OPTIONAL_RESULT, "linked_optional_result");
69895
+ }
69896
+ const folded = onResult?.kind === "bracket-number" ? onResult.index : onResult?.kind === "bracket-expr" ? onResult.expr : void 0;
69897
+ const linkedIndex = folded ?? "each";
69898
+ const afterCall = folded === void 0 ? reverse2 : reverse2.slice(0, -1);
69899
+ const argScope = { ...scope, depth: scope.depth + 1 };
69900
+ if (argScope.depth > MAX_EXPRESSION_DEPTH) {
69901
+ fail(node, `Expression nesting is too deep (max ${MAX_EXPRESSION_DEPTH} levels).`, "depth_limit");
69902
+ }
69903
+ const inner = flattenChain(arg, argScope);
69904
+ const via = inner.segments[inner.segments.length - 1];
69905
+ if (!via || via.kind !== "dot" && via.kind !== "bracket-string") {
69906
+ fail(arg, LINKED_ARGUMENT_SHAPE, "linked_argument_shape");
69907
+ }
69908
+ return {
69909
+ rootName: inner.rootName,
69910
+ rootNode: inner.rootNode,
69911
+ segments: [
69912
+ ...inner.segments.slice(0, -1),
69913
+ // `linked(record?.["fld_link"])` guards the RECEIVER of the link read, so
69914
+ // the guard belongs on the segment the call became.
69915
+ {
69916
+ kind: "linked",
69917
+ via: via.key,
69918
+ index: linkedIndex,
69919
+ optional: via.optional === true,
69920
+ node: arg
69921
+ },
69922
+ ...[...afterCall].reverse()
69923
+ ]
69924
+ };
69925
+ }
69857
69926
  function wrapOptionalGuards(chain2, buildRead) {
69858
69927
  const firstOpt = chain2.segments.findIndex((s) => s.optional);
69859
69928
  if (firstOpt === -1) return buildRead(chain2.segments);
@@ -69916,7 +69985,7 @@ function walkRead(node, scope) {
69916
69985
  const base = inlined;
69917
69986
  return wrapOptionalGuards(chain2, (segments) => ({
69918
69987
  ...base,
69919
- path: [...base.path, ...classifyRecordPath(segments)]
69988
+ path: [...base.path, ...pathFor(base.source, segments)]
69920
69989
  }));
69921
69990
  }
69922
69991
  if (root === "record" || root === "prev_record") {
@@ -69983,19 +70052,20 @@ function walkRead(node, scope) {
69983
70052
  );
69984
70053
  }
69985
70054
  function readFromRecord(from, chain2) {
70055
+ const root = from === "trigger_record" ? "record" : "prev_record";
69986
70056
  return wrapOptionalGuards(chain2, (segments) => {
69987
70057
  if (segments.length === 0) {
69988
70058
  return { kind: "read", source: { from }, path: [] };
69989
70059
  }
69990
70060
  const first3 = segments[0];
69991
70061
  if (first3.kind === "bracket-number" || first3.kind === "bracket-expr") {
69992
- fail(first3.node, "Field access uses string keys. For linked records use record.LinkedField[0].");
70062
+ fail(
70063
+ first3.node,
70064
+ `A record is keyed by field, not by index \u2014 ${root}["fld_\u2026"]. To fetch a linked row write ${LINKED_CALL}(${root}["fld_link"])[0].`
70065
+ );
69993
70066
  }
69994
- return {
69995
- kind: "read",
69996
- source: { from },
69997
- path: classifyRecordPath(segments)
69998
- };
70067
+ const source = { from };
70068
+ return { kind: "read", source, path: pathFor(source, segments) };
69999
70069
  });
70000
70070
  }
70001
70071
  function readFromChanges(chain2) {
@@ -70007,17 +70077,8 @@ function readFromChanges(chain2) {
70007
70077
  if (first3.kind === "bracket-number" || first3.kind === "bracket-expr") {
70008
70078
  fail(first3.node, "changes is keyed by field name, not numeric index.");
70009
70079
  }
70010
- const path13 = [{ at: "key", key: first3.key }];
70011
- for (const seg of segments.slice(1)) {
70012
- if (seg.kind === "bracket-number") {
70013
- path13.push({ at: "index", index: seg.index });
70014
- } else if (seg.kind === "bracket-expr") {
70015
- path13.push({ at: "index", index: seg.expr });
70016
- } else {
70017
- path13.push({ at: "key", key: seg.key });
70018
- }
70019
- }
70020
- return { kind: "read", source: { from: "trigger_changes" }, path: path13 };
70080
+ const source = { from: "trigger_changes" };
70081
+ return { kind: "read", source, path: pathFor(source, segments) };
70021
70082
  });
70022
70083
  }
70023
70084
  var TRIGGER_RECORD_VIEWS = /* @__PURE__ */ new Map([
@@ -70035,17 +70096,8 @@ function readFromTrigger(chain2) {
70035
70096
  return view === "trigger_changes" ? readFromChanges(lowered) : readFromRecord(view, lowered);
70036
70097
  }
70037
70098
  return wrapOptionalGuards(chain2, (segments) => {
70038
- const path13 = [];
70039
- for (const seg of segments) {
70040
- if (seg.kind === "bracket-number") {
70041
- path13.push({ at: "index", index: seg.index });
70042
- } else if (seg.kind === "bracket-expr") {
70043
- path13.push({ at: "index", index: seg.expr });
70044
- } else {
70045
- path13.push({ at: "key", key: seg.key });
70046
- }
70047
- }
70048
- return { kind: "read", source: { from: "trigger" }, path: path13 };
70099
+ const source = { from: "trigger" };
70100
+ return { kind: "read", source, path: pathFor(source, segments) };
70049
70101
  });
70050
70102
  }
70051
70103
  function readFromRuntime(chain2) {
@@ -70057,112 +70109,79 @@ function readFromRuntime(chain2) {
70057
70109
  fail(chain2.rootNode, "runtime needs a key (e.g., runtime.timezone).");
70058
70110
  }
70059
70111
  const first3 = segments[0];
70112
+ if (first3.kind === "linked") {
70113
+ fail(first3.node, LINKED_ON_NON_RECORD, "linked_on_non_record");
70114
+ }
70060
70115
  if (first3.kind !== "dot" && first3.kind !== "bracket-string") {
70061
70116
  fail(first3.node, "runtime is keyed by name (runtime.timezone, runtime.now, ...).");
70062
70117
  }
70063
- const key = first3.key ?? "";
70118
+ const key = first3.key;
70064
70119
  if (!isRuntimeKey(key)) {
70065
70120
  fail(first3.node, `Unknown runtime key "${key}". Allowed: ${READABLE_RUNTIME_KEYS.join(", ")}.`);
70066
70121
  }
70067
70122
  const parked = PARKED_RUNTIME_KEYS.get(key);
70068
70123
  if (parked) fail(first3.node, parked, parkedRuntimeKeyConstruct(key));
70069
70124
  const source = { from: "runtime", key };
70070
- const path13 = [];
70071
- for (const seg of segments.slice(1)) {
70072
- if (seg.kind === "bracket-number") {
70073
- path13.push({ at: "index", index: seg.index });
70074
- } else if (seg.kind === "bracket-expr") {
70075
- path13.push({ at: "index", index: seg.expr });
70076
- } else {
70077
- path13.push({ at: "key", key: seg.key });
70078
- }
70079
- }
70080
- return { kind: "read", source, path: path13 };
70125
+ return { kind: "read", source, path: pathFor(source, segments.slice(1)) };
70081
70126
  });
70082
70127
  }
70083
70128
  function readFromForeachItem(name2, chain2) {
70084
- return wrapOptionalGuards(chain2, (segments) => ({
70085
- kind: "read",
70086
- // NAMED, so a read inside a nested loop says which loop it means. Without
70087
- // it both binds resolved to the innermost item, and a join between two
70088
- // collections matched nothing while the workflow reported success.
70089
- source: { from: "foreach_item", name: name2 },
70090
- path: classifyRecordPath(segments)
70091
- }));
70129
+ return wrapOptionalGuards(chain2, (segments) => {
70130
+ const source = { from: "foreach_item", name: name2 };
70131
+ return { kind: "read", source, path: pathFor(source, segments) };
70132
+ });
70092
70133
  }
70093
70134
  function readFromLambdaParam(name2, chain2) {
70094
- return wrapOptionalGuards(chain2, (segments) => ({
70095
- kind: "read",
70096
- source: { from: "lambda_param", name: name2 },
70097
- // Lambda params commonly hold records iterated from an array (`filter`,
70098
- // `find`, `sortBy`), so use the same path classifier as foreach_item:
70099
- // `x.Field` is a key/field read, `x.LinkedField[0]` collapses to a
70100
- // `linked` segment. The resolver promotes `at: "key"` → `at: "field"`
70101
- // when it can infer the param's table from the enclosing helper call.
70102
- path: classifyRecordPath(segments)
70103
- }));
70135
+ return wrapOptionalGuards(chain2, (segments) => {
70136
+ const source = { from: "lambda_param", name: name2 };
70137
+ return { kind: "read", source, path: pathFor(source, segments) };
70138
+ });
70104
70139
  }
70105
70140
  function readFromLexical(name2, chain2) {
70106
70141
  return wrapOptionalGuards(chain2, (segments) => {
70107
- if (segments.length === 0) {
70108
- return { kind: "read", source: { from: "lexical", name: name2 }, path: [] };
70109
- }
70110
- return {
70111
- kind: "read",
70112
- source: { from: "lexical", name: name2 },
70113
- path: classifyRecordPath(segments)
70114
- };
70142
+ const source = { from: "lexical", name: name2 };
70143
+ return { kind: "read", source, path: pathFor(source, segments) };
70115
70144
  });
70116
70145
  }
70117
70146
  function readFromStepOutput(stepId, chain2) {
70118
70147
  return wrapOptionalGuards(chain2, (segments) => {
70119
- const path13 = [];
70120
- for (const seg of segments) {
70121
- if (seg.kind === "bracket-number") {
70122
- path13.push({ at: "index", index: seg.index });
70123
- } else if (seg.kind === "bracket-expr") {
70124
- path13.push({ at: "index", index: seg.expr });
70125
- } else {
70126
- path13.push({ at: "key", key: seg.key });
70127
- }
70128
- }
70129
- return { kind: "read", source: { from: "step_output", step_id: stepId }, path: path13 };
70148
+ const source = { from: "step_output", step_id: stepId };
70149
+ return { kind: "read", source, path: pathFor(source, segments) };
70130
70150
  });
70131
70151
  }
70132
- function classifyRecordPath(segs) {
70133
- const out = [];
70134
- let i2 = 0;
70135
- while (i2 < segs.length) {
70136
- const seg = segs[i2];
70137
- if (seg.kind === "bracket-number") {
70138
- out.push({ at: "index", index: seg.index });
70139
- i2 += 1;
70140
- continue;
70141
- }
70142
- if (seg.kind === "bracket-expr") {
70143
- out.push({ at: "index", index: seg.expr });
70144
- i2 += 1;
70145
- continue;
70146
- }
70147
- const name2 = seg.key;
70148
- const next = segs[i2 + 1];
70149
- if (next && next.kind === "bracket-number") {
70150
- out.push({ at: "linked", via: name2, index: next.index });
70151
- i2 += 2;
70152
- continue;
70153
- }
70154
- if (next && next.kind === "bracket-expr") {
70155
- out.push({ at: "linked", via: name2, index: next.expr });
70156
- i2 += 2;
70157
- continue;
70152
+ var LINKED_REFUSAL = {
70153
+ trigger_record: null,
70154
+ trigger_record_prev: null,
70155
+ foreach_item: null,
70156
+ lambda_param: null,
70157
+ lexical: null,
70158
+ step_output: null,
70159
+ trigger: LINKED_ON_NON_RECORD,
70160
+ trigger_changes: LINKED_ON_CHANGES,
70161
+ runtime: LINKED_ON_NON_RECORD,
70162
+ foreach_index: LINKED_ON_NON_RECORD
70163
+ };
70164
+ function pathFor(source, segs) {
70165
+ const refusal = LINKED_REFUSAL[source.from];
70166
+ return segs.map((seg) => {
70167
+ switch (seg.kind) {
70168
+ case "bracket-number":
70169
+ return { at: "index", index: seg.index };
70170
+ case "bracket-expr":
70171
+ return { at: "index", index: seg.expr };
70172
+ case "linked":
70173
+ if (refusal !== null) fail(seg.node, refusal, "linked_on_non_record");
70174
+ return { at: "linked", via: seg.via, index: seg.index };
70175
+ case "dot":
70176
+ case "bracket-string":
70177
+ return { at: "key", key: seg.key };
70158
70178
  }
70159
- out.push({ at: "key", key: name2 });
70160
- i2 += 1;
70161
- }
70162
- return out;
70179
+ });
70163
70180
  }
70164
70181
 
70165
70182
  // ../shared/src/parse_workflow_js.ts
70183
+ var LINKED_STATEMENT = `${LINKED_CALL}(...) reads linked rows; it is not a step. Iterate them \u2014 for (const r of ${LINKED_CALL}(record["fld_link"])) { ... } \u2014 or index one: ${LINKED_CALL}(record["fld_link"])[0]["fld_x"].`;
70184
+ var LINKED_AWAIT = `${LINKED_CALL}(...) is grammar, not a tool, and takes no await \u2014 it reads linked rows in place. Write ${LINKED_CALL}(record["fld_link"])[0]["fld_x"], or iterate them: for (const r of ${LINKED_CALL}(record["fld_link"])) { ... }.`;
70166
70185
  var MAX_SOURCE_LENGTH = 512 * 1024;
70167
70186
  function checkSourceLength(source) {
70168
70187
  if (source.length > MAX_SOURCE_LENGTH) {
@@ -70301,6 +70320,7 @@ function walkStatement(stmt, scope, stepIds, opts, nextSyntheticId, depth) {
70301
70320
  function walkStatementInner(stmt, scope, stepIds, opts, nextSyntheticId, depth) {
70302
70321
  const { id: explicitId, description } = readLeadingComments(stmt);
70303
70322
  if (explicitId !== void 0) {
70323
+ assertNotReservedName(stmt, explicitId, `Step id "${explicitId}"`, opts.toolNames);
70304
70324
  assertNoFunctionCollision(stmt, explicitId, scope, "Step id");
70305
70325
  }
70306
70326
  switch (stmt.type) {
@@ -70422,12 +70442,7 @@ function walkConstDeclarator(decl, scope, stepIds, opts, explicitId, description
70422
70442
  fail(decl.id, "Bindings must use an identifier or `{ ... }` / `[ ... ]` destructuring.");
70423
70443
  }
70424
70444
  const id = decl.id.name;
70425
- if (RESERVED_ROOTS.has(id)) {
70426
- fail(decl.id, `${id} is reserved.`);
70427
- }
70428
- if (opts.toolNames && opts.toolNames.has(id) || isHelper(id)) {
70429
- fail(decl.id, `Cannot bind to "${id}" \u2014 name collides with a tool or helper.`);
70430
- }
70445
+ assertNotReservedName(decl.id, id, `Binding "${id}"`, opts.toolNames);
70431
70446
  assertNoFunctionCollision(decl.id, id, scope, "Binding");
70432
70447
  if (stepIds.has(id)) {
70433
70448
  fail(decl.id, `Step id "${id}" is already used.`);
@@ -70599,12 +70614,7 @@ function walkPatternBinding(decl, scope, stepIds, opts, explicitId, description,
70599
70614
  makeBase = (segment) => ({ ...rhs, path: [...rhs.path, segment] });
70600
70615
  }
70601
70616
  for (const entry of walkPatternEntries(pattern, scope)) {
70602
- if (RESERVED_ROOTS.has(entry.name)) {
70603
- fail(entry.node, `${entry.name} is reserved.`);
70604
- }
70605
- if (opts.toolNames && opts.toolNames.has(entry.name) || isHelper(entry.name)) {
70606
- fail(entry.node, `Cannot bind to "${entry.name}" \u2014 name collides with a tool or helper.`);
70607
- }
70617
+ assertNotReservedName(entry.node, entry.name, `Binding "${entry.name}"`, opts.toolNames);
70608
70618
  assertNoFunctionCollision(entry.node, entry.name, scope, "Binding");
70609
70619
  if (stepIds.has(entry.name)) {
70610
70620
  fail(entry.node, `Step id "${entry.name}" is already used.`);
@@ -70643,12 +70653,7 @@ function walkLetDeclaration(stmt, scope, stepIds, opts, explicitId, description)
70643
70653
  const frame = scope.lexicalBindings[scope.lexicalBindings.length - 1];
70644
70654
  for (const entry of walkPatternEntries(decl.id, scope)) {
70645
70655
  const name3 = entry.name;
70646
- if (RESERVED_ROOTS.has(name3)) {
70647
- fail(entry.node, `${name3} is reserved and cannot be a let binding name.`);
70648
- }
70649
- if (opts.toolNames && opts.toolNames.has(name3) || isHelper(name3)) {
70650
- fail(entry.node, `let binding "${name3}" collides with a tool or helper.`);
70651
- }
70656
+ assertNotReservedName(entry.node, name3, `let binding "${name3}"`, opts.toolNames);
70652
70657
  assertNoFunctionCollision(entry.node, name3, scope, "let binding");
70653
70658
  if (stepIds.has(name3)) {
70654
70659
  fail(entry.node, `let binding "${name3}" collides with a step id.`);
@@ -70674,12 +70679,7 @@ function walkLetDeclaration(stmt, scope, stepIds, opts, explicitId, description)
70674
70679
  fail(decl.id, "let bindings require a plain identifier name.");
70675
70680
  }
70676
70681
  const name2 = decl.id.name;
70677
- if (RESERVED_ROOTS.has(name2)) {
70678
- fail(decl.id, `${name2} is reserved and cannot be a let binding name.`);
70679
- }
70680
- if (opts.toolNames && opts.toolNames.has(name2) || isHelper(name2)) {
70681
- fail(decl.id, `let binding "${name2}" collides with a tool or helper.`);
70682
- }
70682
+ assertNotReservedName(decl.id, name2, `let binding "${name2}"`, opts.toolNames);
70683
70683
  assertNoFunctionCollision(decl.id, name2, scope, "let binding");
70684
70684
  if (stepIds.has(name2)) {
70685
70685
  fail(decl.id, `let binding "${name2}" collides with a step id.`);
@@ -70726,24 +70726,21 @@ function walkExpressionStatement(stmt, scope, stepIds, opts, stepId, description
70726
70726
  stepIds.add(stepId);
70727
70727
  return walkAgentInvocation(inner, scope, stepId, description);
70728
70728
  }
70729
- if (!opts.toolNames || opts.toolNames.has(name2)) {
70730
- const { tool_name, input } = walkToolInvocation(
70731
- inner,
70732
- scope,
70733
- opts,
70734
- /*allowWaits*/
70735
- true
70736
- );
70737
- stepIds.add(stepId);
70738
- return {
70739
- id: stepId,
70740
- description: description ?? tool_name,
70741
- type: "tool_call",
70742
- tool_name,
70743
- input
70744
- };
70745
- }
70746
- fail(callee, unknownToolMessage(name2, opts.toolNames) + " Tools must be registered.");
70729
+ const { tool_name, input } = walkToolInvocation(
70730
+ inner,
70731
+ scope,
70732
+ opts,
70733
+ /*allowWaits*/
70734
+ true
70735
+ );
70736
+ stepIds.add(stepId);
70737
+ return {
70738
+ id: stepId,
70739
+ description: description ?? tool_name,
70740
+ type: "tool_call",
70741
+ tool_name,
70742
+ input
70743
+ };
70747
70744
  }
70748
70745
  if (e.type === "CallExpression" && e.callee.type === "MemberExpression" && !e.callee.computed && e.callee.property.type === "Identifier" && e.callee.property.name === "push") {
70749
70746
  return walkPushStatement(e, scope, stepId, description);
@@ -70767,6 +70764,9 @@ function walkExpressionStatement(stmt, scope, stepIds, opts, stepId, description
70767
70764
  if (callee.type === "Identifier" && WAIT_TOOLS.has(callee.name)) {
70768
70765
  fail(e, `${callee.name} must be awaited: write "await ${callee.name}({...})".`);
70769
70766
  }
70767
+ if (callee.type === "Identifier" && callee.name === LINKED_CALL) {
70768
+ fail(e, LINKED_STATEMENT, "linked_statement");
70769
+ }
70770
70770
  if (callee.type === "Identifier" && isHelper(callee.name)) {
70771
70771
  fail(e, `Helpers may only appear inside expressions, not as standalone statements.`);
70772
70772
  }
@@ -70789,9 +70789,7 @@ function walkAssignmentStatement(e, scope, stepId, description) {
70789
70789
  fail(e.left, "Assignment targets must be a plain identifier referring to a `let` binding.");
70790
70790
  }
70791
70791
  const name2 = e.left.name;
70792
- if (RESERVED_ROOTS.has(name2)) {
70793
- fail(e.left, `${name2} is reserved and cannot be reassigned.`);
70794
- }
70792
+ assertNotReservedName(e.left, name2, `Assignment target "${name2}"`, scope.toolNames);
70795
70793
  if (!findLexicalBinding(scope, name2)) {
70796
70794
  fail(e.left, `Cannot assign to "${name2}" \u2014 no enclosing \`let\` declaration in scope.`);
70797
70795
  }
@@ -70824,9 +70822,7 @@ function walkUpdateStatement(e, scope, stepId, description) {
70824
70822
  fail(e.argument, "Update operators (++/--) require a plain identifier referring to a `let` binding.");
70825
70823
  }
70826
70824
  const name2 = e.argument.name;
70827
- if (RESERVED_ROOTS.has(name2)) {
70828
- fail(e.argument, `${name2} is reserved.`);
70829
- }
70825
+ assertNotReservedName(e.argument, name2, `Update target "${name2}"`, scope.toolNames);
70830
70826
  if (!findLexicalBinding(scope, name2)) {
70831
70827
  fail(e.argument, `Cannot update "${name2}" \u2014 no enclosing \`let\` declaration in scope.`);
70832
70828
  }
@@ -71002,17 +70998,12 @@ function walkForOf(stmt, scope, stepIds, opts, stepId, description, nextSyntheti
71002
70998
  } else {
71003
70999
  fail(decl.id, "for-of must bind an identifier or a `{ ... }` / `[ ... ]` pattern.");
71004
71000
  }
71005
- if (RESERVED_ROOTS.has(bindName) && bindName !== "item") {
71006
- fail(decl.id, `${bindName} is reserved and cannot be a foreach bind name.`);
71007
- }
71001
+ assertNotReservedName(decl.id, bindName, `Foreach bind name "${bindName}"`, opts.toolNames);
71008
71002
  if (stepIds.has(bindName)) {
71009
71003
  fail(decl.id, `Foreach bind name "${bindName}" collides with a step id.`);
71010
71004
  }
71011
- if (opts.toolNames && opts.toolNames.has(bindName) || isHelper(bindName)) {
71012
- fail(decl.id, `Foreach bind name "${bindName}" collides with a tool or helper.`);
71013
- }
71014
71005
  assertNoFunctionCollision(decl.id, bindName, scope, "Foreach bind name");
71015
- const items = walkForOfRight(stmt.right, scope);
71006
+ const items = walkExpression(stmt.right, scope);
71016
71007
  const innerScope = {
71017
71008
  ...scope,
71018
71009
  foreachBinds: [...scope.foreachBinds, bindName],
@@ -71025,12 +71016,7 @@ function walkForOf(stmt, scope, stepIds, opts, stepId, description, nextSyntheti
71025
71016
  const innerSteps = [];
71026
71017
  if (patternEntries) {
71027
71018
  for (const entry of patternEntries) {
71028
- if (RESERVED_ROOTS.has(entry.name)) {
71029
- fail(entry.node, `${entry.name} is reserved.`);
71030
- }
71031
- if (opts.toolNames && opts.toolNames.has(entry.name) || isHelper(entry.name)) {
71032
- fail(entry.node, `Cannot bind to "${entry.name}" \u2014 name collides with a tool or helper.`);
71033
- }
71019
+ assertNotReservedName(entry.node, entry.name, `Binding "${entry.name}"`, opts.toolNames);
71034
71020
  assertNoFunctionCollision(entry.node, entry.name, scope, "Binding");
71035
71021
  if (stepIds.has(entry.name)) {
71036
71022
  fail(entry.node, `Step id "${entry.name}" is already used.`);
@@ -71067,9 +71053,6 @@ function walkForOf(stmt, scope, stepIds, opts, stepId, description, nextSyntheti
71067
71053
  steps: innerSteps
71068
71054
  };
71069
71055
  }
71070
- function walkForOfRight(node, scope) {
71071
- return walkExpression(node, scope);
71072
- }
71073
71056
  function walkWhile(stmt, scope, stepIds, opts, stepId, description, nextSyntheticId, depth) {
71074
71057
  if (stmt.body.type !== "BlockStatement") {
71075
71058
  fail(stmt.body, "while body must be a { ... } block.");
@@ -71212,12 +71195,7 @@ function walkTry(stmt, scope, stepIds, opts, stepId, description, nextSyntheticI
71212
71195
  fail(handler.param, "Catch parameter must be a plain identifier.");
71213
71196
  }
71214
71197
  const errorBinding = handler.param.name;
71215
- if (RESERVED_ROOTS.has(errorBinding)) {
71216
- fail(handler.param, `${errorBinding} is reserved and cannot be a catch binding.`);
71217
- }
71218
- if (opts.toolNames && opts.toolNames.has(errorBinding) || isHelper(errorBinding)) {
71219
- fail(handler.param, `Catch binding "${errorBinding}" collides with a tool or helper.`);
71220
- }
71198
+ assertNotReservedName(handler.param, errorBinding, `Catch binding "${errorBinding}"`, opts.toolNames);
71221
71199
  assertNoFunctionCollision(handler.param, errorBinding, scope, "Catch binding");
71222
71200
  if (stepIds.has(errorBinding)) {
71223
71201
  fail(handler.param, `Catch binding "${errorBinding}" collides with an existing step id.`);
@@ -71407,6 +71385,9 @@ function walkToolInvocation(call, scope, opts, allowWaits) {
71407
71385
  if (allowWaits && WAIT_TOOLS.has(name2)) {
71408
71386
  fail(call, `${name2} is a wait step, not a tool. Use "await ${name2}({...})" as a standalone statement.`);
71409
71387
  }
71388
+ if (name2 === LINKED_CALL) {
71389
+ fail(call, LINKED_AWAIT, "linked_statement");
71390
+ }
71410
71391
  if (opts.toolNames && !opts.toolNames.has(name2)) {
71411
71392
  fail(callee, unknownToolMessage(name2, opts.toolNames));
71412
71393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.187.1",
3
+ "version": "0.188.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {