bonsai-js 0.3.0 → 0.5.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.
@@ -1,4 +1,4 @@
1
- import { i as ExpressionError, n as BonsaiSecurityError } from "./errors-t5i9AEaf.mjs";
1
+ import { i as ExpressionError, n as BonsaiSecurityError } from "./errors-CCZDd1gK.mjs";
2
2
  //#region src/lexer.ts
3
3
  const KEYWORDS = new Map([
4
4
  ["true", {
@@ -38,6 +38,19 @@ function isAlphaNumeric(ch) {
38
38
  function isHexDigit(ch) {
39
39
  return isDigit(ch) || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
40
40
  }
41
+ const NUMERIC_SEPARATOR_PATTERNS = {
42
+ hex: /^0[xX][0-9a-fA-F]+(?:_[0-9a-fA-F]+)*$/u,
43
+ bin: /^0[bB][01]+(?:_[01]+)*$/u,
44
+ oct: /^0[oO][0-7]+(?:_[0-7]+)*$/u,
45
+ dec: /^\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?$/u
46
+ };
47
+ function checkNumericSeparators(value, kind, source, start, end) {
48
+ if (!NUMERIC_SEPARATOR_PATTERNS[kind].test(value)) throw new ExpressionError(`Invalid numeric literal "${value}": "_" separators must sit between digits`, {
49
+ source,
50
+ start,
51
+ end
52
+ });
53
+ }
41
54
  function tokenize(source) {
42
55
  const tokens = [];
43
56
  let i = 0;
@@ -51,35 +64,62 @@ function tokenize(source) {
51
64
  const start = i;
52
65
  if (ch === "0" && i + 1 < source.length && (source[i + 1] === "x" || source[i + 1] === "X")) {
53
66
  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),
67
+ if (i >= source.length || !isHexDigit(source[i])) throw new ExpressionError("Invalid hexadecimal literal: expected at least one digit after \"0x\"", {
68
+ source,
58
69
  start,
59
70
  end: i
60
71
  });
72
+ while (i < source.length && (isHexDigit(source[i]) || source[i] === "_")) i++;
73
+ {
74
+ const value = source.slice(start, i);
75
+ checkNumericSeparators(value, "hex", source, start, i);
76
+ tokens.push({
77
+ type: "Number",
78
+ value,
79
+ start,
80
+ end: i
81
+ });
82
+ }
61
83
  continue;
62
84
  }
63
85
  if (ch === "0" && i + 1 < source.length && (source[i + 1] === "b" || source[i + 1] === "B")) {
64
86
  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),
87
+ if (i >= source.length || source[i] !== "0" && source[i] !== "1") throw new ExpressionError("Invalid binary literal: expected at least one digit after \"0b\"", {
88
+ source,
69
89
  start,
70
90
  end: i
71
91
  });
92
+ while (i < source.length && (source[i] === "0" || source[i] === "1" || source[i] === "_")) i++;
93
+ {
94
+ const value = source.slice(start, i);
95
+ checkNumericSeparators(value, "bin", source, start, i);
96
+ tokens.push({
97
+ type: "Number",
98
+ value,
99
+ start,
100
+ end: i
101
+ });
102
+ }
72
103
  continue;
73
104
  }
74
105
  if (ch === "0" && i + 1 < source.length && (source[i + 1] === "o" || source[i + 1] === "O")) {
75
106
  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),
107
+ if (i >= source.length || source[i] < "0" || source[i] > "7") throw new ExpressionError("Invalid octal literal: expected at least one digit after \"0o\"", {
108
+ source,
80
109
  start,
81
110
  end: i
82
111
  });
112
+ while (i < source.length && (source[i] >= "0" && source[i] <= "7" || source[i] === "_")) i++;
113
+ {
114
+ const value = source.slice(start, i);
115
+ checkNumericSeparators(value, "oct", source, start, i);
116
+ tokens.push({
117
+ type: "Number",
118
+ value,
119
+ start,
120
+ end: i
121
+ });
122
+ }
83
123
  continue;
84
124
  }
85
125
  while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
@@ -90,11 +130,18 @@ function tokenize(source) {
90
130
  if (i < source.length && (source[i] === "e" || source[i] === "E")) {
91
131
  i++;
92
132
  if (i < source.length && (source[i] === "+" || source[i] === "-")) i++;
133
+ if (i >= source.length || !isDigit(source[i])) throw new ExpressionError("Invalid number: exponent has no digits", {
134
+ source,
135
+ start,
136
+ end: i
137
+ });
93
138
  while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
94
139
  }
140
+ const value = source.slice(start, i);
141
+ checkNumericSeparators(value, "dec", source, start, i);
95
142
  tokens.push({
96
143
  type: "Number",
97
- value: source.slice(start, i),
144
+ value,
98
145
  start,
99
146
  end: i
100
147
  });
@@ -230,6 +277,24 @@ function tokenize(source) {
230
277
  }
231
278
  continue;
232
279
  }
280
+ if (ic === "`") {
281
+ raw += ic;
282
+ i++;
283
+ while (i < source.length && source[i] !== "`") {
284
+ if (source[i] === "\\" && i + 1 < source.length) {
285
+ raw += source[i] + source[i + 1];
286
+ i += 2;
287
+ continue;
288
+ }
289
+ raw += source[i];
290
+ i++;
291
+ }
292
+ if (i < source.length) {
293
+ raw += source[i];
294
+ i++;
295
+ }
296
+ continue;
297
+ }
233
298
  if (ic === "{") depth++;
234
299
  else if (ic === "}") depth--;
235
300
  if (depth > 0) raw += ic;
@@ -472,6 +537,66 @@ function tokenize(source) {
472
537
  return tokens;
473
538
  }
474
539
  //#endregion
540
+ //#region src/safe-methods.ts
541
+ /** Method name -> the receiver types it is permitted on. */
542
+ const SAFE_METHODS = {
543
+ startsWith: ["string"],
544
+ endsWith: ["string"],
545
+ substring: ["string"],
546
+ charAt: ["string"],
547
+ charCodeAt: ["string"],
548
+ repeat: ["string"],
549
+ trim: ["string"],
550
+ trimStart: ["string"],
551
+ trimEnd: ["string"],
552
+ toLowerCase: ["string"],
553
+ toUpperCase: ["string"],
554
+ replace: ["string"],
555
+ replaceAll: ["string"],
556
+ padStart: ["string"],
557
+ padEnd: ["string"],
558
+ split: ["string"],
559
+ includes: ["string", "array"],
560
+ indexOf: ["string", "array"],
561
+ lastIndexOf: ["string", "array"],
562
+ slice: ["string", "array"],
563
+ at: ["string", "array"],
564
+ concat: ["string", "array"],
565
+ filter: ["array"],
566
+ map: ["array"],
567
+ find: ["array"],
568
+ findIndex: ["array"],
569
+ some: ["array"],
570
+ every: ["array"],
571
+ flatMap: ["array"],
572
+ join: ["array"],
573
+ flat: ["array"],
574
+ toReversed: ["array"],
575
+ toSorted: ["array"],
576
+ toSpliced: ["array"],
577
+ with: ["array"],
578
+ toFixed: ["number"],
579
+ toString: ["number", "string"]
580
+ };
581
+ /** Map a runtime value to its method-receiver type, or undefined if it has none. */
582
+ function receiverTypeOf(obj) {
583
+ const t = typeof obj;
584
+ if (t === "string") return "string";
585
+ if (t === "number") return "number";
586
+ if (Array.isArray(obj)) return "array";
587
+ }
588
+ /** Whether `method` is a safe built-in method for the runtime type of `obj`. */
589
+ function isMethodAllowedOn(obj, method) {
590
+ const receiver = receiverTypeOf(obj);
591
+ if (receiver === void 0) return false;
592
+ const allowed = SAFE_METHODS[method];
593
+ return allowed !== void 0 && allowed.includes(receiver);
594
+ }
595
+ /** All safe method names available on a given receiver type. */
596
+ function methodsForReceiverType(type) {
597
+ return Object.keys(SAFE_METHODS).filter((method) => SAFE_METHODS[method].includes(type));
598
+ }
599
+ //#endregion
475
600
  //#region src/execution-context.ts
476
601
  const BLOCKED_PROPERTIES = new Set([
477
602
  "__proto__",
@@ -487,16 +612,19 @@ function isCanonicalIndex(key) {
487
612
  const TIMEOUT_CHECK_INTERVAL = 1e3;
488
613
  const DEFAULT_MAX_DEPTH = 100;
489
614
  const DEFAULT_MAX_ARRAY_LENGTH = 1e5;
615
+ const DEFAULT_MAX_STRING_LENGTH = 1e5;
490
616
  /** Immutable per-instance security configuration derived from BonsaiOptions. */
491
617
  var SecurityPolicy = class {
492
618
  maxDepth;
493
619
  maxArrayLength;
620
+ maxStringLength;
494
621
  timeout;
495
622
  allowedProperties;
496
623
  deniedProperties;
497
624
  constructor(options = {}) {
498
625
  this.maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
499
626
  this.maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
627
+ this.maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;
500
628
  this.timeout = options.timeout ?? 0;
501
629
  this.allowedProperties = options.allowedProperties ? new Set(options.allowedProperties) : void 0;
502
630
  this.deniedProperties = options.deniedProperties ? new Set(options.deniedProperties) : void 0;
@@ -557,8 +685,11 @@ var ExecutionContext = class {
557
685
  checkArrayLength(length) {
558
686
  if (length > this.policy.maxArrayLength) throw new BonsaiSecurityError("MAX_ARRAY_LENGTH", `Array length (${length}) exceeds maximum (${this.policy.maxArrayLength})`);
559
687
  }
688
+ checkStringLength(length) {
689
+ if (length > this.policy.maxStringLength) throw new BonsaiSecurityError("MAX_STRING_LENGTH", `String length (${length}) exceeds maximum (${this.policy.maxStringLength})`);
690
+ }
560
691
  };
561
692
  //#endregion
562
- export { tokenize as i, ExecutionContext as n, SecurityPolicy as r, BLOCKED_PROPERTIES as t };
693
+ export { methodsForReceiverType as a, isMethodAllowedOn as i, ExecutionContext as n, tokenize as o, SecurityPolicy as r, BLOCKED_PROPERTIES as t };
563
694
 
564
- //# sourceMappingURL=execution-context-DqYC3L92.mjs.map
695
+ //# sourceMappingURL=execution-context-b6FBfEdv.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-context-b6FBfEdv.mjs","names":[],"sources":["../src/lexer.ts","../src/safe-methods.ts","../src/execution-context.ts"],"sourcesContent":["import type { Token, PunctuationValue } from './types.js'\nimport { ExpressionError } from './errors.js'\n\nconst KEYWORDS = new Map<string, { type: Token['type']; value: string }>([\n ['true', { type: 'Boolean', value: 'true' }],\n ['false', { type: 'Boolean', value: 'false' }],\n ['null', { type: 'Null', value: 'null' }],\n ['undefined', { type: 'Undefined', value: 'undefined' }],\n ['in', { type: 'Operator', value: 'in' }],\n ['not', { type: 'Operator', value: 'not' }],\n])\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction isAlpha(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_' || ch === '$'\n}\n\nfunction isAlphaNumeric(ch: string): boolean {\n return isAlpha(ch) || isDigit(ch)\n}\n\nfunction isHexDigit(ch: string): boolean {\n return isDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')\n}\n\n// A numeric separator `_` is only legal between two digits of the same radix.\n// The digit-consuming loops below accept `_` freely, so validate the produced\n// literal here to reject leading, trailing, or doubled separators (`1_`,\n// `1__0`, `0xff_`), which JS rejects and which Number() would otherwise coerce\n// silently (e.g. Number('0xff_') === 255, Number('1_') === NaN).\nconst NUMERIC_SEPARATOR_PATTERNS = {\n hex: /^0[xX][0-9a-fA-F]+(?:_[0-9a-fA-F]+)*$/u,\n bin: /^0[bB][01]+(?:_[01]+)*$/u,\n oct: /^0[oO][0-7]+(?:_[0-7]+)*$/u,\n dec: /^\\d+(?:_\\d+)*(?:\\.\\d+(?:_\\d+)*)?(?:[eE][+-]?\\d+(?:_\\d+)*)?$/u,\n} as const\n\nfunction checkNumericSeparators(\n value: string,\n kind: keyof typeof NUMERIC_SEPARATOR_PATTERNS,\n source: string,\n start: number,\n end: number,\n): void {\n if (!NUMERIC_SEPARATOR_PATTERNS[kind].test(value)) {\n throw new ExpressionError(\n `Invalid numeric literal \"${value}\": \"_\" separators must sit between digits`,\n { source, start, end },\n )\n }\n}\n\nexport function tokenize(source: string): Token[] {\n const tokens: Token[] = []\n let i = 0\n\n while (i < source.length) {\n const ch = source[i]\n\n // Skip whitespace\n if (ch === ' ' || ch === '\\t' || ch === '\\n' || ch === '\\r') {\n i++\n continue\n }\n\n // Numbers: decimal, hex, binary, octal, scientific, underscores\n if (isDigit(ch)) {\n const start = i\n\n // Hex: 0x...\n if (ch === '0' && i + 1 < source.length && (source[i + 1] === 'x' || source[i + 1] === 'X')) {\n i += 2\n if (i >= source.length || !isHexDigit(source[i])) {\n throw new ExpressionError('Invalid hexadecimal literal: expected at least one digit after \"0x\"', { source, start, end: i })\n }\n while (i < source.length && (isHexDigit(source[i]) || source[i] === '_')) i++\n {\n const value = source.slice(start, i)\n checkNumericSeparators(value, 'hex', source, start, i)\n tokens.push({ type: 'Number', value, start, end: i })\n }\n continue\n }\n\n // Binary: 0b...\n if (ch === '0' && i + 1 < source.length && (source[i + 1] === 'b' || source[i + 1] === 'B')) {\n i += 2\n if (i >= source.length || (source[i] !== '0' && source[i] !== '1')) {\n throw new ExpressionError('Invalid binary literal: expected at least one digit after \"0b\"', { source, start, end: i })\n }\n while (i < source.length && (source[i] === '0' || source[i] === '1' || source[i] === '_')) i++\n {\n const value = source.slice(start, i)\n checkNumericSeparators(value, 'bin', source, start, i)\n tokens.push({ type: 'Number', value, start, end: i })\n }\n continue\n }\n\n // Octal: 0o...\n if (ch === '0' && i + 1 < source.length && (source[i + 1] === 'o' || source[i + 1] === 'O')) {\n i += 2\n if (i >= source.length || source[i] < '0' || source[i] > '7') {\n throw new ExpressionError('Invalid octal literal: expected at least one digit after \"0o\"', { source, start, end: i })\n }\n while (i < source.length && ((source[i] >= '0' && source[i] <= '7') || source[i] === '_')) i++\n {\n const value = source.slice(start, i)\n checkNumericSeparators(value, 'oct', source, start, i)\n tokens.push({ type: 'Number', value, start, end: i })\n }\n continue\n }\n\n // Decimal (with optional underscore separators)\n while (i < source.length && (isDigit(source[i]) || source[i] === '_')) i++\n if (i < source.length && source[i] === '.' && i + 1 < source.length && isDigit(source[i + 1])) {\n i++ // skip '.'\n while (i < source.length && (isDigit(source[i]) || source[i] === '_')) i++\n }\n\n // Scientific notation: e/E followed by optional +/- and at least one digit\n if (i < source.length && (source[i] === 'e' || source[i] === 'E')) {\n i++\n if (i < source.length && (source[i] === '+' || source[i] === '-')) i++\n if (i >= source.length || !isDigit(source[i])) {\n throw new ExpressionError('Invalid number: exponent has no digits', { source, start, end: i })\n }\n while (i < source.length && (isDigit(source[i]) || source[i] === '_')) i++\n }\n\n const value = source.slice(start, i)\n checkNumericSeparators(value, 'dec', source, start, i)\n tokens.push({ type: 'Number', value, start, end: i })\n continue\n }\n\n // Strings\n if (ch === '\"' || ch === \"'\") {\n const quote = ch\n const start = i\n i++ // skip opening quote\n let value = ''\n while (i < source.length && source[i] !== quote) {\n if (source[i] === '\\\\') {\n i++\n const escaped = source[i]\n switch (escaped) {\n case 'n': value += '\\n'; break\n case 't': value += '\\t'; break\n case 'r': value += '\\r'; break\n case '\\\\': value += '\\\\'; break\n case '0': value += '\\0'; break\n case 'x': {\n const hex = source.slice(i + 1, i + 3)\n if (hex.length < 2 || !isHexDigit(hex[0]) || !isHexDigit(hex[1])) {\n throw new ExpressionError('Invalid \\\\x escape: expected 2 hex digits', { source, start, end: i + 3 })\n }\n value += String.fromCharCode(parseInt(hex, 16))\n i += 2\n break\n }\n case 'u': {\n if (i + 1 < source.length && source[i + 1] === '{') {\n const closeBrace = source.indexOf('}', i + 2)\n if (closeBrace === -1) {\n throw new ExpressionError('Invalid \\\\u{} escape: missing closing brace', { source, start, end: source.length })\n }\n const hex = source.slice(i + 2, closeBrace)\n if (hex.length === 0) {\n throw new ExpressionError('Invalid \\\\u{} escape: empty code point', { source, start, end: closeBrace + 1 })\n }\n for (const c of hex) {\n if (!isHexDigit(c)) {\n throw new ExpressionError(`Invalid \\\\u{} escape: non-hex digit \"${c}\"`, { source, start, end: closeBrace + 1 })\n }\n }\n const MAX_UNICODE_CODE_POINT = 0x10FFFF\n const codePoint = parseInt(hex, 16)\n if (codePoint > MAX_UNICODE_CODE_POINT) {\n throw new ExpressionError('Invalid \\\\u{} escape: code point out of range', { source, start, end: closeBrace + 1 })\n }\n value += String.fromCodePoint(codePoint)\n i = closeBrace\n } else {\n const UNICODE_ESCAPE_LEN = 4\n const hex = source.slice(i + 1, i + 1 + UNICODE_ESCAPE_LEN)\n if (hex.length < UNICODE_ESCAPE_LEN) {\n throw new ExpressionError('Invalid \\\\u escape: expected 4 hex digits', { source, start, end: i + 1 + UNICODE_ESCAPE_LEN })\n }\n for (const c of hex) {\n if (!isHexDigit(c)) {\n throw new ExpressionError('Invalid \\\\u escape: expected 4 hex digits', { source, start, end: i + 1 + UNICODE_ESCAPE_LEN })\n }\n }\n value += String.fromCharCode(parseInt(hex, 16))\n i += UNICODE_ESCAPE_LEN\n }\n break\n }\n default: value += escaped; break\n }\n } else {\n value += source[i]\n }\n i++\n }\n if (i >= source.length) {\n throw new ExpressionError('Unterminated string', { source, start, end: i })\n }\n i++ // skip closing quote\n tokens.push({ type: 'String', value, start, end: i })\n continue\n }\n\n // Template literals\n if (ch === '`') {\n const start = i\n i++ // skip opening backtick\n let raw = '`'\n while (i < source.length && source[i] !== '`') {\n if (source[i] === '$' && i + 1 < source.length && source[i + 1] === '{') {\n raw += '${'\n i += 2\n let depth = 1\n while (i < source.length && depth > 0) {\n const ic = source[i]\n // Skip string literals inside interpolations\n if (ic === '\"' || ic === \"'\") {\n raw += ic\n i++\n while (i < source.length && source[i] !== ic) {\n if (source[i] === '\\\\' && i + 1 < source.length) {\n raw += source[i] + source[i + 1]\n i += 2\n continue\n }\n raw += source[i]\n i++\n }\n if (i < source.length) {\n raw += source[i] // closing quote\n i++\n }\n continue\n }\n // Skip nested template literals so their raw braces (and the braces\n // of their own interpolations) do not throw off this brace count.\n // The nested template is preserved verbatim and re-parsed later.\n if (ic === '`') {\n raw += ic\n i++\n while (i < source.length && source[i] !== '`') {\n if (source[i] === '\\\\' && i + 1 < source.length) {\n raw += source[i] + source[i + 1]\n i += 2\n continue\n }\n raw += source[i]\n i++\n }\n if (i < source.length) {\n raw += source[i] // closing backtick\n i++\n }\n continue\n }\n if (ic === '{') depth++\n else if (ic === '}') depth--\n if (depth > 0) raw += ic\n i++\n }\n raw += '}'\n } else {\n raw += source[i]\n i++\n }\n }\n if (i >= source.length) {\n throw new ExpressionError('Unterminated template literal', { source, start, end: i })\n }\n raw += '`'\n i++ // skip closing backtick\n tokens.push({ type: 'TemplateLiteral', value: raw, start, end: i })\n continue\n }\n\n // Identifiers and keywords\n if (isAlpha(ch)) {\n const start = i\n while (i < source.length && isAlphaNumeric(source[i])) i++\n const value = source.slice(start, i)\n const kw = KEYWORDS.get(value)\n if (kw) {\n tokens.push({ type: kw.type, value: kw.value, start, end: i } as Token)\n } else {\n tokens.push({ type: 'Identifier', value, start, end: i })\n }\n continue\n }\n\n // Multi-character operators and punctuation\n const start = i\n\n // Spread operator ...\n if (ch === '.' && i + 2 < source.length && source[i + 1] === '.' && source[i + 2] === '.') {\n tokens.push({ type: 'Spread', value: '...', start, end: i + 3 })\n i += 3\n continue\n }\n\n // Dot (punctuation)\n if (ch === '.') {\n tokens.push({ type: 'Punctuation', value: '.', start, end: i + 1 })\n i++\n continue\n }\n\n // Pipe |> or logical ||\n if (ch === '|') {\n if (i + 1 < source.length && source[i + 1] === '>') {\n tokens.push({ type: 'Pipe', value: '|>', start, end: i + 2 })\n i += 2\n continue\n }\n if (i + 1 < source.length && source[i + 1] === '|') {\n tokens.push({ type: 'Operator', value: '||', start, end: i + 2 })\n i += 2\n continue\n }\n }\n\n // &&\n if (ch === '&' && i + 1 < source.length && source[i + 1] === '&') {\n tokens.push({ type: 'Operator', value: '&&', start, end: i + 2 })\n i += 2\n continue\n }\n\n // == !=\n if (ch === '=' && i + 1 < source.length && source[i + 1] === '=') {\n tokens.push({ type: 'Operator', value: '==', start, end: i + 2 })\n i += 2\n continue\n }\n if (ch === '!' && i + 1 < source.length && source[i + 1] === '=') {\n tokens.push({ type: 'Operator', value: '!=', start, end: i + 2 })\n i += 2\n continue\n }\n\n // ! (logical not) - must come after != check\n if (ch === '!') {\n tokens.push({ type: 'Operator', value: '!', start, end: i + 1 })\n i++\n continue\n }\n\n // <= >= < >\n if (ch === '<') {\n if (i + 1 < source.length && source[i + 1] === '=') {\n tokens.push({ type: 'Operator', value: '<=', start, end: i + 2 })\n i += 2\n continue\n }\n tokens.push({ type: 'Operator', value: '<', start, end: i + 1 })\n i++\n continue\n }\n if (ch === '>') {\n if (i + 1 < source.length && source[i + 1] === '=') {\n tokens.push({ type: 'Operator', value: '>=', start, end: i + 2 })\n i += 2\n continue\n }\n tokens.push({ type: 'Operator', value: '>', start, end: i + 1 })\n i++\n continue\n }\n\n // ** (exponentiation) — must come before single *\n if (ch === '*' && i + 1 < source.length && source[i + 1] === '*') {\n tokens.push({ type: 'Operator', value: '**', start, end: i + 2 })\n i += 2\n continue\n }\n\n // ?. (optional chaining) or ?? (nullish coalescing) or ? (ternary)\n if (ch === '?') {\n if (i + 1 < source.length && source[i + 1] === '.') {\n tokens.push({ type: 'OptionalChain', value: '?.', start, end: i + 2 })\n i += 2\n continue\n }\n if (i + 1 < source.length && source[i + 1] === '?') {\n tokens.push({ type: 'NullishCoalescing', value: '??', start, end: i + 2 })\n i += 2\n continue\n }\n tokens.push({ type: 'Punctuation', value: '?', start, end: i + 1 })\n i++\n continue\n }\n\n // Single-character operators\n if (ch === '+' || ch === '-' || ch === '*' || ch === '/' || ch === '%') {\n tokens.push({ type: 'Operator', value: ch, start, end: i + 1 })\n i++\n continue\n }\n\n // Punctuation\n if ('()[]{},:'.includes(ch)) {\n tokens.push({ type: 'Punctuation', value: ch as PunctuationValue, start, end: i + 1 })\n i++\n continue\n }\n\n throw new ExpressionError(`Unexpected character \"${ch}\"`, { source, start, end: i + 1 })\n }\n\n tokens.push({ type: 'EOF', value: '', start: i, end: i })\n return tokens\n}\n","/**\n * Single source of truth for the safe built-in method allowlist.\n *\n * Both the runtime enforcement (`isAllowedReceiver` in eval-ops.ts) and the\n * autocomplete catalog (`METHODS_BY_TYPE` in autocomplete/catalog.ts) derive\n * from this table, so a method can never be suggested but rejected (or vice\n * versa). Keep this list intentionally small and audited: every entry is a\n * non-mutating, callback-or-primitive method that is safe to call on untrusted\n * data. `tests/autocomplete/catalog-sync.test.ts` enforces that the two views\n * stay aligned.\n */\n\n/** Receiver types that have a method allowlist. */\nexport type MethodReceiverType = 'string' | 'array' | 'number'\n\n// `as const` keeps each entry precisely typed; the exported view below adds a\n// string index signature so methods can be looked up by an arbitrary name.\nconst SAFE_METHODS_TABLE = {\n // String-only\n startsWith: ['string'],\n endsWith: ['string'],\n substring: ['string'],\n charAt: ['string'],\n charCodeAt: ['string'],\n repeat: ['string'],\n trim: ['string'],\n trimStart: ['string'],\n trimEnd: ['string'],\n toLowerCase: ['string'],\n toUpperCase: ['string'],\n replace: ['string'],\n replaceAll: ['string'],\n padStart: ['string'],\n padEnd: ['string'],\n split: ['string'],\n // String + Array\n includes: ['string', 'array'],\n indexOf: ['string', 'array'],\n lastIndexOf: ['string', 'array'],\n slice: ['string', 'array'],\n at: ['string', 'array'],\n concat: ['string', 'array'],\n // Array-only (higher-order)\n filter: ['array'],\n map: ['array'],\n find: ['array'],\n findIndex: ['array'],\n some: ['array'],\n every: ['array'],\n flatMap: ['array'],\n // Array-only (non-callback, non-mutating)\n join: ['array'],\n flat: ['array'],\n toReversed: ['array'],\n toSorted: ['array'],\n toSpliced: ['array'],\n with: ['array'],\n // Number\n toFixed: ['number'],\n // Number + String\n toString: ['number', 'string'],\n} as const\n\n/** Method name -> the receiver types it is permitted on. */\nconst SAFE_METHODS: Readonly<Record<string, readonly MethodReceiverType[]>> = SAFE_METHODS_TABLE\n\n/** Map a runtime value to its method-receiver type, or undefined if it has none. */\nfunction receiverTypeOf(obj: unknown): MethodReceiverType | undefined {\n const t = typeof obj\n if (t === 'string') return 'string'\n if (t === 'number') return 'number'\n if (Array.isArray(obj)) return 'array'\n return undefined\n}\n\n/** Whether `method` is a safe built-in method for the runtime type of `obj`. */\nexport function isMethodAllowedOn(obj: unknown, method: string): boolean {\n const receiver = receiverTypeOf(obj)\n if (receiver === undefined) return false\n const allowed = SAFE_METHODS[method]\n return allowed !== undefined && allowed.includes(receiver)\n}\n\n/** All safe method names available on a given receiver type. */\nexport function methodsForReceiverType(type: MethodReceiverType): string[] {\n return Object.keys(SAFE_METHODS).filter(method => SAFE_METHODS[method].includes(type))\n}\n","import type { BonsaiOptions } from './types.js'\nimport { BonsaiSecurityError } from './errors.js'\n\n/**\n * Discriminates how a property name is being accessed so security checks can\n * apply different rules (e.g. root identifiers bypass allow/deny lists).\n */\nexport type AccessKind = 'identifier' | 'member' | 'method' | 'object-key'\n\nexport const BLOCKED_PROPERTIES: ReadonlySet<string> = new Set([\n '__proto__', 'constructor', 'prototype',\n])\n\nconst MAX_INDEX_DIGITS = 10\n\nfunction isCanonicalIndex(key: string): boolean {\n if (key.length === 0 || key.length > MAX_INDEX_DIGITS) return false\n const n = Number(key)\n return Number.isInteger(n) && n >= 0 && String(n) === key\n}\n\nconst TIMEOUT_CHECK_INTERVAL = 1000\nconst DEFAULT_MAX_DEPTH = 100\nconst DEFAULT_MAX_ARRAY_LENGTH = 100_000\nconst DEFAULT_MAX_STRING_LENGTH = 100_000\n\n/** Immutable per-instance security configuration derived from BonsaiOptions. */\nexport class SecurityPolicy {\n readonly maxDepth: number\n readonly maxArrayLength: number\n readonly maxStringLength: number\n readonly timeout: number\n readonly allowedProperties?: ReadonlySet<string>\n readonly deniedProperties?: ReadonlySet<string>\n\n constructor(options: BonsaiOptions = {}) {\n this.maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH\n this.maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH\n this.maxStringLength = options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH\n this.timeout = options.timeout ?? 0\n this.allowedProperties = options.allowedProperties\n ? new Set(options.allowedProperties)\n : undefined\n this.deniedProperties = options.deniedProperties\n ? new Set(options.deniedProperties)\n : undefined\n }\n}\n\n/** Mutable per-evaluation state: tracks depth, step count, and deadline for a single evaluation. */\nexport class ExecutionContext {\n private stepCount = 0\n private depth = 0\n private deadline: number\n readonly policy: SecurityPolicy\n private readonly now: () => number\n\n constructor(policy: SecurityPolicy, now: () => number = Date.now) {\n this.policy = policy\n this.now = now\n this.deadline = policy.timeout ? now() + policy.timeout : 0\n }\n\n /** Reset mutable state for reuse. Avoids allocating a new instance per evaluation. */\n reset(): void {\n this.stepCount = 0\n this.depth = 0\n this.deadline = this.policy.timeout ? this.now() + this.policy.timeout : 0\n }\n\n step(): void {\n if (this.deadline) {\n this.stepCount++\n if (this.stepCount % TIMEOUT_CHECK_INTERVAL === 0) {\n this.checkTimeout()\n }\n }\n }\n\n checkTimeout(): void {\n if (this.deadline && this.now() >= this.deadline) {\n throw new BonsaiSecurityError(\n 'TIMEOUT',\n `Expression timeout: exceeded ${this.policy.timeout}ms`,\n )\n }\n }\n\n enterDepth(): void {\n if (++this.depth > this.policy.maxDepth) {\n throw new BonsaiSecurityError(\n 'MAX_DEPTH',\n `Maximum expression depth (${this.policy.maxDepth}) exceeded`,\n )\n }\n }\n\n exitDepth(): void {\n this.depth--\n }\n\n withDepth<T>(fn: () => T): T {\n this.enterDepth()\n try {\n return fn()\n } finally {\n this.exitDepth()\n }\n }\n\n checkNameAccess(key: string, kind: AccessKind): void {\n if (BLOCKED_PROPERTIES.has(key)) {\n throw new BonsaiSecurityError(\n 'BLOCKED_PROPERTY',\n `Blocked: access to \"${key}\" is not allowed`,\n )\n }\n\n if (kind === 'identifier' || kind === 'object-key') return\n\n if (isCanonicalIndex(key)) return\n\n if (this.policy.allowedProperties) {\n if (!this.policy.allowedProperties.has(key)) {\n throw new BonsaiSecurityError(\n 'PROPERTY_NOT_ALLOWED',\n `Blocked: \"${key}\" is not in allowed properties`,\n )\n }\n }\n\n if (this.policy.deniedProperties) {\n if (this.policy.deniedProperties.has(key)) {\n throw new BonsaiSecurityError(\n 'PROPERTY_DENIED',\n `Blocked: \"${key}\" is in denied properties`,\n )\n }\n }\n }\n\n checkArrayLength(length: number): void {\n if (length > this.policy.maxArrayLength) {\n throw new BonsaiSecurityError(\n 'MAX_ARRAY_LENGTH',\n `Array length (${length}) exceeds maximum (${this.policy.maxArrayLength})`,\n )\n }\n }\n\n checkStringLength(length: number): void {\n if (length > this.policy.maxStringLength) {\n throw new BonsaiSecurityError(\n 'MAX_STRING_LENGTH',\n `String length (${length}) exceeds maximum (${this.policy.maxStringLength})`,\n )\n }\n }\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as CompiledExpression, c as InferredTypeName, d as Token, f as TokenType, i as BonsaiPlugin, l as PolicySnapshot, m as ValidationResult, n as BonsaiInstance, o as ExpressionReferences, p as TransformFn, r as BonsaiOptions, s as FunctionFn, t as ASTNode, u as ResolveResult } from "./types-BhsZZsLY.mjs";
1
+ import { a as BonsaiPlugin, c as ExpressionReferences, d as PolicySnapshot, f as ResolveResult, g as ValidationResult, h as TransformFn, i as BonsaiOptions, l as FunctionFn, m as TokenType, n as BonsaiContext, o as CompiledExpression, p as Token, r as BonsaiInstance, s as ContextFunctionFn, t as ASTNode, u as InferredTypeName } from "./types-DkXfy5IJ.mjs";
2
2
 
3
3
  //#region src/errors.d.ts
4
4
  interface ErrorLocation {
@@ -62,16 +62,28 @@ declare function compile(ast: ASTNode): ASTNode;
62
62
  declare function evaluateExpression<T = unknown>(expression: string, context?: Record<string, unknown>): T;
63
63
  /**
64
64
  * Create a new Bonsai instance with optional safety and caching configuration.
65
- * Register transforms and functions via `.use()`, `.addTransform()`, and `.addFunction()`.
65
+ * Register transforms and functions via `.use()`, `.addTransform()`, `.addFunction()`,
66
+ * and `.addContextFunction()`.
67
+ *
68
+ * Pass a context type argument to get end-to-end type safety on the evaluation
69
+ * context: `bonsai<MyContext>()` will type-check `evaluate(expr, ctx)` calls,
70
+ * require a context argument when `MyContext` has required fields, and
71
+ * propagate `MyContext` into context-aware function signatures.
66
72
  *
67
73
  * @example
68
74
  * ```ts
69
75
  * const expr = bonsai({ timeout: 50 })
70
76
  * expr.use(strings)
71
77
  * expr.evaluateSync('name |> trim |> upper', { name: ' hello ' }) // "HELLO"
78
+ *
79
+ * type AppCtx = { userId: string; perms: string[] }
80
+ * const app = bonsai<AppCtx>()
81
+ * app.addContextFunction('hasPermission', (ctx, action) =>
82
+ * ctx.perms.includes(String(action)))
83
+ * app.evaluateSync('hasPermission("write")', { userId: 'u_1', perms: ['write'] })
72
84
  * ```
73
85
  */
74
- declare function bonsai(options?: BonsaiOptions): BonsaiInstance;
86
+ declare function bonsai<TCtx extends BonsaiContext = Record<string, unknown>>(options?: BonsaiOptions): BonsaiInstance<TCtx>;
75
87
  //#endregion
76
- export { type ASTNode, type BonsaiInstance, type BonsaiOptions, type BonsaiPlugin, BonsaiReferenceError, BonsaiSecurityError, BonsaiTypeError, type CompiledExpression, ExpressionError, type ExpressionReferences, type FunctionFn, type InferredTypeName, type PolicySnapshot, type ResolveResult, type Token, type TokenType, type TransformFn, type ValidationResult, bonsai, compile, evaluateExpression, formatBonsaiError, formatError, parse, tokenize };
88
+ export { type ASTNode, type BonsaiInstance, type BonsaiOptions, type BonsaiPlugin, BonsaiReferenceError, BonsaiSecurityError, BonsaiTypeError, type CompiledExpression, type ContextFunctionFn, ExpressionError, type ExpressionReferences, type FunctionFn, type InferredTypeName, type PolicySnapshot, type ResolveResult, type Token, type TokenType, type TransformFn, type ValidationResult, bonsai, compile, evaluateExpression, formatBonsaiError, formatError, parse, tokenize };
77
89
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/lexer.ts","../src/parser.ts","../src/compiler.ts","../src/index.ts"],"mappings":";;;UAIiB,aAAA;EACf,MAAA;EACA,KAAA;EACA,GAAA;AAAA;;cAIW,eAAA,SAAwB,KAAA;EAAA,SAC1B,UAAA;EAAA,SACA,KAAA;EAAA,SACA,GAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;cAGP,OAAA,UACA,QAAA,EAAU,aAAA,EACV,UAAA;AAAA;;cAcS,eAAA,SAAwB,KAAA;EAAA,SAC1B,SAAA;EAAA,SACA,QAAA;EAAA,SACA,QAAA;EACT,QAAA,GAAW,aAAA;EACX,SAAA;cAEY,SAAA,UAAmB,QAAA,UAAkB,KAAA;AAAA;;cAetC,mBAAA,SAA4B,KAAA;EAAA,SAC9B,IAAA;EACT,QAAA,GAAW,aAAA;EACX,SAAA;cAEY,IAAA,UAAc,OAAA;AAAA;;cAQf,oBAAA,SAA6B,KAAA;EAAA,SAC/B,UAAA;EAAA,SACA,IAAA;EAAA,SACA,UAAA;EACT,QAAA,GAAW,aAAA;EACX,SAAA;cAEY,IAAA,uCAA2C,UAAA,UAAoB,UAAA;AAAA;;iBAmE7D,WAAA,CACd,OAAA,UACA,QAAA,EAAU,aAAA,EACV,UAAA;;iBA6Bc,iBAAA,CAAkB,KAAA;;;iBCpJlB,QAAA,CAAS,MAAA,WAAiB,KAAA;;;iBCS1B,KAAA,CAAM,MAAA,UAAgB,MAAA,YAAa,OAAA;;;iBCnCnC,OAAA,CAAQ,GAAA,EAAK,OAAA,GAAU,OAAA;;;;;;;iBCiDvB,kBAAA,aAAA,CAAgC,UAAA,UAAoB,OAAA,GAAU,MAAA,oBAA0B,CAAA;;AJxCxG;;;;;;;;;;iBIwDgB,MAAA,CAAO,OAAA,GAAS,aAAA,GAAqB,cAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/lexer.ts","../src/parser.ts","../src/compiler.ts","../src/index.ts"],"mappings":";;;UAIiB,aAAA;EACf,MAAA;EACA,KAAA;EACA,GAAA;AAAA;;cAIW,eAAA,SAAwB,KAAK;EAAA,SAC/B,UAAA;EAAA,SACA,KAAA;EAAA,SACA,GAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;cAGP,OAAA,UACA,QAAA,EAAU,aAAA,EACV,UAAA;AAAA;;cAcS,eAAA,SAAwB,KAAK;EAAA,SAC/B,SAAA;EAAA,SACA,QAAA;EAAA,SACA,QAAA;EACT,QAAA,GAAW,aAAA;EACX,SAAA;cAEY,SAAA,UAAmB,QAAA,UAAkB,KAAA;AAAA;;cAetC,mBAAA,SAA4B,KAAK;EAAA,SACnC,IAAA;EACT,QAAA,GAAW,aAAA;EACX,SAAA;cAEY,IAAA,UAAc,OAAA;AAAA;;cAQf,oBAAA,SAA6B,KAAK;EAAA,SACpC,UAAA;EAAA,SACA,IAAA;EAAA,SACA,UAAA;EACT,QAAA,GAAW,aAAA;EACX,SAAA;cAEY,IAAA,uCAA2C,UAAA,UAAoB,UAAA;AAAA;;iBAmE7D,WAAA,CACd,OAAA,UACA,QAAA,EAAU,aAAa,EACvB,UAAA;AA1FF;AAAA,iBAuHgB,iBAAA,CAAkB,KAAc;;;iBCzHhC,QAAA,CAAS,MAAA,WAAiB,KAAK;;;iBCV/B,KAAA,CAAM,MAAA,UAAgB,MAAA,YAAa,OAAO;;;iBC3C1C,OAAA,CAAQ,GAAA,EAAK,OAAA,GAAU,OAAO;;;;;;;iBCmF9B,kBAAA,aAAA,CAAgC,UAAA,UAAoB,OAAA,GAAU,MAAA,oBAA0B,CAAC;AJ9EpG;AAIL;;;;;;;;;;;;;;;;AAUuB;AAcvB;;;;;AA5BK,iBI0GW,MAAA,cAAoB,aAAA,GAAgB,MAAA,kBAAA,CAClD,OAAA,GAAS,aAAA,GACR,cAAA,CAAe,IAAA"}