bonsai-js 0.2.1 → 0.3.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/dist/index.mjs CHANGED
@@ -1,477 +1,5 @@
1
1
  import { a as attachLocation, c as offsetToPosition, i as ExpressionError, l as suggest, n as BonsaiSecurityError, o as formatBonsaiError, r as BonsaiTypeError, s as formatError, t as BonsaiReferenceError } from "./errors-t5i9AEaf.mjs";
2
- //#region src/lexer.ts
3
- const KEYWORDS = new Map([
4
- ["true", {
5
- type: "Boolean",
6
- value: "true"
7
- }],
8
- ["false", {
9
- type: "Boolean",
10
- value: "false"
11
- }],
12
- ["null", {
13
- type: "Null",
14
- value: "null"
15
- }],
16
- ["undefined", {
17
- type: "Undefined",
18
- value: "undefined"
19
- }],
20
- ["in", {
21
- type: "Operator",
22
- value: "in"
23
- }],
24
- ["not", {
25
- type: "Operator",
26
- value: "not"
27
- }]
28
- ]);
29
- function isDigit(ch) {
30
- return ch >= "0" && ch <= "9";
31
- }
32
- function isAlpha(ch) {
33
- return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$";
34
- }
35
- function isAlphaNumeric(ch) {
36
- return isAlpha(ch) || isDigit(ch);
37
- }
38
- function isHexDigit(ch) {
39
- return isDigit(ch) || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
40
- }
41
- function tokenize(source) {
42
- const tokens = [];
43
- let i = 0;
44
- while (i < source.length) {
45
- const ch = source[i];
46
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
47
- i++;
48
- continue;
49
- }
50
- if (isDigit(ch)) {
51
- const start = i;
52
- if (ch === "0" && i + 1 < source.length && (source[i + 1] === "x" || source[i + 1] === "X")) {
53
- i += 2;
54
- while (i < source.length && (isHexDigit(source[i]) || source[i] === "_")) i++;
55
- tokens.push({
56
- type: "Number",
57
- value: source.slice(start, i),
58
- start,
59
- end: i
60
- });
61
- continue;
62
- }
63
- if (ch === "0" && i + 1 < source.length && (source[i + 1] === "b" || source[i + 1] === "B")) {
64
- i += 2;
65
- while (i < source.length && (source[i] === "0" || source[i] === "1" || source[i] === "_")) i++;
66
- tokens.push({
67
- type: "Number",
68
- value: source.slice(start, i),
69
- start,
70
- end: i
71
- });
72
- continue;
73
- }
74
- if (ch === "0" && i + 1 < source.length && (source[i + 1] === "o" || source[i + 1] === "O")) {
75
- i += 2;
76
- while (i < source.length && (source[i] >= "0" && source[i] <= "7" || source[i] === "_")) i++;
77
- tokens.push({
78
- type: "Number",
79
- value: source.slice(start, i),
80
- start,
81
- end: i
82
- });
83
- continue;
84
- }
85
- while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
86
- if (i < source.length && source[i] === "." && i + 1 < source.length && isDigit(source[i + 1])) {
87
- i++;
88
- while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
89
- }
90
- if (i < source.length && (source[i] === "e" || source[i] === "E")) {
91
- i++;
92
- if (i < source.length && (source[i] === "+" || source[i] === "-")) i++;
93
- while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
94
- }
95
- tokens.push({
96
- type: "Number",
97
- value: source.slice(start, i),
98
- start,
99
- end: i
100
- });
101
- continue;
102
- }
103
- if (ch === "\"" || ch === "'") {
104
- const quote = ch;
105
- const start = i;
106
- i++;
107
- let value = "";
108
- while (i < source.length && source[i] !== quote) {
109
- if (source[i] === "\\") {
110
- i++;
111
- const escaped = source[i];
112
- switch (escaped) {
113
- case "n":
114
- value += "\n";
115
- break;
116
- case "t":
117
- value += " ";
118
- break;
119
- case "r":
120
- value += "\r";
121
- break;
122
- case "\\":
123
- value += "\\";
124
- break;
125
- case "0":
126
- value += "\0";
127
- break;
128
- case "x": {
129
- const hex = source.slice(i + 1, i + 3);
130
- if (hex.length < 2 || !isHexDigit(hex[0]) || !isHexDigit(hex[1])) throw new ExpressionError("Invalid \\x escape: expected 2 hex digits", {
131
- source,
132
- start,
133
- end: i + 3
134
- });
135
- value += String.fromCharCode(parseInt(hex, 16));
136
- i += 2;
137
- break;
138
- }
139
- case "u":
140
- if (i + 1 < source.length && source[i + 1] === "{") {
141
- const closeBrace = source.indexOf("}", i + 2);
142
- if (closeBrace === -1) throw new ExpressionError("Invalid \\u{} escape: missing closing brace", {
143
- source,
144
- start,
145
- end: source.length
146
- });
147
- const hex = source.slice(i + 2, closeBrace);
148
- if (hex.length === 0) throw new ExpressionError("Invalid \\u{} escape: empty code point", {
149
- source,
150
- start,
151
- end: closeBrace + 1
152
- });
153
- for (const c of hex) if (!isHexDigit(c)) throw new ExpressionError(`Invalid \\u{} escape: non-hex digit "${c}"`, {
154
- source,
155
- start,
156
- end: closeBrace + 1
157
- });
158
- const MAX_UNICODE_CODE_POINT = 1114111;
159
- const codePoint = parseInt(hex, 16);
160
- if (codePoint > MAX_UNICODE_CODE_POINT) throw new ExpressionError("Invalid \\u{} escape: code point out of range", {
161
- source,
162
- start,
163
- end: closeBrace + 1
164
- });
165
- value += String.fromCodePoint(codePoint);
166
- i = closeBrace;
167
- } else {
168
- const UNICODE_ESCAPE_LEN = 4;
169
- const hex = source.slice(i + 1, i + 1 + UNICODE_ESCAPE_LEN);
170
- if (hex.length < UNICODE_ESCAPE_LEN) throw new ExpressionError("Invalid \\u escape: expected 4 hex digits", {
171
- source,
172
- start,
173
- end: i + 1 + UNICODE_ESCAPE_LEN
174
- });
175
- for (const c of hex) if (!isHexDigit(c)) throw new ExpressionError("Invalid \\u escape: expected 4 hex digits", {
176
- source,
177
- start,
178
- end: i + 1 + UNICODE_ESCAPE_LEN
179
- });
180
- value += String.fromCharCode(parseInt(hex, 16));
181
- i += UNICODE_ESCAPE_LEN;
182
- }
183
- break;
184
- default:
185
- value += escaped;
186
- break;
187
- }
188
- } else value += source[i];
189
- i++;
190
- }
191
- if (i >= source.length) throw new ExpressionError("Unterminated string", {
192
- source,
193
- start,
194
- end: i
195
- });
196
- i++;
197
- tokens.push({
198
- type: "String",
199
- value,
200
- start,
201
- end: i
202
- });
203
- continue;
204
- }
205
- if (ch === "`") {
206
- const start = i;
207
- i++;
208
- let raw = "`";
209
- while (i < source.length && source[i] !== "`") if (source[i] === "$" && i + 1 < source.length && source[i + 1] === "{") {
210
- raw += "${";
211
- i += 2;
212
- let depth = 1;
213
- while (i < source.length && depth > 0) {
214
- const ic = source[i];
215
- if (ic === "\"" || ic === "'") {
216
- raw += ic;
217
- i++;
218
- while (i < source.length && source[i] !== ic) {
219
- if (source[i] === "\\" && i + 1 < source.length) {
220
- raw += source[i] + source[i + 1];
221
- i += 2;
222
- continue;
223
- }
224
- raw += source[i];
225
- i++;
226
- }
227
- if (i < source.length) {
228
- raw += source[i];
229
- i++;
230
- }
231
- continue;
232
- }
233
- if (ic === "{") depth++;
234
- else if (ic === "}") depth--;
235
- if (depth > 0) raw += ic;
236
- i++;
237
- }
238
- raw += "}";
239
- } else {
240
- raw += source[i];
241
- i++;
242
- }
243
- if (i >= source.length) throw new ExpressionError("Unterminated template literal", {
244
- source,
245
- start,
246
- end: i
247
- });
248
- raw += "`";
249
- i++;
250
- tokens.push({
251
- type: "TemplateLiteral",
252
- value: raw,
253
- start,
254
- end: i
255
- });
256
- continue;
257
- }
258
- if (isAlpha(ch)) {
259
- const start = i;
260
- while (i < source.length && isAlphaNumeric(source[i])) i++;
261
- const value = source.slice(start, i);
262
- const kw = KEYWORDS.get(value);
263
- if (kw) tokens.push({
264
- type: kw.type,
265
- value: kw.value,
266
- start,
267
- end: i
268
- });
269
- else tokens.push({
270
- type: "Identifier",
271
- value,
272
- start,
273
- end: i
274
- });
275
- continue;
276
- }
277
- const start = i;
278
- if (ch === "." && i + 2 < source.length && source[i + 1] === "." && source[i + 2] === ".") {
279
- tokens.push({
280
- type: "Spread",
281
- value: "...",
282
- start,
283
- end: i + 3
284
- });
285
- i += 3;
286
- continue;
287
- }
288
- if (ch === ".") {
289
- tokens.push({
290
- type: "Punctuation",
291
- value: ".",
292
- start,
293
- end: i + 1
294
- });
295
- i++;
296
- continue;
297
- }
298
- if (ch === "|") {
299
- if (i + 1 < source.length && source[i + 1] === ">") {
300
- tokens.push({
301
- type: "Pipe",
302
- value: "|>",
303
- start,
304
- end: i + 2
305
- });
306
- i += 2;
307
- continue;
308
- }
309
- if (i + 1 < source.length && source[i + 1] === "|") {
310
- tokens.push({
311
- type: "Operator",
312
- value: "||",
313
- start,
314
- end: i + 2
315
- });
316
- i += 2;
317
- continue;
318
- }
319
- }
320
- if (ch === "&" && i + 1 < source.length && source[i + 1] === "&") {
321
- tokens.push({
322
- type: "Operator",
323
- value: "&&",
324
- start,
325
- end: i + 2
326
- });
327
- i += 2;
328
- continue;
329
- }
330
- if (ch === "=" && i + 1 < source.length && source[i + 1] === "=") {
331
- tokens.push({
332
- type: "Operator",
333
- value: "==",
334
- start,
335
- end: i + 2
336
- });
337
- i += 2;
338
- continue;
339
- }
340
- if (ch === "!" && i + 1 < source.length && source[i + 1] === "=") {
341
- tokens.push({
342
- type: "Operator",
343
- value: "!=",
344
- start,
345
- end: i + 2
346
- });
347
- i += 2;
348
- continue;
349
- }
350
- if (ch === "!") {
351
- tokens.push({
352
- type: "Operator",
353
- value: "!",
354
- start,
355
- end: i + 1
356
- });
357
- i++;
358
- continue;
359
- }
360
- if (ch === "<") {
361
- if (i + 1 < source.length && source[i + 1] === "=") {
362
- tokens.push({
363
- type: "Operator",
364
- value: "<=",
365
- start,
366
- end: i + 2
367
- });
368
- i += 2;
369
- continue;
370
- }
371
- tokens.push({
372
- type: "Operator",
373
- value: "<",
374
- start,
375
- end: i + 1
376
- });
377
- i++;
378
- continue;
379
- }
380
- if (ch === ">") {
381
- if (i + 1 < source.length && source[i + 1] === "=") {
382
- tokens.push({
383
- type: "Operator",
384
- value: ">=",
385
- start,
386
- end: i + 2
387
- });
388
- i += 2;
389
- continue;
390
- }
391
- tokens.push({
392
- type: "Operator",
393
- value: ">",
394
- start,
395
- end: i + 1
396
- });
397
- i++;
398
- continue;
399
- }
400
- if (ch === "*" && i + 1 < source.length && source[i + 1] === "*") {
401
- tokens.push({
402
- type: "Operator",
403
- value: "**",
404
- start,
405
- end: i + 2
406
- });
407
- i += 2;
408
- continue;
409
- }
410
- if (ch === "?") {
411
- if (i + 1 < source.length && source[i + 1] === ".") {
412
- tokens.push({
413
- type: "OptionalChain",
414
- value: "?.",
415
- start,
416
- end: i + 2
417
- });
418
- i += 2;
419
- continue;
420
- }
421
- if (i + 1 < source.length && source[i + 1] === "?") {
422
- tokens.push({
423
- type: "NullishCoalescing",
424
- value: "??",
425
- start,
426
- end: i + 2
427
- });
428
- i += 2;
429
- continue;
430
- }
431
- tokens.push({
432
- type: "Punctuation",
433
- value: "?",
434
- start,
435
- end: i + 1
436
- });
437
- i++;
438
- continue;
439
- }
440
- if (ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%") {
441
- tokens.push({
442
- type: "Operator",
443
- value: ch,
444
- start,
445
- end: i + 1
446
- });
447
- i++;
448
- continue;
449
- }
450
- if ("()[]{},:".includes(ch)) {
451
- tokens.push({
452
- type: "Punctuation",
453
- value: ch,
454
- start,
455
- end: i + 1
456
- });
457
- i++;
458
- continue;
459
- }
460
- throw new ExpressionError(`Unexpected character "${ch}"`, {
461
- source,
462
- start,
463
- end: i + 1
464
- });
465
- }
466
- tokens.push({
467
- type: "EOF",
468
- value: "",
469
- start: i,
470
- end: i
471
- });
472
- return tokens;
473
- }
474
- //#endregion
2
+ import { i as tokenize, n as ExecutionContext, r as SecurityPolicy } from "./execution-context-DqYC3L92.mjs";
475
3
  //#region src/parser.ts
476
4
  const PIPE_PRECEDENCE = 5;
477
5
  const PRECEDENCE = {
@@ -1418,19 +946,24 @@ function evaluate(node, context, transforms, functions, guard, source) {
1418
946
  });
1419
947
  }
1420
948
  function evalNode(node, env) {
1421
- const { ctx, g, s } = env;
949
+ switch (node.type) {
950
+ case "NumberLiteral":
951
+ case "StringLiteral":
952
+ case "BooleanLiteral": return node.value;
953
+ case "NullLiteral": return null;
954
+ case "UndefinedLiteral": return;
955
+ case "Identifier":
956
+ env.g.checkNameAccess(node.name, "identifier");
957
+ return Object.hasOwn(env.ctx, node.name) ? env.ctx[node.name] : void 0;
958
+ }
959
+ return evalCompound(node, env);
960
+ }
961
+ function evalCompound(node, env) {
962
+ const { g, s } = env;
1422
963
  g.enterDepth();
1423
964
  g.step();
1424
965
  try {
1425
966
  switch (node.type) {
1426
- case "NumberLiteral":
1427
- case "StringLiteral":
1428
- case "BooleanLiteral": return node.value;
1429
- case "NullLiteral": return null;
1430
- case "UndefinedLiteral": return;
1431
- case "Identifier":
1432
- g.checkNameAccess(node.name, "identifier");
1433
- return Object.hasOwn(ctx, node.name) ? ctx[node.name] : void 0;
1434
967
  case "UnaryExpression": return applyUnaryOp(node.operator, evalNode(node.operand, env));
1435
968
  case "BinaryExpression": {
1436
969
  const op = node.operator;
@@ -1654,19 +1187,24 @@ async function evaluateAsync(node, context, transforms, functions, guard, source
1654
1187
  return result;
1655
1188
  }
1656
1189
  async function evalNodeAsync(node, env) {
1657
- const { ctx, g, s } = env;
1190
+ switch (node.type) {
1191
+ case "NumberLiteral":
1192
+ case "StringLiteral":
1193
+ case "BooleanLiteral": return node.value;
1194
+ case "NullLiteral": return null;
1195
+ case "UndefinedLiteral": return;
1196
+ case "Identifier":
1197
+ env.g.checkNameAccess(node.name, "identifier");
1198
+ return Object.hasOwn(env.ctx, node.name) ? env.ctx[node.name] : void 0;
1199
+ }
1200
+ return evalCompoundAsync(node, env);
1201
+ }
1202
+ async function evalCompoundAsync(node, env) {
1203
+ const { g, s } = env;
1658
1204
  g.enterDepth();
1659
1205
  g.step();
1660
1206
  try {
1661
1207
  switch (node.type) {
1662
- case "NumberLiteral":
1663
- case "StringLiteral":
1664
- case "BooleanLiteral": return node.value;
1665
- case "NullLiteral": return null;
1666
- case "UndefinedLiteral": return;
1667
- case "Identifier":
1668
- g.checkNameAccess(node.name, "identifier");
1669
- return Object.hasOwn(ctx, node.name) ? ctx[node.name] : void 0;
1670
1208
  case "UnaryExpression": return applyUnaryOp(node.operator, await evalNodeAsync(node.operand, env));
1671
1209
  case "BinaryExpression": {
1672
1210
  const op = node.operator;
@@ -1937,99 +1475,23 @@ function makeLambdaAccessor(property, guard) {
1937
1475
  };
1938
1476
  }
1939
1477
  //#endregion
1940
- //#region src/execution-context.ts
1941
- const BLOCKED_PROPERTIES = new Set([
1942
- "__proto__",
1943
- "constructor",
1944
- "prototype"
1945
- ]);
1946
- const MAX_INDEX_DIGITS = 10;
1947
- function isCanonicalIndex(key) {
1948
- if (key.length === 0 || key.length > MAX_INDEX_DIGITS) return false;
1949
- const n = Number(key);
1950
- return Number.isInteger(n) && n >= 0 && String(n) === key;
1951
- }
1952
- const TIMEOUT_CHECK_INTERVAL = 1e3;
1953
- const DEFAULT_MAX_DEPTH = 100;
1954
- const DEFAULT_MAX_ARRAY_LENGTH = 1e5;
1955
- /** Immutable per-instance security configuration derived from BonsaiOptions. */
1956
- var SecurityPolicy = class {
1957
- maxDepth;
1958
- maxArrayLength;
1959
- timeout;
1960
- allowedProperties;
1961
- deniedProperties;
1962
- constructor(options = {}) {
1963
- this.maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
1964
- this.maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
1965
- this.timeout = options.timeout ?? 0;
1966
- this.allowedProperties = options.allowedProperties ? new Set(options.allowedProperties) : void 0;
1967
- this.deniedProperties = options.deniedProperties ? new Set(options.deniedProperties) : void 0;
1968
- }
1969
- };
1970
- /** Mutable per-evaluation state: tracks depth, step count, and deadline for a single evaluation. */
1971
- var ExecutionContext = class {
1972
- stepCount = 0;
1973
- depth = 0;
1974
- deadline;
1975
- policy;
1976
- now;
1977
- constructor(policy, now = Date.now) {
1978
- this.policy = policy;
1979
- this.now = now;
1980
- this.deadline = policy.timeout ? now() + policy.timeout : 0;
1981
- }
1982
- step() {
1983
- if (this.deadline) {
1984
- this.stepCount++;
1985
- if (this.stepCount % TIMEOUT_CHECK_INTERVAL === 0) this.checkTimeout();
1986
- }
1987
- }
1988
- checkTimeout() {
1989
- if (this.deadline && this.now() >= this.deadline) throw new BonsaiSecurityError("TIMEOUT", `Expression timeout: exceeded ${this.policy.timeout}ms`);
1990
- }
1991
- enterDepth() {
1992
- if (++this.depth > this.policy.maxDepth) throw new BonsaiSecurityError("MAX_DEPTH", `Maximum expression depth (${this.policy.maxDepth}) exceeded`);
1993
- }
1994
- exitDepth() {
1995
- this.depth--;
1996
- }
1997
- withDepth(fn) {
1998
- this.enterDepth();
1999
- try {
2000
- return fn();
2001
- } finally {
2002
- this.exitDepth();
2003
- }
2004
- }
2005
- checkNameAccess(key, kind) {
2006
- if (BLOCKED_PROPERTIES.has(key)) throw new BonsaiSecurityError("BLOCKED_PROPERTY", `Blocked: access to "${key}" is not allowed`);
2007
- if (kind === "identifier" || kind === "object-key") return;
2008
- if (isCanonicalIndex(key)) return;
2009
- if (this.policy.allowedProperties) {
2010
- if (!this.policy.allowedProperties.has(key)) throw new BonsaiSecurityError("PROPERTY_NOT_ALLOWED", `Blocked: "${key}" is not in allowed properties`);
2011
- }
2012
- if (this.policy.deniedProperties) {
2013
- if (this.policy.deniedProperties.has(key)) throw new BonsaiSecurityError("PROPERTY_DENIED", `Blocked: "${key}" is in denied properties`);
2014
- }
2015
- }
2016
- checkArrayLength(length) {
2017
- if (length > this.policy.maxArrayLength) throw new BonsaiSecurityError("MAX_ARRAY_LENGTH", `Array length (${length}) exceeds maximum (${this.policy.maxArrayLength})`);
2018
- }
2019
- };
2020
- //#endregion
2021
1478
  //#region src/cache.ts
2022
1479
  var LRUCache = class {
2023
1480
  max;
2024
1481
  map = /* @__PURE__ */ new Map();
1482
+ lastKey;
1483
+ lastValue;
2025
1484
  constructor(max) {
2026
1485
  this.max = max > 0 ? max : 0;
2027
1486
  }
2028
1487
  get(key) {
1488
+ if (key === this.lastKey) return this.lastValue;
2029
1489
  if (!this.map.has(key)) return void 0;
2030
1490
  const value = this.map.get(key);
2031
1491
  this.map.delete(key);
2032
1492
  this.map.set(key, value);
1493
+ this.lastKey = key;
1494
+ this.lastValue = value;
2033
1495
  return value;
2034
1496
  }
2035
1497
  set(key, value) {
@@ -2038,14 +1500,22 @@ var LRUCache = class {
2038
1500
  else if (this.map.size >= this.max) {
2039
1501
  const oldest = this.map.keys().next().value;
2040
1502
  this.map.delete(oldest);
1503
+ if (this.lastKey === oldest) {
1504
+ this.lastKey = void 0;
1505
+ this.lastValue = void 0;
1506
+ }
2041
1507
  }
2042
1508
  this.map.set(key, value);
1509
+ this.lastKey = key;
1510
+ this.lastValue = value;
2043
1511
  }
2044
1512
  get size() {
2045
1513
  return this.map.size;
2046
1514
  }
2047
1515
  clear() {
2048
1516
  this.map.clear();
1517
+ this.lastKey = void 0;
1518
+ this.lastValue = void 0;
2049
1519
  }
2050
1520
  };
2051
1521
  //#endregion
@@ -2136,6 +1606,8 @@ function bonsai(options = {}) {
2136
1606
  const cache = new LRUCache(options.cacheSize ?? DEFAULT_CACHE_SIZE);
2137
1607
  const astCache = new LRUCache(options.cacheSize ?? DEFAULT_CACHE_SIZE);
2138
1608
  const policy = new SecurityPolicy(options);
1609
+ const syncCtx = new ExecutionContext(policy);
1610
+ let syncCtxInUse = false;
2139
1611
  function createExecutionContext() {
2140
1612
  return new ExecutionContext(policy);
2141
1613
  }
@@ -2157,7 +1629,14 @@ function bonsai(options = {}) {
2157
1629
  return evaluateAsync(optimized, context, registry.transforms, registry.functions, createExecutionContext(), source);
2158
1630
  },
2159
1631
  evaluateSync(context = {}) {
2160
- return evaluate(optimized, context, registry.transforms, registry.functions, createExecutionContext(), source);
1632
+ if (syncCtxInUse) return evaluate(optimized, context, registry.transforms, registry.functions, createExecutionContext(), source);
1633
+ syncCtxInUse = true;
1634
+ try {
1635
+ syncCtx.reset();
1636
+ return evaluate(optimized, context, registry.transforms, registry.functions, syncCtx, source);
1637
+ } finally {
1638
+ syncCtxInUse = false;
1639
+ }
2161
1640
  }
2162
1641
  };
2163
1642
  cache.set(source, compiled);
@@ -2194,6 +1673,12 @@ function bonsai(options = {}) {
2194
1673
  listFunctions() {
2195
1674
  return registry.getFunctionNames();
2196
1675
  },
1676
+ getPolicy() {
1677
+ return {
1678
+ ...policy.allowedProperties ? { allowedProperties: [...policy.allowedProperties] } : {},
1679
+ ...policy.deniedProperties ? { deniedProperties: [...policy.deniedProperties] } : {}
1680
+ };
1681
+ },
2197
1682
  clearCache() {
2198
1683
  cache.clear();
2199
1684
  astCache.clear();
@@ -2205,7 +1690,15 @@ function bonsai(options = {}) {
2205
1690
  return evaluateAsync(getAst(expression), context, registry.transforms, registry.functions, createExecutionContext(), expression);
2206
1691
  },
2207
1692
  evaluateSync(expression, context = {}) {
2208
- return evaluate(getAst(expression), context, registry.transforms, registry.functions, createExecutionContext(), expression);
1693
+ const ast = getAst(expression);
1694
+ if (syncCtxInUse) return evaluate(ast, context, registry.transforms, registry.functions, createExecutionContext(), expression);
1695
+ syncCtxInUse = true;
1696
+ try {
1697
+ syncCtx.reset();
1698
+ return evaluate(ast, context, registry.transforms, registry.functions, syncCtx, expression);
1699
+ } finally {
1700
+ syncCtxInUse = false;
1701
+ }
2209
1702
  },
2210
1703
  validate(expression) {
2211
1704
  try {