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