@cornerstonejs/metadata 5.13.2 → 5.14.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 (100) hide show
  1. package/dist/esm/displayset/BaseDisplaySet.d.ts +3 -4
  2. package/dist/esm/displayset/BaseDisplaySet.js +1 -5
  3. package/dist/esm/displayset/IDisplaySet.d.ts +9 -3
  4. package/dist/esm/displayset/ImageStackDisplaySet.d.ts +1 -3
  5. package/dist/esm/displayset/ImageStackDisplaySet.js +0 -1
  6. package/dist/esm/displayset/__fixtures__/scenarioInstances.d.ts +12 -0
  7. package/dist/esm/displayset/__fixtures__/scenarioInstances.js +210 -0
  8. package/dist/esm/displayset/compileSplitRules.d.ts +3 -0
  9. package/dist/esm/displayset/compileSplitRules.js +197 -0
  10. package/dist/esm/displayset/compiledDisplaySetRules.d.ts +3 -0
  11. package/dist/esm/displayset/compiledDisplaySetRules.js +3 -0
  12. package/dist/esm/displayset/createDisplaySetFromGroup.js +54 -19
  13. package/dist/esm/displayset/displaySetContext.d.ts +150 -0
  14. package/dist/esm/displayset/displaySetContext.js +918 -0
  15. package/dist/esm/displayset/displaySetRules.d.ts +5 -0
  16. package/dist/esm/displayset/displaySetRules.js +142 -0
  17. package/dist/esm/displayset/displaySetRules.test.d.ts +1 -0
  18. package/dist/esm/displayset/displaySetRules.test.js +1306 -0
  19. package/dist/esm/displayset/displaySetRulesTypes.d.ts +34 -0
  20. package/dist/esm/displayset/displaySetRulesTypes.js +0 -0
  21. package/dist/esm/displayset/displayset.test.js +422 -130
  22. package/dist/esm/displayset/groupInstancesBySplitRules.d.ts +8 -2
  23. package/dist/esm/displayset/groupInstancesBySplitRules.js +174 -17
  24. package/dist/esm/displayset/index.d.ts +14 -4
  25. package/dist/esm/displayset/index.js +10 -3
  26. package/dist/esm/displayset/isEcgInstance.d.ts +2 -1
  27. package/dist/esm/displayset/isEcgInstance.js +4 -9
  28. package/dist/esm/displayset/isImageInstance.d.ts +2 -1
  29. package/dist/esm/displayset/isImageInstance.js +4 -44
  30. package/dist/esm/displayset/isReconstructableInstances.d.ts +2 -0
  31. package/dist/esm/displayset/isReconstructableInstances.js +91 -0
  32. package/dist/esm/displayset/isVideoInstance.d.ts +2 -1
  33. package/dist/esm/displayset/isVideoInstance.js +4 -30
  34. package/dist/esm/displayset/isWsiInstance.d.ts +2 -1
  35. package/dist/esm/displayset/isWsiInstance.js +4 -6
  36. package/dist/esm/displayset/metadataContext.d.ts +13 -0
  37. package/dist/esm/displayset/metadataContext.js +42 -0
  38. package/dist/esm/displayset/metadataContext.test.d.ts +1 -0
  39. package/dist/esm/displayset/metadataContext.test.js +62 -0
  40. package/dist/esm/displayset/scenarioFixtures.test.d.ts +1 -0
  41. package/dist/esm/displayset/scenarioFixtures.test.js +206 -0
  42. package/dist/esm/displayset/seriesContext.test.d.ts +1 -0
  43. package/dist/esm/displayset/seriesContext.test.js +238 -0
  44. package/dist/esm/displayset/seriesFunctions.d.ts +29 -0
  45. package/dist/esm/displayset/seriesFunctions.js +327 -0
  46. package/dist/esm/displayset/sopClassUidLists.d.ts +14 -0
  47. package/dist/esm/displayset/sopClassUidLists.js +100 -0
  48. package/dist/esm/displayset/sortInstances.d.ts +10 -0
  49. package/dist/esm/displayset/sortInstances.js +33 -0
  50. package/dist/esm/displayset/sortInstances.test.d.ts +1 -0
  51. package/dist/esm/displayset/sortInstances.test.js +56 -0
  52. package/dist/esm/displayset/splitImageIdsBySplitRules.d.ts +3 -3
  53. package/dist/esm/displayset/splitImageIdsBySplitRules.js +5 -5
  54. package/dist/esm/displayset/splitRuleSchema.d.ts +11 -0
  55. package/dist/esm/displayset/splitRuleSchema.js +168 -0
  56. package/dist/esm/displayset/splitRuleSet.d.ts +12 -0
  57. package/dist/esm/displayset/splitRuleSet.js +47 -0
  58. package/dist/esm/displayset/splitRuleSet.test.d.ts +1 -0
  59. package/dist/esm/displayset/splitRuleSet.test.js +119 -0
  60. package/dist/esm/displayset/types.d.ts +37 -4
  61. package/dist/esm/displayset/types.js +15 -0
  62. package/dist/esm/index.d.ts +4 -2
  63. package/dist/esm/index.js +2 -1
  64. package/dist/esm/safeFunctions/compile.d.ts +31 -0
  65. package/dist/esm/safeFunctions/compile.js +142 -0
  66. package/dist/esm/safeFunctions/compile.test.d.ts +1 -0
  67. package/dist/esm/safeFunctions/compile.test.js +82 -0
  68. package/dist/esm/safeFunctions/expression/compiler.d.ts +23 -0
  69. package/dist/esm/safeFunctions/expression/compiler.js +481 -0
  70. package/dist/esm/safeFunctions/expression/expression.test.d.ts +1 -0
  71. package/dist/esm/safeFunctions/expression/expression.test.js +325 -0
  72. package/dist/esm/safeFunctions/expression/index.d.ts +4 -0
  73. package/dist/esm/safeFunctions/expression/index.js +3 -0
  74. package/dist/esm/safeFunctions/expression/parser.d.ts +56 -0
  75. package/dist/esm/safeFunctions/expression/parser.js +248 -0
  76. package/dist/esm/safeFunctions/expression/tokenizer.d.ts +29 -0
  77. package/dist/esm/safeFunctions/expression/tokenizer.js +198 -0
  78. package/dist/esm/safeFunctions/index.d.ts +5 -0
  79. package/dist/esm/safeFunctions/index.js +4 -0
  80. package/dist/esm/safeFunctions/matchTests.d.ts +16 -0
  81. package/dist/esm/safeFunctions/matchTests.js +203 -0
  82. package/dist/esm/safeFunctions/schema.d.ts +72 -0
  83. package/dist/esm/safeFunctions/schema.js +361 -0
  84. package/dist/esm/safeFunctions/types.d.ts +47 -0
  85. package/dist/esm/safeFunctions/types.js +0 -0
  86. package/dist/esm/safeFunctions/values.d.ts +6 -0
  87. package/dist/esm/safeFunctions/values.js +71 -0
  88. package/dist/esm/utilities/Tags.d.ts +1 -0
  89. package/dist/esm/utilities/Tags.js +14 -2
  90. package/dist/esm/utilities/index.d.ts +1 -1
  91. package/dist/esm/utilities/index.js +1 -1
  92. package/dist/esm/utilities/toNumber.d.ts +1 -1
  93. package/dist/esm/utilities/toNumber.js +1 -1
  94. package/dist/esm/version.d.ts +1 -1
  95. package/dist/esm/version.js +1 -1
  96. package/package.json +2 -2
  97. package/dist/esm/displayset/defaultDisplaySetSplitRules.d.ts +0 -2
  98. package/dist/esm/displayset/defaultDisplaySetSplitRules.js +0 -93
  99. package/dist/esm/displayset/viewportTypes.d.ts +0 -4
  100. package/dist/esm/displayset/viewportTypes.js +0 -13
@@ -0,0 +1,198 @@
1
+ const PUNCTUATORS = [
2
+ '===',
3
+ '!==',
4
+ '==',
5
+ '!=',
6
+ '<=',
7
+ '>=',
8
+ '&&',
9
+ '||',
10
+ '<',
11
+ '>',
12
+ '+',
13
+ '-',
14
+ '*',
15
+ '/',
16
+ '%',
17
+ '!',
18
+ '(',
19
+ ')',
20
+ '[',
21
+ ']',
22
+ ',',
23
+ '.',
24
+ '?',
25
+ ':',
26
+ ];
27
+ const isIdentStart = (ch) => /[A-Za-z_$]/.test(ch);
28
+ const isIdentPart = (ch) => /[A-Za-z0-9_$]/.test(ch);
29
+ const isDigit = (ch) => ch >= '0' && ch <= '9';
30
+ export class ExpressionSyntaxError extends Error {
31
+ constructor(message, expression) {
32
+ super(`${message} in expression: ${expression}`);
33
+ this.name = 'ExpressionSyntaxError';
34
+ }
35
+ }
36
+ function readEscape(source, index) {
37
+ const escaped = source[index];
38
+ const map = {
39
+ n: '\n',
40
+ t: '\t',
41
+ r: '\r',
42
+ '\\': '\\',
43
+ "'": "'",
44
+ '"': '"',
45
+ '`': '`',
46
+ $: '$',
47
+ };
48
+ return { ch: map[escaped] ?? escaped, next: index + 1 };
49
+ }
50
+ function readTemplate(source, start, expression) {
51
+ const parts = [];
52
+ let text = '';
53
+ let i = start;
54
+ while (i < source.length) {
55
+ const ch = source[i];
56
+ if (ch === '`') {
57
+ if (text) {
58
+ parts.push({ kind: 'text', text });
59
+ }
60
+ return { parts, next: i + 1 };
61
+ }
62
+ if (ch === '\\') {
63
+ const { ch: escaped, next } = readEscape(source, i + 1);
64
+ text += escaped;
65
+ i = next;
66
+ continue;
67
+ }
68
+ if (ch === '$' && source[i + 1] === '{') {
69
+ if (text) {
70
+ parts.push({ kind: 'text', text });
71
+ text = '';
72
+ }
73
+ let depth = 1;
74
+ let j = i + 2;
75
+ let exprSource = '';
76
+ while (j < source.length && depth > 0) {
77
+ const c = source[j];
78
+ if (c === '{') {
79
+ depth++;
80
+ }
81
+ else if (c === '}') {
82
+ depth--;
83
+ if (depth === 0) {
84
+ break;
85
+ }
86
+ }
87
+ else if (c === "'" || c === '"') {
88
+ const quote = c;
89
+ exprSource += c;
90
+ j++;
91
+ while (j < source.length && source[j] !== quote) {
92
+ if (source[j] === '\\') {
93
+ exprSource += source[j];
94
+ j++;
95
+ }
96
+ exprSource += source[j];
97
+ j++;
98
+ }
99
+ }
100
+ exprSource += source[j];
101
+ j++;
102
+ }
103
+ if (depth !== 0) {
104
+ throw new ExpressionSyntaxError('Unterminated template interpolation', expression);
105
+ }
106
+ parts.push({ kind: 'expr', source: exprSource });
107
+ i = j + 1;
108
+ continue;
109
+ }
110
+ text += ch;
111
+ i++;
112
+ }
113
+ throw new ExpressionSyntaxError('Unterminated template literal', expression);
114
+ }
115
+ export function tokenize(source, expression = source) {
116
+ const tokens = [];
117
+ let i = 0;
118
+ outer: while (i < source.length) {
119
+ const ch = source[i];
120
+ if (/\s/.test(ch)) {
121
+ i++;
122
+ continue;
123
+ }
124
+ if (isDigit(ch) || (ch === '.' && isDigit(source[i + 1]))) {
125
+ let j = i;
126
+ while (j < source.length && /[0-9.]/.test(source[j])) {
127
+ j++;
128
+ }
129
+ if (source[j] === 'e' || source[j] === 'E') {
130
+ let k = j + 1;
131
+ if (source[k] === '+' || source[k] === '-') {
132
+ k++;
133
+ }
134
+ if (isDigit(source[k])) {
135
+ k++;
136
+ while (k < source.length && isDigit(source[k])) {
137
+ k++;
138
+ }
139
+ j = k;
140
+ }
141
+ }
142
+ const raw = source.slice(i, j);
143
+ const value = Number(raw);
144
+ if (Number.isNaN(value)) {
145
+ throw new ExpressionSyntaxError(`Invalid number '${raw}'`, expression);
146
+ }
147
+ tokens.push({ kind: 'number', value });
148
+ i = j;
149
+ continue;
150
+ }
151
+ if (ch === "'" || ch === '"') {
152
+ let value = '';
153
+ let j = i + 1;
154
+ while (j < source.length && source[j] !== ch) {
155
+ if (source[j] === '\\') {
156
+ const { ch: escaped, next } = readEscape(source, j + 1);
157
+ value += escaped;
158
+ j = next;
159
+ }
160
+ else {
161
+ value += source[j];
162
+ j++;
163
+ }
164
+ }
165
+ if (j >= source.length) {
166
+ throw new ExpressionSyntaxError('Unterminated string literal', expression);
167
+ }
168
+ tokens.push({ kind: 'string', value });
169
+ i = j + 1;
170
+ continue;
171
+ }
172
+ if (ch === '`') {
173
+ const { parts, next } = readTemplate(source, i + 1, expression);
174
+ tokens.push({ kind: 'template', parts });
175
+ i = next;
176
+ continue;
177
+ }
178
+ if (isIdentStart(ch)) {
179
+ let j = i + 1;
180
+ while (j < source.length && isIdentPart(source[j])) {
181
+ j++;
182
+ }
183
+ tokens.push({ kind: 'ident', name: source.slice(i, j) });
184
+ i = j;
185
+ continue;
186
+ }
187
+ for (const punct of PUNCTUATORS) {
188
+ if (source.startsWith(punct, i)) {
189
+ tokens.push({ kind: 'punct', value: punct });
190
+ i += punct.length;
191
+ continue outer;
192
+ }
193
+ }
194
+ throw new ExpressionSyntaxError(`Unexpected character '${ch}'`, expression);
195
+ }
196
+ tokens.push({ kind: 'end' });
197
+ return tokens;
198
+ }
@@ -0,0 +1,5 @@
1
+ export { compileCondition, compileConditionAt, compileExpressionAt, compileTemplate, compileValue, compileValueAt, createSite, hasNoValue, invalid, isAbsent, isEmpty, isTrue, compareFinite, looseEquals, parseTemplate, readNameAt, readOwnName, safeFunctionCompilers, type CompileOptions, type TemplateSegment, } from './compile.js';
2
+ export { compileMatchTest, MATCH_HELPERS, MATCH_OPERATORS, type ValueTest, } from './matchTests.js';
3
+ export { at, checkForm, compileKey, conditionShape, describeForm, describeShape, didYouMean, INSTANCE_EXPRESSION_SCOPE, invalidAt, isLeafKind, isUnsafeKey, MATCH_OPERATOR_NAMES, matchForm, matchTestShape, hasOwn, readOwn, RuleCompileError, safeFunctionSchema, selectForm, unrecognized, valueShape, type ExpressionScope, type NameReader, type NameReaderFunction, type Schema, type SchemaForm, type SchemaKey, type SchemaLeafKind, type SchemaShape, type SchemaSite, type ShapeCompiler, } from './schema.js';
4
+ export { collectIdentifiers, collectReads, compileExpression, ExpressionSyntaxError, parseExpressionSource, tokenize, FORBIDDEN_PROPERTIES, type CompiledExpression, type CompileExpressionOptions, type ExpressionNode, type ExpressionRead, } from './expression/index.js';
5
+ export type { CompiledPredicate, CompiledValue, InlinePredicate, InlineValue, RawCondition, RawMatchTest, RawScalar, RawValue, SafeFunctionSubject, } from './types.js';
@@ -0,0 +1,4 @@
1
+ export { compileCondition, compileConditionAt, compileExpressionAt, compileTemplate, compileValue, compileValueAt, createSite, hasNoValue, invalid, isAbsent, isEmpty, isTrue, compareFinite, looseEquals, parseTemplate, readNameAt, readOwnName, safeFunctionCompilers, } from './compile.js';
2
+ export { compileMatchTest, MATCH_HELPERS, MATCH_OPERATORS, } from './matchTests.js';
3
+ export { at, checkForm, compileKey, conditionShape, describeForm, describeShape, didYouMean, INSTANCE_EXPRESSION_SCOPE, invalidAt, isLeafKind, isUnsafeKey, MATCH_OPERATOR_NAMES, matchForm, matchTestShape, hasOwn, readOwn, RuleCompileError, safeFunctionSchema, selectForm, unrecognized, valueShape, } from './schema.js';
4
+ export { collectIdentifiers, collectReads, compileExpression, ExpressionSyntaxError, parseExpressionSource, tokenize, FORBIDDEN_PROPERTIES, } from './expression/index.js';
@@ -0,0 +1,16 @@
1
+ import type { SchemaSite } from './schema.js';
2
+ export type ValueTest = (value: unknown, subject?: unknown, context?: unknown) => boolean;
3
+ type Operator = {
4
+ readonly operand: string;
5
+ readonly accepts: (operand: unknown) => boolean;
6
+ readonly compile: (operand: unknown) => ValueTest;
7
+ };
8
+ export declare const MATCH_HELPERS: Readonly<{
9
+ includes: (value: unknown, operand: unknown) => boolean;
10
+ contains: (value: unknown, operand: unknown) => boolean;
11
+ startsWith: (value: unknown, operand: unknown) => boolean;
12
+ endsWith: (value: unknown, operand: unknown) => boolean;
13
+ }>;
14
+ export declare const MATCH_OPERATORS: Readonly<Record<string, Operator>>;
15
+ export declare function compileMatchTest(test: unknown, site: SchemaSite): ValueTest;
16
+ export {};
@@ -0,0 +1,203 @@
1
+ import { asArray, toFiniteFirst } from '@cornerstonejs/utils';
2
+ import { compareFinite, hasNoValue, looseEquals } from './values.js';
3
+ import { invalidAt, matchForm, matchTestShape } from './schema.js';
4
+ const isScalar = (value) => typeof value === 'string' ||
5
+ typeof value === 'boolean' ||
6
+ (typeof value === 'number' && Number.isFinite(value));
7
+ const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
8
+ const isScalarOrScalars = (value) => isScalar(value) ||
9
+ (Array.isArray(value) && value.length > 0 && value.every(isScalar));
10
+ const isTextOrTexts = (value) => typeof value === 'string' ||
11
+ (Array.isArray(value) &&
12
+ value.length > 0 &&
13
+ value.every((item) => typeof item === 'string'));
14
+ const elementsOf = (value) => hasNoValue(value) ? [] : asArray(value).filter((item) => !hasNoValue(item));
15
+ const textsOf = (value, ignoreCase = false) => elementsOf(value).map((item) => ignoreCase ? String(item).toLowerCase() : String(item));
16
+ const needlesOf = (operand, ignoreCase = false) => asArray(operand).map((item) => ignoreCase ? String(item).toLowerCase() : String(item));
17
+ function equalsTest(operand) {
18
+ return operand === null
19
+ ? (value) => hasNoValue(value)
20
+ : (value) => looseEquals(value, operand);
21
+ }
22
+ const SET_MIN_LENGTH = 9;
23
+ const stringSets = new WeakMap();
24
+ function stringSetOf(list) {
25
+ if (!Array.isArray(list) || list.length < SET_MIN_LENGTH) {
26
+ return null;
27
+ }
28
+ let set = stringSets.get(list);
29
+ if (set === undefined) {
30
+ set = list.every((element) => typeof element === 'string' && element)
31
+ ? new Set(list)
32
+ : null;
33
+ stringSets.set(list, set);
34
+ }
35
+ return set;
36
+ }
37
+ function someInList(items, list) {
38
+ const set = stringSetOf(list);
39
+ const elements = set ? list : elementsOf(list);
40
+ return items.some((item) => set && (typeof item === 'string' || typeof item === 'boolean')
41
+ ? set.has(String(item))
42
+ : elements.some((element) => looseEquals(item, element)));
43
+ }
44
+ function includesTest(operand) {
45
+ const wanted = [...asArray(operand)];
46
+ stringSetOf(wanted);
47
+ return (value) => someInList(elementsOf(value), wanted);
48
+ }
49
+ function textTest(ignoreCase, test) {
50
+ return (operand) => {
51
+ const needles = needlesOf(operand, ignoreCase);
52
+ return (value) => textsOf(value, ignoreCase).some((text) => needles.some((needle) => test(text, needle)));
53
+ };
54
+ }
55
+ const not = (compile) => (operand) => {
56
+ const test = compile(operand);
57
+ return (value, subject, context) => !test(value, subject, context);
58
+ };
59
+ function numberTest(test) {
60
+ const compare = compareFinite(test);
61
+ return (operand) => (value) => compare(value, operand);
62
+ }
63
+ const contains = textTest(false, (text, needle) => text.includes(needle));
64
+ const containsI = textTest(true, (text, needle) => text.includes(needle));
65
+ const startsWith = textTest(false, (text, needle) => text.startsWith(needle));
66
+ const endsWith = textTest(false, (text, needle) => text.endsWith(needle));
67
+ export const MATCH_HELPERS = Object.freeze({
68
+ includes: (value, operand) => someInList(elementsOf(operand), value),
69
+ contains: (value, operand) => contains(operand)(value),
70
+ startsWith: (value, operand) => startsWith(operand)(value),
71
+ endsWith: (value, operand) => endsWith(operand)(value),
72
+ });
73
+ export const MATCH_OPERATORS = Object.freeze({
74
+ equals: {
75
+ operand: 'a scalar, a non-empty array of scalars, or null',
76
+ accepts: (operand) => operand === null || isScalarOrScalars(operand),
77
+ compile: equalsTest,
78
+ },
79
+ doesNotEqual: {
80
+ operand: 'a scalar, a non-empty array of scalars, or null',
81
+ accepts: (operand) => operand === null || isScalarOrScalars(operand),
82
+ compile: not(equalsTest),
83
+ },
84
+ notNull: {
85
+ operand: 'a boolean',
86
+ accepts: (operand) => typeof operand === 'boolean',
87
+ compile: (operand) => operand ? (value) => !hasNoValue(value) : (value) => hasNoValue(value),
88
+ },
89
+ includes: {
90
+ operand: 'a scalar, or a non-empty array of scalars',
91
+ accepts: isScalarOrScalars,
92
+ compile: includesTest,
93
+ },
94
+ doesNotInclude: {
95
+ operand: 'a scalar, or a non-empty array of scalars',
96
+ accepts: isScalarOrScalars,
97
+ compile: not(includesTest),
98
+ },
99
+ greaterThan: {
100
+ operand: 'a finite number',
101
+ accepts: isFiniteNumber,
102
+ compile: numberTest((value, operand) => value > operand),
103
+ },
104
+ greaterThanOrEqual: {
105
+ operand: 'a finite number',
106
+ accepts: isFiniteNumber,
107
+ compile: numberTest((value, operand) => value >= operand),
108
+ },
109
+ lessThan: {
110
+ operand: 'a finite number',
111
+ accepts: isFiniteNumber,
112
+ compile: numberTest((value, operand) => value < operand),
113
+ },
114
+ lessThanOrEqual: {
115
+ operand: 'a finite number',
116
+ accepts: isFiniteNumber,
117
+ compile: numberTest((value, operand) => value <= operand),
118
+ },
119
+ range: {
120
+ operand: 'an array of two finite numbers',
121
+ accepts: (operand) => Array.isArray(operand) &&
122
+ operand.length === 2 &&
123
+ operand.every(isFiniteNumber),
124
+ compile: (operand) => {
125
+ const [a, b] = operand;
126
+ const low = Math.min(a, b);
127
+ const high = Math.max(a, b);
128
+ return (value) => {
129
+ const number = toFiniteFirst(value);
130
+ return number !== undefined && number >= low && number <= high;
131
+ };
132
+ },
133
+ },
134
+ contains: {
135
+ operand: 'a string, or a non-empty array of strings',
136
+ accepts: isTextOrTexts,
137
+ compile: contains,
138
+ },
139
+ containsI: {
140
+ operand: 'a string, or a non-empty array of strings',
141
+ accepts: isTextOrTexts,
142
+ compile: containsI,
143
+ },
144
+ doesNotContain: {
145
+ operand: 'a string, or a non-empty array of strings',
146
+ accepts: isTextOrTexts,
147
+ compile: not(contains),
148
+ },
149
+ doesNotContainI: {
150
+ operand: 'a string, or a non-empty array of strings',
151
+ accepts: isTextOrTexts,
152
+ compile: not(containsI),
153
+ },
154
+ containsAll: {
155
+ operand: 'a scalar, or a non-empty array of scalars',
156
+ accepts: isScalarOrScalars,
157
+ compile: (operand) => {
158
+ const wanted = asArray(operand);
159
+ return (value) => {
160
+ const elements = elementsOf(value);
161
+ return (elements.length > 0 &&
162
+ wanted.every((element) => elements.some((item) => looseEquals(item, element))));
163
+ };
164
+ },
165
+ },
166
+ startsWith: {
167
+ operand: 'a string, or a non-empty array of strings',
168
+ accepts: isTextOrTexts,
169
+ compile: startsWith,
170
+ },
171
+ endsWith: {
172
+ operand: 'a string, or a non-empty array of strings',
173
+ accepts: isTextOrTexts,
174
+ compile: endsWith,
175
+ },
176
+ });
177
+ export function compileMatchTest(test, site) {
178
+ if (typeof test === 'function') {
179
+ return test;
180
+ }
181
+ if (test === null) {
182
+ return equalsTest(test);
183
+ }
184
+ if (isScalar(test)) {
185
+ return includesTest(test);
186
+ }
187
+ if (Array.isArray(test)) {
188
+ if (!test.length || !test.every(isScalar)) {
189
+ return invalidAt(site, 'an array test must hold one or more scalars', test);
190
+ }
191
+ return includesTest(test);
192
+ }
193
+ if (!test || typeof test !== 'object') {
194
+ return invalidAt(site, 'expected a scalar, an array of scalars, null, an object with one operator, or a function', test);
195
+ }
196
+ matchForm(matchTestShape, test, site);
197
+ const [name, operand] = Object.entries(test)[0];
198
+ const operator = MATCH_OPERATORS[name];
199
+ if (!operator.accepts(operand)) {
200
+ return invalidAt(site, `the operand of ${name} must be ${operator.operand}`, test);
201
+ }
202
+ return operator.compile(operand);
203
+ }
@@ -0,0 +1,72 @@
1
+ export type ExpressionScope = {
2
+ readonly params: readonly string[];
3
+ readonly implicitScope: string | false;
4
+ };
5
+ export type SchemaLeafKind = 'string' | 'number' | 'boolean' | 'true' | 'scalar' | 'literal' | 'priority' | 'expression';
6
+ export type SchemaKey = {
7
+ readonly kind: string;
8
+ readonly list?: boolean;
9
+ readonly record?: boolean;
10
+ readonly optional?: boolean;
11
+ readonly oneOf?: readonly string[];
12
+ readonly keys?: Readonly<Record<string, SchemaKey>>;
13
+ readonly extraKeys?: Readonly<Record<string, SchemaKey>>;
14
+ readonly role?: 'operator' | 'modifier';
15
+ readonly with?: readonly string[];
16
+ readonly function?: boolean;
17
+ readonly call?: string;
18
+ readonly expression?: ExpressionScope;
19
+ readonly description?: string;
20
+ };
21
+ export type SchemaForm = {
22
+ readonly selectBy?: string;
23
+ readonly keys: Readonly<Record<string, SchemaKey>>;
24
+ readonly expression?: ExpressionScope;
25
+ readonly description?: string;
26
+ };
27
+ export type SchemaShape = {
28
+ readonly name: string;
29
+ readonly string?: 'expression' | 'attribute';
30
+ readonly function?: boolean;
31
+ readonly forms: Readonly<Record<string, SchemaForm>>;
32
+ readonly keyNoun?: string;
33
+ };
34
+ export type Schema = Readonly<Record<string, SchemaShape>>;
35
+ export type NameReaderFunction = (subject: unknown, context?: unknown) => unknown;
36
+ export type NameReader = (name: string, site: SchemaSite, field?: string) => NameReaderFunction;
37
+ export type ShapeCompiler = (fragment: unknown, site: SchemaSite) => unknown;
38
+ export type SchemaSite = {
39
+ readonly path: string;
40
+ readonly definition: string;
41
+ readonly schema: Schema;
42
+ readonly compilers: Readonly<Record<string, ShapeCompiler>>;
43
+ readonly readName?: NameReader;
44
+ readonly expression?: ExpressionScope;
45
+ readonly call?: string;
46
+ readonly extraKeys?: Readonly<Record<string, SchemaKey>>;
47
+ };
48
+ export declare const INSTANCE_EXPRESSION_SCOPE: ExpressionScope;
49
+ export declare function hasOwn(object: unknown, key: string): boolean;
50
+ export declare function readOwn(object: unknown, key: string): unknown;
51
+ export declare function isUnsafeKey(key: string): boolean;
52
+ export declare class RuleCompileError extends Error {
53
+ readonly definition: string;
54
+ readonly path: string;
55
+ constructor(definition: string, path: string, message: string);
56
+ }
57
+ export declare function invalidAt(site: Pick<SchemaSite, 'path' | 'definition'>, message: string, ...fragment: unknown[]): never;
58
+ export declare function didYouMean(name: string, candidates: Iterable<string | undefined>): string;
59
+ export declare function at(site: SchemaSite, key: string | number, spec?: SchemaKey): SchemaSite;
60
+ export declare function isLeafKind(kind: string): kind is SchemaLeafKind;
61
+ export declare function describeForm(form: SchemaForm, scope?: ExpressionScope): string;
62
+ export declare function describeShape(shape: SchemaShape, site?: SchemaSite): string;
63
+ export declare function unrecognized(shape: SchemaShape, fragment: unknown, site: SchemaSite): never;
64
+ export declare function selectForm(shape: SchemaShape, fragment: unknown, site: SchemaSite): [string, SchemaForm];
65
+ export declare function checkForm(form: SchemaForm, fragment: Record<string, unknown>, site: SchemaSite, keyNoun?: string): void;
66
+ export declare function compileKey(spec: SchemaKey, value: unknown, site: SchemaSite): unknown;
67
+ export declare function matchForm(shape: SchemaShape, fragment: unknown, site: SchemaSite): [string, SchemaForm];
68
+ export declare const MATCH_OPERATOR_NAMES: readonly ["equals", "doesNotEqual", "notNull", "includes", "doesNotInclude", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "range", "contains", "containsI", "doesNotContain", "doesNotContainI", "containsAll", "startsWith", "endsWith"];
69
+ export declare const matchTestShape: SchemaShape;
70
+ export declare const conditionShape: SchemaShape;
71
+ export declare const valueShape: SchemaShape;
72
+ export declare const safeFunctionSchema: Schema;