@stll/template-conditions 0.6.2 → 0.6.4

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/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
+ import { resolvePath } from "./path.js";
1
2
  import { CompareNode, CompareOp, Condition, ConditionNode, ConditionNode as ConditionNode$1, GroupNode, Operand, PredicateNode, PredicateOp, conditionHasFormula, conditionNodeSchema, conditionSchema, emptyCondition } from "@stll/conditions";
2
- //#region src/path.d.ts
3
- /** Resolve a dotted path like `company.name` against data. */
4
- export declare const resolvePath: (path: string, data: Record<string, unknown>) => unknown;
5
- //#endregion
6
3
  //#region src/parse.d.ts
4
+ /** Maximum combined nesting of parentheses and unary negation. */
5
+ export declare const MAX_CONDITION_NESTING = 256;
7
6
  /** Bare words the tokenizer reads as operators or literals; a data path can
8
7
  * never be one of them. */
9
8
  export declare const CONDITION_RESERVED_WORDS: readonly ["and", "or", "not", "in", "is", "defined", "true", "false"];
@@ -349,7 +348,8 @@ type InvalidMarker = {
349
348
  */
350
349
  export declare const scanInvalidMarkers: (text: string) => InvalidMarker[];
351
350
  /** The loop alias the codemod (and the legacy-marker message) generates for a
352
- * `{{#each path}}`: the singular of the last plain-word segment, else `item`. */
351
+ * `{{#each path}}`: the singular of the last plain-word segment, else `item`.
352
+ * A fresh `{% for %}` inserted by the Studio names its item by the same rule. */
353
353
  export declare const legacyLoopAlias: (path: string) => string;
354
354
  /** Translate an old-dialect condition expression to the Jinja surface:
355
355
  * `!a` becomes `not a`, `a contains "x"` becomes `"x" in a`. */
@@ -621,4 +621,4 @@ export declare const MAX_CONDITION_DEPTH = 50;
621
621
  */
622
622
  export declare const evaluateCondition: (expression: string, data: Record<string, unknown>, namedConditions?: readonly NamedCondition[], _resolved?: ReadonlySet<string>, _depth?: number) => boolean;
623
623
  //#endregion
624
- export { type BlockDirectiveKind, type CompareNode, type CompareOp, type Condition, type ConditionNode, type ConditionTagOptions, type DateFormatStyle, type DeterministicFieldConfig, type DirectiveKind, type FieldDateFormat, type FilterArgument, type FilterCall, type FilterName, type ForOpenerOptions, type GroupNode, type InvalidMarker, type LoopProperty, type MarkerDefect, type MarkerDefectKind, type MarkerFieldConfig, type MarkerFieldInputType, type MarkerFieldLookup, type MarkerFieldSource, type MarkerFieldValidation, type MarkerForm, type MarkerLiteral, type MarkerMeta, type MarkerPrefix, type NumericFunctionName, type Operand, type PredicateNode, type PredicateOp, type RowBlockMarker, type RowBlockPair, type RowScope, type ScannedMarker, type UnwritableReason, conditionHasFormula, conditionNodeSchema, conditionSchema, emptyCondition };
624
+ export { type BlockDirectiveKind, type CompareNode, type CompareOp, type Condition, type ConditionNode, type ConditionTagOptions, type DateFormatStyle, type DeterministicFieldConfig, type DirectiveKind, type FieldDateFormat, type FilterArgument, type FilterCall, type FilterName, type ForOpenerOptions, type GroupNode, type InvalidMarker, type LoopProperty, type MarkerDefect, type MarkerDefectKind, type MarkerFieldConfig, type MarkerFieldInputType, type MarkerFieldLookup, type MarkerFieldSource, type MarkerFieldValidation, type MarkerForm, type MarkerLiteral, type MarkerMeta, type MarkerPrefix, type NumericFunctionName, type Operand, type PredicateNode, type PredicateOp, type RowBlockMarker, type RowBlockPair, type RowScope, type ScannedMarker, type UnwritableReason, conditionHasFormula, conditionNodeSchema, conditionSchema, emptyCondition, resolvePath };
package/dist/index.js CHANGED
@@ -1,33 +1,7 @@
1
+ import { resolvePath } from "./path.js";
1
2
  import { conditionHasFormula, conditionNodeSchema, conditionSchema, emptyCondition, evaluateCondition as evaluateCondition$1 } from "@stll/conditions";
2
3
  import { Result, panic } from "better-result";
3
4
  import { Temporal } from "temporal-polyfill/full";
4
- //#region src/path.ts
5
- /**
6
- * Dotted-path resolution against a data record. Extracted from index.ts so both
7
- * the condition engine (index.ts) and the arithmetic evaluator (compute.ts) can
8
- * import it without a dependency cycle.
9
- */
10
- /**
11
- * Narrow `unknown` to something a path segment can be read from.
12
- *
13
- * Unlike the same-named predicates that screen JSON payload shapes, this one
14
- * accepts arrays on purpose: a dotted path segment may be an array index
15
- * (`parties.1.name`), and rejecting arrays would silently resolve those to
16
- * `undefined`.
17
- */
18
- const isNonNullObject = (v) => typeof v === "object" && v !== null;
19
- /** Resolve a dotted path like `company.name` against data. */
20
- const resolvePath = (path, data) => {
21
- if (Object.hasOwn(data, path)) return data[path];
22
- const parts = path.split(".");
23
- let current = data;
24
- for (const part of parts) {
25
- if (!isNonNullObject(current)) return;
26
- current = current[part];
27
- }
28
- return current;
29
- };
30
- //#endregion
31
5
  //#region src/compute.ts
32
6
  /**
33
7
  * Arithmetic evaluator for computed template fields.
@@ -191,6 +165,8 @@ const evaluateNumericExpression = (expression, data) => {
191
165
  };
192
166
  //#endregion
193
167
  //#region src/parse.ts
168
+ /** Maximum combined nesting of parentheses and unary negation. */
169
+ const MAX_CONDITION_NESTING = 256;
194
170
  const COMPARE_SYMBOL_TO_OP = {
195
171
  "==": "eq",
196
172
  "!=": "neq",
@@ -325,13 +301,16 @@ const parseCondition = (expression) => {
325
301
  if (tokens.length === 0) return null;
326
302
  let pos = 0;
327
303
  const peek = () => tokens[pos];
328
- const parseOr = () => {
329
- const first = parseAnd();
304
+ const parseOr = (depth) => {
305
+ const first = parseAnd(depth);
306
+ if (first === null) return null;
330
307
  if (peek()?.type !== "or") return first;
331
308
  const children = [first];
332
309
  while (peek()?.type === "or") {
333
310
  pos += 1;
334
- children.push(parseAnd());
311
+ const child = parseAnd(depth);
312
+ if (child === null) return null;
313
+ children.push(child);
335
314
  }
336
315
  return {
337
316
  type: "group",
@@ -339,13 +318,16 @@ const parseCondition = (expression) => {
339
318
  children
340
319
  };
341
320
  };
342
- const parseAnd = () => {
343
- const first = parseNot();
321
+ const parseAnd = (depth) => {
322
+ const first = parseNot(depth);
323
+ if (first === null) return null;
344
324
  if (peek()?.type !== "and") return first;
345
325
  const children = [first];
346
326
  while (peek()?.type === "and") {
347
327
  pos += 1;
348
- children.push(parseNot());
328
+ const child = parseNot(depth);
329
+ if (child === null) return null;
330
+ children.push(child);
349
331
  }
350
332
  return {
351
333
  type: "group",
@@ -353,22 +335,27 @@ const parseCondition = (expression) => {
353
335
  children
354
336
  };
355
337
  };
356
- const parseNot = () => {
338
+ const parseNot = (depth) => {
357
339
  if (peek()?.type === "not") {
340
+ if (depth >= 256) return null;
358
341
  pos += 1;
342
+ const child = parseNot(depth + 1);
343
+ if (child === null) return null;
359
344
  return {
360
345
  type: "group",
361
346
  combinator: "and",
362
347
  negated: true,
363
- children: [parseNot()]
348
+ children: [child]
364
349
  };
365
350
  }
366
- return parseComparison();
351
+ return parseComparison(depth);
367
352
  };
368
- const parseComparison = () => {
353
+ const parseComparison = (depth) => {
369
354
  if (peek()?.type === "lparen") {
355
+ if (depth >= 256) return null;
370
356
  pos += 1;
371
- const inner = parseOr();
357
+ const inner = parseOr(depth + 1);
358
+ if (inner === null) return null;
372
359
  if (peek()?.type === "rparen") pos += 1;
373
360
  return inner;
374
361
  }
@@ -437,7 +424,7 @@ const parseCondition = (expression) => {
437
424
  right: operandFromToken(rightTok)
438
425
  };
439
426
  };
440
- return parseOr();
427
+ return parseOr(0);
441
428
  };
442
429
  //#endregion
443
430
  //#region src/referenced-paths.ts
@@ -1183,7 +1170,8 @@ const singularize = (segment) => {
1183
1170
  return segment;
1184
1171
  };
1185
1172
  /** The loop alias the codemod (and the legacy-marker message) generates for a
1186
- * `{{#each path}}`: the singular of the last plain-word segment, else `item`. */
1173
+ * `{{#each path}}`: the singular of the last plain-word segment, else `item`.
1174
+ * A fresh `{% for %}` inserted by the Studio names its item by the same rule. */
1187
1175
  const legacyLoopAlias = (path) => {
1188
1176
  const segment = path.split(".").at(-1) ?? "";
1189
1177
  if (!IDENTIFIER_RE.test(segment)) return "item";
@@ -1713,4 +1701,4 @@ const evaluateCondition = (expression, data, namedConditions, _resolved = /* @__
1713
1701
  return evaluateCondition$1(node, makeResolver(data, namedConditions, _resolved, _depth));
1714
1702
  };
1715
1703
  //#endregion
1716
- export { ARRAY_FILTER_NAMES, BLOCK_DIRECTIVE_KINDS, CONDITION_RESERVED_WORDS, DATE_FORMAT_EXAMPLE_ISO, DATE_FORMAT_STYLES, DIRECTIVE_KINDS, FILTER_NAMES, LOOP_PROPERTIES, MARKER_DEFECT_KINDS, MARKER_OUTPUT_BODY, MARKER_STATEMENT_BODY, MAX_CONDITION_DEPTH, NUMERIC_FUNCTION_NAMES, arrayFiltersFromFieldConfig, assertNever, blockDirectiveLinePattern, classifyMarker, classifyMarkerDefect, clauseSlotKey, clauseSlotPattern, conditionHasFormula, conditionNodeSchema, conditionSchema, detectRowBlockPair, emptyCondition, evaluateCondition, evaluateNumericExpression, filtersFromFieldConfig, formatDate, hasBlockDirectivePattern, hasNumberingPattern, isArrayFilterName, isBlockDirectiveKind, isClauseSlotName, isFieldPath, isSafeFieldPath, legacyLoopAlias, legacyMarkerReplacement, loopPattern, markerPattern, normalizeMarkerInner, numPattern, parseCondition, placeholderPattern, qualifyLoopPath, qualifyRowScopedPath, qualifyRowScopedPlaceholder, refPattern, referencedConditionPaths, renderConditionTag, renderDeterministicFieldValue, renderFilterChain, renderForOpener, renderValueMarker, replaceOutputMarkers, resolvePath, rowScopePaths, scanInvalidMarkers, scanMarkers, serializeCondition, substitutionKey, translateLegacyExpression, unwritableFilterValues, unwritableMarkerLiteral };
1704
+ export { ARRAY_FILTER_NAMES, BLOCK_DIRECTIVE_KINDS, CONDITION_RESERVED_WORDS, DATE_FORMAT_EXAMPLE_ISO, DATE_FORMAT_STYLES, DIRECTIVE_KINDS, FILTER_NAMES, LOOP_PROPERTIES, MARKER_DEFECT_KINDS, MARKER_OUTPUT_BODY, MARKER_STATEMENT_BODY, MAX_CONDITION_DEPTH, MAX_CONDITION_NESTING, NUMERIC_FUNCTION_NAMES, arrayFiltersFromFieldConfig, assertNever, blockDirectiveLinePattern, classifyMarker, classifyMarkerDefect, clauseSlotKey, clauseSlotPattern, conditionHasFormula, conditionNodeSchema, conditionSchema, detectRowBlockPair, emptyCondition, evaluateCondition, evaluateNumericExpression, filtersFromFieldConfig, formatDate, hasBlockDirectivePattern, hasNumberingPattern, isArrayFilterName, isBlockDirectiveKind, isClauseSlotName, isFieldPath, isSafeFieldPath, legacyLoopAlias, legacyMarkerReplacement, loopPattern, markerPattern, normalizeMarkerInner, numPattern, parseCondition, placeholderPattern, qualifyLoopPath, qualifyRowScopedPath, qualifyRowScopedPlaceholder, refPattern, referencedConditionPaths, renderConditionTag, renderDeterministicFieldValue, renderFilterChain, renderForOpener, renderValueMarker, replaceOutputMarkers, resolvePath, rowScopePaths, scanInvalidMarkers, scanMarkers, serializeCondition, substitutionKey, translateLegacyExpression, unwritableFilterValues, unwritableMarkerLiteral };
package/dist/path.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ //#region src/path.d.ts
2
+ /**
3
+ * Dotted-path resolution against a data record. Extracted from index.ts so both
4
+ * the condition engine (index.ts) and the arithmetic evaluator (compute.ts) can
5
+ * import it without a dependency cycle.
6
+ */
7
+ /**
8
+ * Narrow `unknown` to something a path segment can be read from.
9
+ *
10
+ * Accepts arrays and class instances. A dotted path segment may be an array index
11
+ * (`parties.1.name`), and rejecting arrays would silently resolve those to
12
+ * `undefined`.
13
+ */
14
+ export declare const isNonNullObject: (v: unknown) => v is Record<string, unknown>;
15
+ /** Resolve a dotted path like `company.name` against data. */
16
+ export declare const resolvePath: (path: string, data: Record<string, unknown>) => unknown;
17
+ //#endregion
package/dist/path.js ADDED
@@ -0,0 +1,27 @@
1
+ //#region src/path.ts
2
+ /**
3
+ * Dotted-path resolution against a data record. Extracted from index.ts so both
4
+ * the condition engine (index.ts) and the arithmetic evaluator (compute.ts) can
5
+ * import it without a dependency cycle.
6
+ */
7
+ /**
8
+ * Narrow `unknown` to something a path segment can be read from.
9
+ *
10
+ * Accepts arrays and class instances. A dotted path segment may be an array index
11
+ * (`parties.1.name`), and rejecting arrays would silently resolve those to
12
+ * `undefined`.
13
+ */
14
+ const isNonNullObject = (v) => typeof v === "object" && v !== null;
15
+ /** Resolve a dotted path like `company.name` against data. */
16
+ const resolvePath = (path, data) => {
17
+ if (Object.hasOwn(data, path)) return data[path];
18
+ const parts = path.split(".");
19
+ let current = data;
20
+ for (const part of parts) {
21
+ if (!isNonNullObject(current)) return;
22
+ current = current[part];
23
+ }
24
+ return current;
25
+ };
26
+ //#endregion
27
+ export { isNonNullObject, resolvePath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/template-conditions",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "A document-template marker and condition engine: {{...}} marker grammar, parse, compute, serialize, field values.",
5
5
  "keywords": [
6
6
  "condition",
@@ -31,6 +31,10 @@
31
31
  ".": {
32
32
  "types": "./dist/index.d.ts",
33
33
  "import": "./dist/index.js"
34
+ },
35
+ "./path": {
36
+ "types": "./dist/path.d.ts",
37
+ "import": "./dist/path.js"
34
38
  }
35
39
  },
36
40
  "publishConfig": {
@@ -41,9 +45,9 @@
41
45
  "build": "tsdown",
42
46
  "pack:dry-run": "bun pm pack --dry-run",
43
47
  "test": "bun test",
44
- "test:property": "bun test --preload @stll/property-testing/preload $(grep -rlF 'fc.assert' src --include='*.test.ts')",
48
+ "test:property": "bun test --preload @stll/property-testing/preload $(grep -rlE 'fc\\.assert|assertProperty' src --include='*.test.ts')",
45
49
  "typecheck": "bun ../../packages/scripts/src/tsc-native.ts --noEmit",
46
- "lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware packages/template-conditions",
50
+ "lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware --type-check packages/template-conditions",
47
51
  "lint:fix": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --type-aware --fix packages/template-conditions",
48
52
  "format": "bun ../../scripts/run-oxfmt.ts .",
49
53
  "prepack": "bun run build"
@@ -57,7 +61,7 @@
57
61
  "@stll/property-testing": "0.0.0",
58
62
  "@stll/typescript-config": "0.0.0",
59
63
  "bun-types": "1.4.2",
60
- "fast-check": "^4.10.1",
64
+ "fast-check": "^4.10.2",
61
65
  "tsdown": "0.23.0"
62
66
  },
63
67
  "main": "./dist/index.js",