@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 +6 -6
- package/dist/index.js +29 -41
- package/dist/path.d.ts +17 -0
- package/dist/path.js +27 -0
- package/package.json +8 -4
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
|
-
|
|
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
|
-
|
|
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: [
|
|
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.
|
|
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 -
|
|
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.
|
|
64
|
+
"fast-check": "^4.10.2",
|
|
61
65
|
"tsdown": "0.23.0"
|
|
62
66
|
},
|
|
63
67
|
"main": "./dist/index.js",
|