@coldsmirk/abacus-core 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -14
- package/dist/index.cjs +684 -61
- package/dist/index.d.cts +255 -43
- package/dist/index.d.ts +255 -43
- package/dist/index.js +673 -62
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/engine/errors.ts
|
|
2
1
|
var ExpressionError = class extends Error {
|
|
3
2
|
expression;
|
|
4
3
|
constructor(message, expression, cause) {
|
|
@@ -13,8 +12,42 @@ var ExpressionNotReadyError = class extends ExpressionError {
|
|
|
13
12
|
this.name = "ExpressionNotReadyError";
|
|
14
13
|
}
|
|
15
14
|
};
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
function isAscii(source) {
|
|
16
|
+
for (let index = 0; index < source.length; index += 1) if (source.codePointAt(index) > 127) return false;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
function utf8ByteLength(codePoint) {
|
|
20
|
+
if (codePoint < 128) return 1;
|
|
21
|
+
if (codePoint < 2048) return 2;
|
|
22
|
+
return codePoint < 65536 ? 3 : 4;
|
|
23
|
+
}
|
|
24
|
+
function utf8OffsetConverter(source) {
|
|
25
|
+
if (isAscii(source)) return (byteOffset) => Math.max(0, Math.min(byteOffset, source.length));
|
|
26
|
+
const byteStarts = [];
|
|
27
|
+
const unitStarts = [];
|
|
28
|
+
let bytes = 0;
|
|
29
|
+
let units = 0;
|
|
30
|
+
for (const char of source) {
|
|
31
|
+
byteStarts.push(bytes);
|
|
32
|
+
unitStarts.push(units);
|
|
33
|
+
bytes += utf8ByteLength(char.codePointAt(0));
|
|
34
|
+
units += char.length;
|
|
35
|
+
}
|
|
36
|
+
byteStarts.push(bytes);
|
|
37
|
+
unitStarts.push(units);
|
|
38
|
+
return (byteOffset) => {
|
|
39
|
+
if (byteOffset <= 0) return 0;
|
|
40
|
+
if (byteOffset >= bytes) return source.length;
|
|
41
|
+
let low = 0;
|
|
42
|
+
let high = byteStarts.length - 1;
|
|
43
|
+
while (low < high) {
|
|
44
|
+
const middle = low + high + 1 >> 1;
|
|
45
|
+
if (byteStarts[middle] <= byteOffset) low = middle;
|
|
46
|
+
else high = middle - 1;
|
|
47
|
+
}
|
|
48
|
+
return unitStarts[low];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
18
51
|
function isUndefined(value) {
|
|
19
52
|
return value === void 0;
|
|
20
53
|
}
|
|
@@ -30,13 +63,13 @@ function isNullish(value) {
|
|
|
30
63
|
function isRecord(value) {
|
|
31
64
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
65
|
}
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/engine/loader.ts
|
|
35
66
|
let enginePromise = null;
|
|
36
67
|
let engineSync = null;
|
|
37
68
|
let engineError = null;
|
|
38
69
|
let configuredInput;
|
|
39
70
|
let typeContextCache = null;
|
|
71
|
+
let loadEpoch = 0;
|
|
72
|
+
let lastLoad = null;
|
|
40
73
|
function evaluateSafely(expression, run) {
|
|
41
74
|
try {
|
|
42
75
|
return run();
|
|
@@ -56,9 +89,13 @@ function configureEngine(options) {
|
|
|
56
89
|
function loadEngine() {
|
|
57
90
|
if (enginePromise) return enginePromise;
|
|
58
91
|
engineError = null;
|
|
59
|
-
|
|
92
|
+
const epoch = loadEpoch;
|
|
93
|
+
const predecessor = lastLoad;
|
|
94
|
+
const input = configuredInput;
|
|
95
|
+
const load = (async () => {
|
|
96
|
+
if (predecessor) await predecessor.catch(() => void 0);
|
|
60
97
|
const zen = await import("@gorules/zen-engine-wasm");
|
|
61
|
-
await zen.default(isUndefined(
|
|
98
|
+
await zen.default(isUndefined(input) ? void 0 : { module_or_path: input });
|
|
62
99
|
const toVariableType = (type) => zen.VariableType.fromJson(type);
|
|
63
100
|
const ensureTypeContext = (variables) => {
|
|
64
101
|
if (typeContextCache && typeContextCache.variables === variables) return typeContextCache;
|
|
@@ -87,9 +124,19 @@ function loadEngine() {
|
|
|
87
124
|
analyze: (variables, source, unary) => {
|
|
88
125
|
const context = ensureTypeContext(variables);
|
|
89
126
|
const rawSpans = unary ? context.handle.typeCheckUnary(source) : context.handle.typeCheck(source);
|
|
127
|
+
if (!Array.isArray(rawSpans)) return {
|
|
128
|
+
rootKind: context.rootKind,
|
|
129
|
+
spans: []
|
|
130
|
+
};
|
|
131
|
+
const toUtf16 = utf8OffsetConverter(source);
|
|
90
132
|
return {
|
|
91
133
|
rootKind: context.rootKind,
|
|
92
|
-
spans:
|
|
134
|
+
spans: rawSpans.map((span) => {
|
|
135
|
+
return {
|
|
136
|
+
...span,
|
|
137
|
+
span: [toUtf16(span.span[0]), toUtf16(span.span[1])]
|
|
138
|
+
};
|
|
139
|
+
})
|
|
93
140
|
};
|
|
94
141
|
},
|
|
95
142
|
satisfies: (actual, expected) => {
|
|
@@ -107,12 +154,17 @@ function loadEngine() {
|
|
|
107
154
|
},
|
|
108
155
|
isReady: () => zen.isReady()
|
|
109
156
|
});
|
|
110
|
-
engineSync = engine;
|
|
157
|
+
if (epoch === loadEpoch) engineSync = engine;
|
|
111
158
|
return engine;
|
|
112
|
-
})()
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
159
|
+
})();
|
|
160
|
+
lastLoad = load;
|
|
161
|
+
enginePromise = load.catch((error) => {
|
|
162
|
+
const failure = new ExpressionError(loadFailureMessage(), void 0, error);
|
|
163
|
+
if (epoch === loadEpoch) {
|
|
164
|
+
enginePromise = null;
|
|
165
|
+
engineError = failure;
|
|
166
|
+
}
|
|
167
|
+
throw failure;
|
|
116
168
|
});
|
|
117
169
|
return enginePromise;
|
|
118
170
|
}
|
|
@@ -127,6 +179,7 @@ function getEngineSync() {
|
|
|
127
179
|
return engineSync;
|
|
128
180
|
}
|
|
129
181
|
function resetEngine() {
|
|
182
|
+
loadEpoch += 1;
|
|
130
183
|
typeContextCache?.handle.free();
|
|
131
184
|
typeContextCache = null;
|
|
132
185
|
enginePromise = null;
|
|
@@ -134,17 +187,180 @@ function resetEngine() {
|
|
|
134
187
|
engineError = null;
|
|
135
188
|
configuredInput = void 0;
|
|
136
189
|
}
|
|
137
|
-
//#endregion
|
|
138
|
-
//#region src/internal/env.ts
|
|
139
190
|
const isDev = detectDev();
|
|
140
191
|
function detectDev() {
|
|
141
192
|
if (typeof process === "undefined") return false;
|
|
142
193
|
return process.env ? process.env.NODE_ENV !== "production" : false;
|
|
143
194
|
}
|
|
144
|
-
|
|
145
|
-
|
|
195
|
+
const U64_MAX = 18446744073709551615n;
|
|
196
|
+
const I64_MIN_MAGNITUDE = 9223372036854775808n;
|
|
197
|
+
const ZEN_DECIMAL_MANTISSA_LIMIT = 2n ** 96n;
|
|
198
|
+
const ZEN_DECIMAL_MANTISSA_LIMIT_TEXT = String(ZEN_DECIMAL_MANTISSA_LIMIT);
|
|
199
|
+
const DECIMAL_SIGN_BIT = 2n ** 95n;
|
|
200
|
+
const EXCESS_PRECISION_LIMIT = 2n ** 52n;
|
|
201
|
+
const NUMBER_TEXT_PATTERN = /^(?<integer>\d+)(?:\.(?<fraction>\d+))?(?:e(?<exponent>[+-]\d+))?$/;
|
|
202
|
+
const UNSIGNED_INTEGER_TEXT_PATTERN = /^\d+$/;
|
|
203
|
+
function isZenUnsignedIntegerText(value) {
|
|
204
|
+
if (!UNSIGNED_INTEGER_TEXT_PATTERN.test(value)) return false;
|
|
205
|
+
const normalized = value.replace(/^0+(?=\d)/, "");
|
|
206
|
+
return normalized.length < ZEN_DECIMAL_MANTISSA_LIMIT_TEXT.length || normalized.length === ZEN_DECIMAL_MANTISSA_LIMIT_TEXT.length && normalized < ZEN_DECIMAL_MANTISSA_LIMIT_TEXT;
|
|
207
|
+
}
|
|
208
|
+
function powerOfTen(exponent) {
|
|
209
|
+
return Number(`1e${exponent}`);
|
|
210
|
+
}
|
|
211
|
+
function normalize(digits, exponent) {
|
|
212
|
+
let d = digits;
|
|
213
|
+
let e = exponent;
|
|
214
|
+
while (d !== 0n && d % 10n === 0n) {
|
|
215
|
+
d /= 10n;
|
|
216
|
+
e += 1;
|
|
217
|
+
}
|
|
218
|
+
return d === 0n ? {
|
|
219
|
+
digits: 0n,
|
|
220
|
+
exponent: 0
|
|
221
|
+
} : {
|
|
222
|
+
digits: d,
|
|
223
|
+
exponent: e
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function parseAsSerdeJson(text) {
|
|
227
|
+
const match = NUMBER_TEXT_PATTERN.exec(text);
|
|
228
|
+
if (match?.groups === void 0) return null;
|
|
229
|
+
const { integer, fraction, exponent } = match.groups;
|
|
230
|
+
let significand = 0n;
|
|
231
|
+
let scale = 0;
|
|
232
|
+
let overflowed = false;
|
|
233
|
+
for (const digit of integer) {
|
|
234
|
+
if (!overflowed && significand * 10n + BigInt(digit) > U64_MAX) overflowed = true;
|
|
235
|
+
if (overflowed) scale += 1;
|
|
236
|
+
else significand = significand * 10n + BigInt(digit);
|
|
237
|
+
}
|
|
238
|
+
if (fraction === void 0 && exponent === void 0 && !overflowed) return {
|
|
239
|
+
kind: "int",
|
|
240
|
+
int: significand,
|
|
241
|
+
f64: 0
|
|
242
|
+
};
|
|
243
|
+
if (fraction !== void 0) for (const digit of fraction) {
|
|
244
|
+
if (overflowed || significand * 10n + BigInt(digit) > U64_MAX) break;
|
|
245
|
+
significand = significand * 10n + BigInt(digit);
|
|
246
|
+
scale -= 1;
|
|
247
|
+
}
|
|
248
|
+
if (exponent !== void 0) scale += Number(exponent);
|
|
249
|
+
let value = Number(significand);
|
|
250
|
+
let remaining = scale;
|
|
251
|
+
for (;;) {
|
|
252
|
+
if (Math.abs(remaining) <= 308) {
|
|
253
|
+
value = remaining >= 0 ? value * powerOfTen(remaining) : value / powerOfTen(-remaining);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
if (value === 0) break;
|
|
257
|
+
if (remaining >= 0) return null;
|
|
258
|
+
value /= 1e308;
|
|
259
|
+
remaining += 308;
|
|
260
|
+
}
|
|
261
|
+
return Number.isFinite(value) ? {
|
|
262
|
+
kind: "f64",
|
|
263
|
+
int: 0n,
|
|
264
|
+
f64: value
|
|
265
|
+
} : null;
|
|
266
|
+
}
|
|
267
|
+
function decimalFromF64(value) {
|
|
268
|
+
if (value === 0) return {
|
|
269
|
+
digits: 0n,
|
|
270
|
+
exponent: 0
|
|
271
|
+
};
|
|
272
|
+
const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
|
|
273
|
+
view.setFloat64(0, value);
|
|
274
|
+
const raw = view.getBigUint64(0);
|
|
275
|
+
const biasedExponent = Number(raw >> 52n & 2047n);
|
|
276
|
+
const fractionBits = raw & 4503599627370495n;
|
|
277
|
+
let bits = biasedExponent === 0 ? fractionBits : fractionBits | 1n << 52n;
|
|
278
|
+
const exponent2 = (biasedExponent === 0 ? 1 : biasedExponent) - 1023 - 52;
|
|
279
|
+
let exponent5 = -exponent2;
|
|
280
|
+
let exponent10 = exponent2;
|
|
281
|
+
while (exponent5 > 0) if ((bits & 1n) === 0n) {
|
|
282
|
+
exponent10 += 1;
|
|
283
|
+
exponent5 -= 1;
|
|
284
|
+
bits >>= 1n;
|
|
285
|
+
} else {
|
|
286
|
+
exponent5 -= 1;
|
|
287
|
+
const timesFive = bits * 5n;
|
|
288
|
+
if (timesFive < ZEN_DECIMAL_MANTISSA_LIMIT) bits = timesFive;
|
|
289
|
+
else {
|
|
290
|
+
exponent10 += 1;
|
|
291
|
+
bits >>= 1n;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
while (exponent5 < 0) if ((bits & DECIMAL_SIGN_BIT) === 0n) {
|
|
295
|
+
exponent10 -= 1;
|
|
296
|
+
exponent5 += 1;
|
|
297
|
+
bits <<= 1n;
|
|
298
|
+
} else if (exponent10 * 2 > -exponent5) return null;
|
|
299
|
+
else {
|
|
300
|
+
exponent5 += 1;
|
|
301
|
+
bits /= 5n;
|
|
302
|
+
}
|
|
303
|
+
while (exponent10 > 0) {
|
|
304
|
+
const timesTen = bits * 10n;
|
|
305
|
+
if (timesTen >= ZEN_DECIMAL_MANTISSA_LIMIT) return null;
|
|
306
|
+
bits = timesTen;
|
|
307
|
+
exponent10 -= 1;
|
|
308
|
+
}
|
|
309
|
+
while (exponent10 < -28) {
|
|
310
|
+
const remainder = bits % 10n;
|
|
311
|
+
bits /= 10n;
|
|
312
|
+
exponent10 += 1;
|
|
313
|
+
if (bits === 0n) exponent10 = 0;
|
|
314
|
+
else if (remainder >= 5n) bits += 1n;
|
|
315
|
+
}
|
|
316
|
+
while (exponent10 < 0 && bits >= EXCESS_PRECISION_LIMIT) {
|
|
317
|
+
const remainder = bits % 10n;
|
|
318
|
+
bits /= 10n;
|
|
319
|
+
exponent10 += 1;
|
|
320
|
+
if (remainder >= 5n) bits += 1n;
|
|
321
|
+
}
|
|
322
|
+
while (exponent10 < 0 && bits % 10n === 0n) {
|
|
323
|
+
bits /= 10n;
|
|
324
|
+
exponent10 += 1;
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
digits: bits,
|
|
328
|
+
exponent: exponent10
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function contextDecimal(value) {
|
|
332
|
+
const magnitude = Math.abs(value);
|
|
333
|
+
if (magnitude === 0) return {
|
|
334
|
+
digits: 0n,
|
|
335
|
+
exponent: 0
|
|
336
|
+
};
|
|
337
|
+
const parsed = parseAsSerdeJson(String(magnitude));
|
|
338
|
+
if (parsed === null) return null;
|
|
339
|
+
if (parsed.kind === "int") {
|
|
340
|
+
if (value >= 0 || parsed.int <= I64_MIN_MAGNITUDE) return normalize(parsed.int, 0);
|
|
341
|
+
const viaF64 = decimalFromF64(Number(parsed.int));
|
|
342
|
+
return viaF64 === null ? null : normalize(viaF64.digits, viaF64.exponent);
|
|
343
|
+
}
|
|
344
|
+
const converted = decimalFromF64(parsed.f64);
|
|
345
|
+
return converted === null ? null : normalize(converted.digits, converted.exponent);
|
|
346
|
+
}
|
|
347
|
+
function literalDecimal(value) {
|
|
348
|
+
const match = NUMBER_TEXT_PATTERN.exec(String(Math.abs(value)));
|
|
349
|
+
if (match?.groups === void 0) return null;
|
|
350
|
+
const { integer, fraction = "", exponent = "0" } = match.groups;
|
|
351
|
+
return normalize(BigInt(integer + fraction), Number(exponent) - fraction.length);
|
|
352
|
+
}
|
|
353
|
+
function isZenConsistentNumber(value) {
|
|
354
|
+
if (!Number.isFinite(value)) return false;
|
|
355
|
+
const literal = literalDecimal(value);
|
|
356
|
+
if (literal === null || literal.digits >= ZEN_DECIMAL_MANTISSA_LIMIT) return false;
|
|
357
|
+
const context = contextDecimal(value);
|
|
358
|
+
return context !== null && literal.digits === context.digits && literal.exponent === context.exponent;
|
|
359
|
+
}
|
|
146
360
|
const SUBJECT_PATTERN = /^[A-Z_$][\w$]*(?:\.[A-Z_$][\w$]*|\[\d+\])*$/i;
|
|
147
|
-
const
|
|
361
|
+
const SUBJECT_IDENTIFIER_PATTERN = /[A-Z_$][\w$]*/gi;
|
|
362
|
+
const SUBJECT_INDEX_PATTERN = /\[(?<index>\d+)\]/g;
|
|
363
|
+
const ZEN_ROOT_RESERVED_WORDS = new Set([
|
|
148
364
|
"and",
|
|
149
365
|
"or",
|
|
150
366
|
"not",
|
|
@@ -153,18 +369,28 @@ const ZEN_RESERVED_WORDS = new Set([
|
|
|
153
369
|
"false",
|
|
154
370
|
"null"
|
|
155
371
|
]);
|
|
372
|
+
const ZEN_MEMBER_RESERVED_WORDS = new Set(["true", "false"]);
|
|
156
373
|
function isIdentifierPath(subject) {
|
|
157
|
-
|
|
374
|
+
if (!SUBJECT_PATTERN.test(subject)) return false;
|
|
375
|
+
const [root, ...members] = subject.match(SUBJECT_IDENTIFIER_PATTERN) ?? [];
|
|
376
|
+
if (root === void 0 || ZEN_ROOT_RESERVED_WORDS.has(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
|
|
377
|
+
for (const match of subject.matchAll(SUBJECT_INDEX_PATTERN)) if (match.groups?.index === void 0 || !isZenUnsignedIntegerText(match.groups.index)) return false;
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
function isZenRepresentableNumber(value) {
|
|
381
|
+
return isZenConsistentNumber(value);
|
|
158
382
|
}
|
|
159
|
-
//#endregion
|
|
160
|
-
//#region src/condition/compile.ts
|
|
161
383
|
function toZenLiteral(value) {
|
|
162
384
|
if (isNullish(value)) return "null";
|
|
163
385
|
if (typeof value === "number") {
|
|
164
|
-
if (!
|
|
386
|
+
if (!isZenRepresentableNumber(value)) throw new ExpressionError(`Number ${String(value)} has no faithful ZEN literal representation`);
|
|
387
|
+
return String(value);
|
|
388
|
+
}
|
|
389
|
+
if (typeof value === "bigint") {
|
|
390
|
+
if (value >= ZEN_DECIMAL_MANTISSA_LIMIT || -value >= ZEN_DECIMAL_MANTISSA_LIMIT) throw new ExpressionError(`Bigint ${String(value)} has no ZEN literal representation`);
|
|
165
391
|
return String(value);
|
|
166
392
|
}
|
|
167
|
-
if (typeof value === "boolean"
|
|
393
|
+
if (typeof value === "boolean") return String(value);
|
|
168
394
|
if (isString(value)) return encodeZenString(value);
|
|
169
395
|
if (isArray(value)) return `[${value.map((item) => toZenLiteral(item)).join(", ")}]`;
|
|
170
396
|
throw new ExpressionError(`Value of type "${typeof value}" has no ZEN literal representation`);
|
|
@@ -172,14 +398,17 @@ function toZenLiteral(value) {
|
|
|
172
398
|
function encodeZenString(value) {
|
|
173
399
|
const hasSingle = value.includes("'");
|
|
174
400
|
const hasDouble = value.includes("\"");
|
|
175
|
-
|
|
176
|
-
|
|
401
|
+
const hasBacktick = value.includes("`");
|
|
402
|
+
if (!hasSingle) return `'${value}'`;
|
|
403
|
+
if (!hasDouble) return `"${value}"`;
|
|
404
|
+
if (!hasBacktick) return `\`${value}\``;
|
|
405
|
+
throw new ExpressionError("String contains every ZEN raw-string delimiter and has no literal representation");
|
|
177
406
|
}
|
|
178
407
|
function toArrayLiteral(value) {
|
|
179
408
|
return isArray(value) ? toZenLiteral(value) : `[${toZenLiteral(value)}]`;
|
|
180
409
|
}
|
|
181
410
|
function zenIsEmpty(subject) {
|
|
182
|
-
return `(${subject} == null or (type(${subject}) == 'string' and len(trim(${subject})) == 0) or (type(${subject}) == 'array' and len(${subject}) == 0))`;
|
|
411
|
+
return `(${subject} == null or (type(${subject}) == 'string' and len(trim(${subject})) == 0) or (type(${subject}) == 'array' and len(${subject}) == 0) or (type(${subject}) == 'object' and len(keys(${subject})) == 0))`;
|
|
183
412
|
}
|
|
184
413
|
function compileFieldCondition(subject, operator, value) {
|
|
185
414
|
switch (operator) {
|
|
@@ -260,8 +489,6 @@ function reportEvaluationFailure(engine, expression, error) {
|
|
|
260
489
|
async function selectBranch(branches, context) {
|
|
261
490
|
return selectBranchWith(branches, context, await loadEngine());
|
|
262
491
|
}
|
|
263
|
-
//#endregion
|
|
264
|
-
//#region src/condition/types.ts
|
|
265
492
|
const CONDITION_OPERATORS = [
|
|
266
493
|
"eq",
|
|
267
494
|
"ne",
|
|
@@ -297,9 +524,83 @@ const CONDITION_OPERATOR_ARITIES = {
|
|
|
297
524
|
function conditionOperatorArity(operator) {
|
|
298
525
|
return CONDITION_OPERATOR_ARITIES[operator];
|
|
299
526
|
}
|
|
300
|
-
|
|
301
|
-
|
|
527
|
+
const MAX_CONDITION_TREE_DEPTH = 64;
|
|
528
|
+
const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
|
|
529
|
+
const nodeIdRealm = Math.random().toString(36).slice(2, 7);
|
|
530
|
+
let nodeIdCounter = 0;
|
|
531
|
+
function newConditionNodeId() {
|
|
532
|
+
nodeIdCounter += 1;
|
|
533
|
+
return `cn-${nodeIdRealm}-${nodeIdCounter}`;
|
|
534
|
+
}
|
|
535
|
+
function emptyConditionGroup() {
|
|
536
|
+
return {
|
|
537
|
+
kind: "group",
|
|
538
|
+
id: newConditionNodeId(),
|
|
539
|
+
op: "and",
|
|
540
|
+
items: []
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function assertConditionTreeStructure(tree) {
|
|
544
|
+
if (!isRecord(tree) || tree.kind !== "group") throw new ExpressionError("Condition tree root must be a group");
|
|
545
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
546
|
+
const stack = [{
|
|
547
|
+
group: tree,
|
|
548
|
+
depth: 0,
|
|
549
|
+
leaving: false
|
|
550
|
+
}];
|
|
551
|
+
while (stack.length > 0) {
|
|
552
|
+
const frame = stack.pop();
|
|
553
|
+
const { group } = frame;
|
|
554
|
+
if (frame.leaving) {
|
|
555
|
+
ancestors.delete(group);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (frame.depth > 64) throw new ExpressionError(`Condition tree exceeds the maximum depth of 64`);
|
|
559
|
+
if (ancestors.has(group)) throw new ExpressionError("Condition tree contains a cycle");
|
|
560
|
+
if (group.op !== "and" && group.op !== "or") throw new ExpressionError(`Unsupported group operator: ${String(group.op)}`);
|
|
561
|
+
if (!isArray(group.items)) throw new ExpressionError("Condition tree group items must be an array");
|
|
562
|
+
ancestors.add(group);
|
|
563
|
+
stack.push({
|
|
564
|
+
...frame,
|
|
565
|
+
leaving: true
|
|
566
|
+
});
|
|
567
|
+
for (let index = group.items.length - 1; index >= 0; index -= 1) {
|
|
568
|
+
const node = group.items[index];
|
|
569
|
+
if (!isRecord(node) || node.kind !== "group" && node.kind !== "rule") throw new ExpressionError("Condition tree contains an invalid node");
|
|
570
|
+
if (node.kind === "group") stack.push({
|
|
571
|
+
group: node,
|
|
572
|
+
depth: frame.depth + 1,
|
|
573
|
+
leaving: false
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function withRuleId(rule) {
|
|
579
|
+
return rule.id === void 0 ? {
|
|
580
|
+
...rule,
|
|
581
|
+
id: newConditionNodeId()
|
|
582
|
+
} : rule;
|
|
583
|
+
}
|
|
584
|
+
function withGroupIds(group) {
|
|
585
|
+
const items = group.items.map((item) => item.kind === "group" ? withGroupIds(item) : withRuleId(item));
|
|
586
|
+
if (group.id !== void 0 && items.every((item, index) => item === group.items[index])) return group;
|
|
587
|
+
return {
|
|
588
|
+
...group,
|
|
589
|
+
id: group.id ?? newConditionNodeId(),
|
|
590
|
+
items
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const normalizedTrees = /* @__PURE__ */ new WeakMap();
|
|
594
|
+
function ensureConditionNodeIds(tree) {
|
|
595
|
+
assertConditionTreeStructure(tree);
|
|
596
|
+
const cached = normalizedTrees.get(tree);
|
|
597
|
+
if (cached) return cached;
|
|
598
|
+
const normalized = withGroupIds(tree);
|
|
599
|
+
normalizedTrees.set(tree, normalized);
|
|
600
|
+
return normalized;
|
|
601
|
+
}
|
|
302
602
|
function compileConditionTree(tree) {
|
|
603
|
+
assertConditionTreeStructure(tree);
|
|
303
604
|
const normalized = normalizeNode(tree);
|
|
304
605
|
return normalized === null ? "" : emitNode(normalized, true);
|
|
305
606
|
}
|
|
@@ -316,9 +617,16 @@ function normalizeNode(node) {
|
|
|
316
617
|
}
|
|
317
618
|
function emitNode(node, topLevel) {
|
|
318
619
|
if (node.kind === "rule") return compileRule(node);
|
|
319
|
-
const joined = node.items.map((item) => emitNode(item, false)).join(node.op
|
|
620
|
+
const joined = node.items.map((item) => emitNode(item, false)).join(groupJoiner(node.op));
|
|
320
621
|
return topLevel ? joined : `(${joined})`;
|
|
321
622
|
}
|
|
623
|
+
function groupJoiner(op) {
|
|
624
|
+
switch (op) {
|
|
625
|
+
case "and": return " and ";
|
|
626
|
+
case "or": return " or ";
|
|
627
|
+
default: throw new ExpressionError(`Unsupported group operator: ${String(op)}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
322
630
|
function compileRule(rule) {
|
|
323
631
|
if (!matchesOperatorArity(rule)) return null;
|
|
324
632
|
return compileCondition({
|
|
@@ -341,8 +649,6 @@ function isConditionScalar(value) {
|
|
|
341
649
|
function isScalarArray(value) {
|
|
342
650
|
return isArray(value) && value.every((item) => isConditionScalar(item));
|
|
343
651
|
}
|
|
344
|
-
//#endregion
|
|
345
|
-
//#region src/condition/lift-tree.ts
|
|
346
652
|
const TWO_CHAR_PUNCTUATION = new Set([
|
|
347
653
|
"==",
|
|
348
654
|
"!=",
|
|
@@ -365,6 +671,10 @@ const IDENT_PART = /[\w$]/;
|
|
|
365
671
|
const DIGIT = /\d/;
|
|
366
672
|
const NUMBER_PATTERN = /^\d+(?:\.\d+)?(?:e[+-]?\d+)?/i;
|
|
367
673
|
const INTEGER_PATTERN = /^\d+$/;
|
|
674
|
+
function parseCanonicalNumber(text) {
|
|
675
|
+
const value = Number(text);
|
|
676
|
+
return String(value) === text && isZenRepresentableNumber(value) ? value : null;
|
|
677
|
+
}
|
|
368
678
|
const COMPARISON_OPERATORS = {
|
|
369
679
|
"==": "eq",
|
|
370
680
|
"!=": "ne",
|
|
@@ -378,7 +688,6 @@ const CALL_OPERATORS = {
|
|
|
378
688
|
startsWith: "starts_with",
|
|
379
689
|
endsWith: "ends_with"
|
|
380
690
|
};
|
|
381
|
-
const MAX_GROUP_DEPTH = 64;
|
|
382
691
|
function liftConditionTree(expression) {
|
|
383
692
|
const tokens = tokenize(expression);
|
|
384
693
|
if (tokens === null || tokens.length === 0) return null;
|
|
@@ -424,8 +733,10 @@ function liftConditionTree(expression) {
|
|
|
424
733
|
return token.value;
|
|
425
734
|
}
|
|
426
735
|
if (token.kind === "number") {
|
|
736
|
+
const value = parseCanonicalNumber(token.value);
|
|
737
|
+
if (value === null) return null;
|
|
427
738
|
pos += 1;
|
|
428
|
-
return
|
|
739
|
+
return value;
|
|
429
740
|
}
|
|
430
741
|
if (token.kind === "ident") {
|
|
431
742
|
if (token.value === "true") {
|
|
@@ -441,8 +752,10 @@ function liftConditionTree(expression) {
|
|
|
441
752
|
if (token.kind === "punct" && token.value === "-") {
|
|
442
753
|
const digits = peek(1);
|
|
443
754
|
if (digits === void 0 || digits.kind !== "number") return null;
|
|
755
|
+
const value = parseCanonicalNumber(digits.value);
|
|
756
|
+
if (value === null || value === 0 || !isZenRepresentableNumber(-value)) return null;
|
|
444
757
|
pos += 2;
|
|
445
|
-
return -
|
|
758
|
+
return -value;
|
|
446
759
|
}
|
|
447
760
|
return null;
|
|
448
761
|
}
|
|
@@ -471,6 +784,7 @@ function liftConditionTree(expression) {
|
|
|
471
784
|
if (right === null || !consumePunct(")")) return null;
|
|
472
785
|
return {
|
|
473
786
|
kind: "rule",
|
|
787
|
+
id: newConditionNodeId(),
|
|
474
788
|
left,
|
|
475
789
|
operator,
|
|
476
790
|
right
|
|
@@ -483,6 +797,7 @@ function liftConditionTree(expression) {
|
|
|
483
797
|
if (right === null || !consumePunct(")")) return null;
|
|
484
798
|
return {
|
|
485
799
|
kind: "rule",
|
|
800
|
+
id: newConditionNodeId(),
|
|
486
801
|
left,
|
|
487
802
|
operator: "not_in",
|
|
488
803
|
right
|
|
@@ -514,6 +829,7 @@ function liftConditionTree(expression) {
|
|
|
514
829
|
pos += expected.length;
|
|
515
830
|
return {
|
|
516
831
|
kind: "rule",
|
|
832
|
+
id: newConditionNodeId(),
|
|
517
833
|
left: read.path,
|
|
518
834
|
operator
|
|
519
835
|
};
|
|
@@ -549,6 +865,7 @@ function liftConditionTree(expression) {
|
|
|
549
865
|
const right = parseArray();
|
|
550
866
|
return right === null ? null : {
|
|
551
867
|
kind: "rule",
|
|
868
|
+
id: newConditionNodeId(),
|
|
552
869
|
left,
|
|
553
870
|
operator: "in",
|
|
554
871
|
right
|
|
@@ -561,6 +878,7 @@ function liftConditionTree(expression) {
|
|
|
561
878
|
const right = parseLiteral();
|
|
562
879
|
return right === null ? null : {
|
|
563
880
|
kind: "rule",
|
|
881
|
+
id: newConditionNodeId(),
|
|
564
882
|
left,
|
|
565
883
|
operator,
|
|
566
884
|
right
|
|
@@ -572,7 +890,7 @@ function liftConditionTree(expression) {
|
|
|
572
890
|
const emptiness = tryEmptiness();
|
|
573
891
|
if (emptiness !== null) return emptiness;
|
|
574
892
|
if (isPunct(peek(), "(")) {
|
|
575
|
-
if (depth >=
|
|
893
|
+
if (depth >= 64) return null;
|
|
576
894
|
pos += 1;
|
|
577
895
|
const inner = parseGroup(depth + 1);
|
|
578
896
|
if (inner === null || !consumePunct(")")) return null;
|
|
@@ -602,6 +920,7 @@ function liftConditionTree(expression) {
|
|
|
602
920
|
}
|
|
603
921
|
return items.length === 1 ? items[0] : {
|
|
604
922
|
kind: "group",
|
|
923
|
+
id: newConditionNodeId(),
|
|
605
924
|
op: op ?? "and",
|
|
606
925
|
items
|
|
607
926
|
};
|
|
@@ -640,6 +959,7 @@ function readPath(tokens, from) {
|
|
|
640
959
|
function asGroup(node) {
|
|
641
960
|
return node.kind === "group" ? node : {
|
|
642
961
|
kind: "group",
|
|
962
|
+
id: newConditionNodeId(),
|
|
643
963
|
op: "and",
|
|
644
964
|
items: [node]
|
|
645
965
|
};
|
|
@@ -656,7 +976,7 @@ function tokenize(input) {
|
|
|
656
976
|
index += 1;
|
|
657
977
|
continue;
|
|
658
978
|
}
|
|
659
|
-
if (char === "'" || char === "\"") {
|
|
979
|
+
if (char === "'" || char === "\"" || char === "`") {
|
|
660
980
|
const end = input.indexOf(char, index + 1);
|
|
661
981
|
if (end === -1) return null;
|
|
662
982
|
tokens.push({
|
|
@@ -707,11 +1027,6 @@ function tokenize(input) {
|
|
|
707
1027
|
}
|
|
708
1028
|
return tokens;
|
|
709
1029
|
}
|
|
710
|
-
//#endregion
|
|
711
|
-
//#region src/condition/tree-types.ts
|
|
712
|
-
const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
|
|
713
|
-
//#endregion
|
|
714
|
-
//#region src/engine/evaluate.ts
|
|
715
1030
|
async function evaluate(expression, context) {
|
|
716
1031
|
await loadEngine();
|
|
717
1032
|
return evaluateSync(expression, context);
|
|
@@ -726,8 +1041,6 @@ function evaluateSync(expression, context) {
|
|
|
726
1041
|
function evaluateUnarySync(expression, context) {
|
|
727
1042
|
return getEngineSync().evaluateUnary(expression, context);
|
|
728
1043
|
}
|
|
729
|
-
//#endregion
|
|
730
|
-
//#region src/engine/messages.ts
|
|
731
1044
|
const COMPLETION_INFO_ZH = {
|
|
732
1045
|
"Returns the length of variable": "返回变量的长度",
|
|
733
1046
|
"Checks if variable contains a needle": "检查变量是否包含指定元素",
|
|
@@ -846,41 +1159,53 @@ const zhCNMessages = {
|
|
|
846
1159
|
expectedType: (expectedType, actualType) => `期望 \`${expectedType}\`,实际为 \`${actualType}\`。`
|
|
847
1160
|
};
|
|
848
1161
|
const localeRegistry = new Map([["en-US", enMessages], ["zh-CN", zhCNMessages]]);
|
|
1162
|
+
let activeBaseMessages = enMessages;
|
|
849
1163
|
let activeMessages = enMessages;
|
|
1164
|
+
const messageListeners = /* @__PURE__ */ new Set();
|
|
850
1165
|
function registerExpressionLocale(locale, messages) {
|
|
851
1166
|
localeRegistry.set(locale, messages);
|
|
852
1167
|
}
|
|
853
1168
|
function configureExpressionMessages({ locale, messages }) {
|
|
854
|
-
const base = locale === void 0 ?
|
|
855
|
-
|
|
1169
|
+
const base = locale === void 0 ? activeBaseMessages : localeRegistry.get(locale) ?? activeBaseMessages;
|
|
1170
|
+
const nextMessages = messages === void 0 ? base : {
|
|
856
1171
|
...base,
|
|
857
1172
|
...messages
|
|
858
1173
|
};
|
|
1174
|
+
activeBaseMessages = base;
|
|
1175
|
+
if (nextMessages === activeMessages) return;
|
|
1176
|
+
activeMessages = nextMessages;
|
|
1177
|
+
for (const listener of messageListeners) listener(activeMessages);
|
|
859
1178
|
}
|
|
860
1179
|
function getExpressionMessages() {
|
|
861
1180
|
return activeMessages;
|
|
862
1181
|
}
|
|
863
|
-
|
|
864
|
-
|
|
1182
|
+
function subscribeExpressionMessages(listener) {
|
|
1183
|
+
messageListeners.add(listener);
|
|
1184
|
+
return () => {
|
|
1185
|
+
messageListeners.delete(listener);
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
865
1188
|
function parseOffset(text) {
|
|
866
1189
|
const trimmed = text?.trim();
|
|
867
1190
|
return trimmed ? Number(trimmed) : NaN;
|
|
868
1191
|
}
|
|
869
1192
|
function extractPosition(message) {
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
return
|
|
1193
|
+
const positionPattern = / at (?:\(\s*(?<rangeFrom>\d+)\s*,\s*(?<rangeTo>\d+)\s*\)|(?<point>\d+))(?=\s*(?:;|$))/g;
|
|
1194
|
+
let position = null;
|
|
1195
|
+
for (const match of message.matchAll(positionPattern)) {
|
|
1196
|
+
const from = parseOffset(match.groups?.rangeFrom ?? match.groups?.point);
|
|
1197
|
+
const to = parseOffset(match.groups?.rangeTo);
|
|
1198
|
+
position = [from, Number.isNaN(to) ? from : to];
|
|
1199
|
+
}
|
|
1200
|
+
return position;
|
|
878
1201
|
}
|
|
879
1202
|
function normalizeDiagnostic(raw, source) {
|
|
880
1203
|
if (raw === null || raw === void 0) return null;
|
|
881
1204
|
const errorType = isRecord(raw) && typeof raw.type === "string" ? raw.type : void 0;
|
|
882
1205
|
const message = isRecord(raw) && typeof raw.source === "string" ? raw.source : String(raw);
|
|
883
|
-
const
|
|
1206
|
+
const bytePosition = extractPosition(message);
|
|
1207
|
+
const toUtf16 = utf8OffsetConverter(source);
|
|
1208
|
+
const [from, to] = bytePosition === null ? [0, source.length] : [toUtf16(bytePosition[0]), toUtf16(bytePosition[1])];
|
|
884
1209
|
return {
|
|
885
1210
|
from,
|
|
886
1211
|
to,
|
|
@@ -932,8 +1257,6 @@ async function satisfiesType(actual, expected) {
|
|
|
932
1257
|
function satisfiesTypeSync(actual, expected) {
|
|
933
1258
|
return getEngineSync().satisfies(actual, expected);
|
|
934
1259
|
}
|
|
935
|
-
//#endregion
|
|
936
|
-
//#region src/engine/template.ts
|
|
937
1260
|
const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
|
|
938
1261
|
function parseTemplateHoles(source) {
|
|
939
1262
|
const holes = [];
|
|
@@ -994,5 +1317,293 @@ async function getTemplateDiagnostics(source) {
|
|
|
994
1317
|
await loadEngine();
|
|
995
1318
|
return getTemplateDiagnosticsSync(source);
|
|
996
1319
|
}
|
|
997
|
-
|
|
998
|
-
|
|
1320
|
+
function fieldType(field) {
|
|
1321
|
+
switch (field.type) {
|
|
1322
|
+
case "string": return "String";
|
|
1323
|
+
case "number": return "Number";
|
|
1324
|
+
case "integer": return "Number";
|
|
1325
|
+
case "boolean": return "Bool";
|
|
1326
|
+
case "object": return { Object: fieldRecord(field.children) };
|
|
1327
|
+
case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
|
|
1328
|
+
case "any": return "Any";
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function fieldRecord(fields) {
|
|
1332
|
+
return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
|
|
1333
|
+
}
|
|
1334
|
+
function schemaTreeToExpressionType(tree) {
|
|
1335
|
+
return { Object: fieldRecord(tree.fields) };
|
|
1336
|
+
}
|
|
1337
|
+
function mergeSchemas(a, b) {
|
|
1338
|
+
if (a.type === "object" && b.type === "object") {
|
|
1339
|
+
const left = a.properties ?? {};
|
|
1340
|
+
const right = b.properties ?? {};
|
|
1341
|
+
const entries = [];
|
|
1342
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1343
|
+
for (const key of keys) {
|
|
1344
|
+
const hasLeft = Object.hasOwn(left, key);
|
|
1345
|
+
const hasRight = Object.hasOwn(right, key);
|
|
1346
|
+
entries.push([key, hasLeft && hasRight ? mergeSchemas(left[key], right[key]) : hasLeft ? left[key] : right[key]]);
|
|
1347
|
+
}
|
|
1348
|
+
const properties = Object.fromEntries(entries);
|
|
1349
|
+
return Object.keys(properties).length > 0 ? {
|
|
1350
|
+
type: "object",
|
|
1351
|
+
properties
|
|
1352
|
+
} : { type: "object" };
|
|
1353
|
+
}
|
|
1354
|
+
if (a.type === "array" && b.type === "array") return a.items !== void 0 && b.items !== void 0 ? {
|
|
1355
|
+
type: "array",
|
|
1356
|
+
items: mergeSchemas(a.items, b.items)
|
|
1357
|
+
} : { type: "array" };
|
|
1358
|
+
return JSON.stringify(a) === JSON.stringify(b) ? a : {};
|
|
1359
|
+
}
|
|
1360
|
+
function inferSchema(sample) {
|
|
1361
|
+
if (sample === null) return {};
|
|
1362
|
+
if (Array.isArray(sample)) {
|
|
1363
|
+
if (sample.length === 0) return { type: "array" };
|
|
1364
|
+
return {
|
|
1365
|
+
type: "array",
|
|
1366
|
+
items: sample.map((element) => inferSchema(element)).reduce((left, right) => mergeSchemas(left, right))
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
if (typeof sample === "string") return { type: "string" };
|
|
1370
|
+
if (typeof sample === "number") return { type: "number" };
|
|
1371
|
+
if (typeof sample === "boolean") return { type: "boolean" };
|
|
1372
|
+
const properties = Object.fromEntries(Object.entries(sample).map(([key, value]) => [key, inferSchema(value)]));
|
|
1373
|
+
return Object.keys(properties).length > 0 ? {
|
|
1374
|
+
type: "object",
|
|
1375
|
+
properties
|
|
1376
|
+
} : { type: "object" };
|
|
1377
|
+
}
|
|
1378
|
+
const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
1379
|
+
const fieldIdRealm = Math.random().toString(36).slice(2, 7);
|
|
1380
|
+
let fieldIdCounter = 0;
|
|
1381
|
+
function newSchemaTreeField(overrides = {}) {
|
|
1382
|
+
fieldIdCounter += 1;
|
|
1383
|
+
return {
|
|
1384
|
+
id: `sf-${fieldIdRealm}-${fieldIdCounter}`,
|
|
1385
|
+
name: "",
|
|
1386
|
+
type: "string",
|
|
1387
|
+
required: false,
|
|
1388
|
+
description: "",
|
|
1389
|
+
children: [],
|
|
1390
|
+
items: null,
|
|
1391
|
+
...overrides
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
const SCALAR_TYPES = new Set([
|
|
1395
|
+
"string",
|
|
1396
|
+
"number",
|
|
1397
|
+
"integer",
|
|
1398
|
+
"boolean"
|
|
1399
|
+
]);
|
|
1400
|
+
const ROOT_KEYS = new Set([
|
|
1401
|
+
"$schema",
|
|
1402
|
+
"type",
|
|
1403
|
+
"properties",
|
|
1404
|
+
"required",
|
|
1405
|
+
"description"
|
|
1406
|
+
]);
|
|
1407
|
+
const SUBSCHEMA_KEYS = new Set([
|
|
1408
|
+
"type",
|
|
1409
|
+
"properties",
|
|
1410
|
+
"required",
|
|
1411
|
+
"items",
|
|
1412
|
+
"description"
|
|
1413
|
+
]);
|
|
1414
|
+
var Unsupported = class extends Error {
|
|
1415
|
+
issue;
|
|
1416
|
+
constructor(issue) {
|
|
1417
|
+
super(issue.code);
|
|
1418
|
+
this.issue = issue;
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
function asPlainObject(value) {
|
|
1422
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1423
|
+
}
|
|
1424
|
+
function checkKeys(schema, allowed) {
|
|
1425
|
+
for (const key of Object.keys(schema)) if (!allowed.has(key)) throw new Unsupported({
|
|
1426
|
+
code: "unsupported-keyword",
|
|
1427
|
+
keyword: key
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
function parseObjectFields(schema) {
|
|
1431
|
+
const properties = schema.properties === void 0 ? null : asPlainObject(schema.properties);
|
|
1432
|
+
if (schema.properties !== void 0 && properties === null) throw new Unsupported({ code: "invalid-properties" });
|
|
1433
|
+
const fields = [];
|
|
1434
|
+
const entries = Object.entries(properties ?? {});
|
|
1435
|
+
for (const [name, subschema] of entries) fields.push({
|
|
1436
|
+
...parseSubschema(subschema, name),
|
|
1437
|
+
name,
|
|
1438
|
+
...name.trim() === "" && { preserveBlankName: true }
|
|
1439
|
+
});
|
|
1440
|
+
if (schema.required !== void 0) {
|
|
1441
|
+
if (!Array.isArray(schema.required)) throw new Unsupported({ code: "invalid-required" });
|
|
1442
|
+
for (const entry of schema.required) {
|
|
1443
|
+
if (typeof entry !== "string") throw new Unsupported({ code: "invalid-required" });
|
|
1444
|
+
const field = fields.find((candidate) => candidate.name === entry);
|
|
1445
|
+
if (field === void 0) throw new Unsupported({
|
|
1446
|
+
code: "unknown-required-field",
|
|
1447
|
+
field: entry
|
|
1448
|
+
});
|
|
1449
|
+
field.required = true;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return fields;
|
|
1453
|
+
}
|
|
1454
|
+
function parseDescription(schema) {
|
|
1455
|
+
if (schema.description === void 0) return "";
|
|
1456
|
+
if (typeof schema.description !== "string") throw new Unsupported({ code: "invalid-description" });
|
|
1457
|
+
return schema.description;
|
|
1458
|
+
}
|
|
1459
|
+
function ensureAbsent(schema, key, holder) {
|
|
1460
|
+
if (schema[key] !== void 0) throw new Unsupported({
|
|
1461
|
+
code: "misplaced-keyword",
|
|
1462
|
+
keyword: key,
|
|
1463
|
+
holder
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
function parseSubschema(value, name) {
|
|
1467
|
+
const schema = asPlainObject(value);
|
|
1468
|
+
if (schema === null) throw new Unsupported({
|
|
1469
|
+
code: "invalid-field-definition",
|
|
1470
|
+
field: name
|
|
1471
|
+
});
|
|
1472
|
+
checkKeys(schema, SUBSCHEMA_KEYS);
|
|
1473
|
+
const { type } = schema;
|
|
1474
|
+
if (type !== void 0 && typeof type !== "string") throw new Unsupported({
|
|
1475
|
+
code: "invalid-field-type",
|
|
1476
|
+
field: name
|
|
1477
|
+
});
|
|
1478
|
+
const description = parseDescription(schema);
|
|
1479
|
+
if (type === "object" || type === void 0 && (schema.properties !== void 0 || schema.required !== void 0)) {
|
|
1480
|
+
ensureAbsent(schema, "items", "array");
|
|
1481
|
+
return newSchemaTreeField({
|
|
1482
|
+
type: "object",
|
|
1483
|
+
description,
|
|
1484
|
+
children: parseObjectFields(schema),
|
|
1485
|
+
explicitType: type === "object"
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
if (type === "array" || type === void 0 && schema.items !== void 0) {
|
|
1489
|
+
ensureAbsent(schema, "properties", "object");
|
|
1490
|
+
ensureAbsent(schema, "required", "object");
|
|
1491
|
+
return newSchemaTreeField({
|
|
1492
|
+
type: "array",
|
|
1493
|
+
description,
|
|
1494
|
+
items: schema.items === void 0 ? null : parseSubschema(schema.items, name),
|
|
1495
|
+
explicitType: type === "array"
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
if (type === void 0) {
|
|
1499
|
+
ensureAbsent(schema, "items", "array");
|
|
1500
|
+
return newSchemaTreeField({
|
|
1501
|
+
type: "any",
|
|
1502
|
+
description
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
if (!SCALAR_TYPES.has(type)) throw new Unsupported({
|
|
1506
|
+
code: "unsupported-field-type",
|
|
1507
|
+
field: name,
|
|
1508
|
+
type
|
|
1509
|
+
});
|
|
1510
|
+
ensureAbsent(schema, "properties", "object");
|
|
1511
|
+
ensureAbsent(schema, "required", "object");
|
|
1512
|
+
ensureAbsent(schema, "items", "array");
|
|
1513
|
+
return newSchemaTreeField({
|
|
1514
|
+
type,
|
|
1515
|
+
description
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
function parseSchemaTree(text) {
|
|
1519
|
+
if (text.trim() === "") return {
|
|
1520
|
+
ok: true,
|
|
1521
|
+
tree: {
|
|
1522
|
+
fields: [],
|
|
1523
|
+
dialect: false,
|
|
1524
|
+
description: ""
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
let parsed;
|
|
1528
|
+
try {
|
|
1529
|
+
parsed = JSON.parse(text);
|
|
1530
|
+
} catch {
|
|
1531
|
+
return {
|
|
1532
|
+
ok: false,
|
|
1533
|
+
issue: { code: "invalid-json" }
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
try {
|
|
1537
|
+
const root = asPlainObject(parsed);
|
|
1538
|
+
if (root === null) throw new Unsupported({ code: "root-not-object" });
|
|
1539
|
+
checkKeys(root, ROOT_KEYS);
|
|
1540
|
+
const dialect = root.$schema !== void 0;
|
|
1541
|
+
if (dialect && root.$schema !== "https://json-schema.org/draft/2020-12/schema") throw new Unsupported({ code: "unsupported-dialect" });
|
|
1542
|
+
if (root.type !== void 0 && root.type !== "object") throw new Unsupported({ code: "root-not-object" });
|
|
1543
|
+
return {
|
|
1544
|
+
ok: true,
|
|
1545
|
+
tree: {
|
|
1546
|
+
fields: parseObjectFields(root),
|
|
1547
|
+
dialect,
|
|
1548
|
+
description: parseDescription(root),
|
|
1549
|
+
explicitType: root.type === "object"
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
if (error instanceof Unsupported) return {
|
|
1554
|
+
ok: false,
|
|
1555
|
+
issue: error.issue
|
|
1556
|
+
};
|
|
1557
|
+
throw error;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function serializeObjectBody(fields) {
|
|
1561
|
+
const named = fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true);
|
|
1562
|
+
const finalByName = /* @__PURE__ */ new Map();
|
|
1563
|
+
const finalFields = [];
|
|
1564
|
+
const body = {};
|
|
1565
|
+
for (const field of named) finalByName.set(field.name, field);
|
|
1566
|
+
finalByName.forEach((field) => {
|
|
1567
|
+
finalFields.push(field);
|
|
1568
|
+
});
|
|
1569
|
+
if (finalFields.length > 0) body.properties = Object.fromEntries(finalFields.map((field) => [field.name, serializeField(field)]));
|
|
1570
|
+
const required = finalFields.filter((field) => field.required).map((field) => field.name);
|
|
1571
|
+
if (required.length > 0) body.required = required;
|
|
1572
|
+
return body;
|
|
1573
|
+
}
|
|
1574
|
+
function serializeField(field) {
|
|
1575
|
+
const description = field.description === "" ? {} : { description: field.description };
|
|
1576
|
+
switch (field.type) {
|
|
1577
|
+
case "any": return { ...description };
|
|
1578
|
+
case "object": return {
|
|
1579
|
+
...field.explicitType !== false && { type: "object" },
|
|
1580
|
+
...description,
|
|
1581
|
+
...serializeObjectBody(field.children)
|
|
1582
|
+
};
|
|
1583
|
+
case "array": {
|
|
1584
|
+
const type = field.explicitType === false ? {} : { type: "array" };
|
|
1585
|
+
return field.items === null || field.items.type === "any" && field.items.description === "" ? {
|
|
1586
|
+
...type,
|
|
1587
|
+
...description
|
|
1588
|
+
} : {
|
|
1589
|
+
...type,
|
|
1590
|
+
...description,
|
|
1591
|
+
items: serializeField(field.items)
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
default: return {
|
|
1595
|
+
type: field.type,
|
|
1596
|
+
...description
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function serializeSchemaTree(tree) {
|
|
1601
|
+
const root = {
|
|
1602
|
+
...tree.dialect && { $schema: "https://json-schema.org/draft/2020-12/schema" },
|
|
1603
|
+
...tree.explicitType !== false && { type: "object" },
|
|
1604
|
+
...tree.description !== "" && { description: tree.description },
|
|
1605
|
+
...serializeObjectBody(tree.fields)
|
|
1606
|
+
};
|
|
1607
|
+
return `${JSON.stringify(root, null, 2)}\n`;
|
|
1608
|
+
}
|
|
1609
|
+
export { CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, ExpressionError, ExpressionNotReadyError, MAX_CONDITION_TREE_DEPTH, SCHEMA_DIALECT_2020_12, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, inferSchema, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, newSchemaTreeField, parseSchemaTree, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, serializeSchemaTree, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|