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.
- package/CHANGELOG.md +41 -5
- package/README.md +84 -13
- package/dist/autocomplete/index.d.mts +1 -1
- package/dist/autocomplete/index.d.mts.map +1 -1
- package/dist/autocomplete/index.mjs +22 -61
- package/dist/autocomplete/index.mjs.map +1 -1
- package/dist/{errors-t5i9AEaf.mjs → errors-CCZDd1gK.mjs} +2 -3
- package/dist/errors-CCZDd1gK.mjs.map +1 -0
- package/dist/{execution-context-DqYC3L92.mjs → execution-context-b6FBfEdv.mjs} +147 -16
- package/dist/execution-context-b6FBfEdv.mjs.map +1 -0
- package/dist/index.d.mts +16 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +228 -111
- package/dist/index.mjs.map +1 -1
- package/dist/stdlib/index.d.mts +1 -1
- package/dist/stdlib/index.d.mts.map +1 -1
- package/dist/stdlib/index.mjs +43 -11
- package/dist/stdlib/index.mjs.map +1 -1
- package/dist/{types-BhsZZsLY.d.mts → types-DkXfy5IJ.d.mts} +59 -17
- package/dist/types-DkXfy5IJ.d.mts.map +1 -0
- package/package.json +37 -11
- package/dist/errors-t5i9AEaf.mjs.map +0 -1
- package/dist/execution-context-DqYC3L92.mjs.map +0 -1
- package/dist/types-BhsZZsLY.d.mts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.5.0] - 2026-06-02
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- Bounded parser recursion: pathologically nested input (deep parentheses, unary chains) now fails closed with a typed `ExpressionError` ("Maximum expression nesting depth exceeded") instead of overflowing the native call stack with an uncaught `RangeError`. `validate()` reports it as a normal syntax error rather than a stack-overflow message.
|
|
15
|
+
- `maxArrayLength` is now enforced on array-returning methods (`split`, `map`, `flatMap`, `flat`, `concat`, `toSorted`, `toReversed`, `with`, `toSpliced`), not just array literals and spread, in both the sync and async evaluators.
|
|
16
|
+
- Added `maxStringLength` (default 100,000) and enforced it as a ceiling on every string produced by a method call, in both the sync and async evaluators. `padStart`/`padEnd`/`repeat` are checked before allocation (capping the produced length, not just the repeat count); all other string-returning methods (`join`, `concat`, `slice`, `toUpperCase`, ...) are checked on their output. This closes single-operation amplifiers such as `arr.join(sep)`, whose output length is array length times separator length in one native call that the cooperative timeout cannot interrupt.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `maxStringLength` option on `bonsai(options)` and a `BonsaiSecurityError('MAX_STRING_LENGTH', ...)` error code.
|
|
21
|
+
- `bonsai(options)` now validates its options and throws a `RangeError`/`TypeError` at construction for out-of-range values (negative `cacheSize`, non-positive `maxDepth`, negative `maxArrayLength`/`maxStringLength`, negative or non-finite `timeout`, allow/deny lists that are not arrays of strings). Previously a negative `cacheSize` silently disabled caching and other bad values failed later with confusing errors.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Async/sync parity for higher-order array methods (`map`, `filter`, `find`, `some`, `every`, `findIndex`, `flatMap`). Predicates now evaluate sequentially in the async evaluator, matching the synchronous one: `some`/`every`/`find`/`findIndex` short-circuit at the first decisive element, and `maxDepth` is no longer inflated by array length (an expression that succeeded with `evaluateSync` could previously throw a spurious `Maximum expression depth` error with `evaluate`).
|
|
26
|
+
- Malformed numeric literals now raise a parse-time `ExpressionError` instead of silently evaluating to `NaN` or coercing: a base prefix or exponent with no digits (`1e`, `1e+`, `0x`, `0b`, `0o`), and misplaced numeric separators (`1_`, `1__0`, `0xff_`), which are rejected the way JavaScript rejects them.
|
|
27
|
+
- Template interpolations containing string literals or nested template literals with braces (for example `` `${ "x}" }` `` or `` `${ `a}b` }` ``) now parse correctly instead of failing with an "Unterminated string"/"Unterminated template literal" error.
|
|
28
|
+
- Higher-order array helpers now fail loud on a non-function callback instead of silently doing the wrong thing. The lambda shorthand (`.x`, `. > 0`) is a function value; passing it to a function (for example `map(inc(.x))`) evaluates to a plain value, which previously made the stdlib `map`/`filter`/`find`/`some`/`every` transforms silently return the input unchanged and the `.map`/`.filter`/`.find`/`.findIndex`/`.some`/`.every`/`.flatMap` methods throw a raw `TypeError`. Both now throw a typed `BonsaiTypeError` naming the helper. No-argument forms (`|> map`, `|> filter`) are unaffected.
|
|
29
|
+
- Autocomplete no longer suggests `array.toString`, which the evaluator rejects (the method allowlist and the autocomplete catalog now derive from one shared source, so they cannot drift). Method completions also respect `allowedProperties`, matching the evaluator, so a method that would be rejected at runtime is no longer offered.
|
|
30
|
+
- stdlib correctness: `formatDate` now replaces every occurrence of a token (not just the first, so `"YYYY/YYYY"` works); `min`/`max` validate their arguments are numbers and return `undefined` for no arguments instead of leaking `Infinity`/`-Infinity`; `clamp` rejects non-finite bounds and `min > max`; `sort` orders strings by code point for deterministic, locale-independent results. NaN passthrough in the numeric transforms is now documented.
|
|
31
|
+
|
|
32
|
+
## [0.4.0] - 2026-06-02
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **Context-aware functions** (`addContextFunction`): register functions that receive the evaluation context as their first parameter (typed `Readonly<TCtx>`, passed by reference), enabling auth/permission/personalization patterns without threading context through expression arguments. Pure and context-aware functions share a single namespace; `isContextFunction(name)` introspects the kind.
|
|
37
|
+
- **Generic context typing** (`bonsai<TCtx>()`): the factory is now generic over context type, with end-to-end type safety through `evaluate`, `evaluateSync`, `compile`, and `addContextFunction`. Backward compatible: defaults to `Record<string, unknown>` when unspecified.
|
|
38
|
+
- New exported types: `ContextFunctionFn`, generic `BonsaiPlugin<TCtx>`, generic `CompiledExpression<TCtx>`, generic `BonsaiInstance<TCtx>`.
|
|
39
|
+
- Internal: pure and context-aware functions share one tagged registry, and the cached `Bindings` snapshot consolidates transforms and that registry into a single object passed to the evaluator.
|
|
40
|
+
|
|
41
|
+
### Resolves
|
|
42
|
+
|
|
43
|
+
- #33: context access from registered functions.
|
|
44
|
+
|
|
45
|
+
### Credits
|
|
46
|
+
|
|
47
|
+
- Thanks to @jaenyf for raising #33 and contributing PR #34.
|
|
48
|
+
|
|
8
49
|
## [0.3.0] - 2026-03-21
|
|
9
50
|
|
|
10
51
|
### Added
|
|
@@ -58,11 +99,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
58
99
|
- **Bare dot identity lambda:** `. > 2` and `. * 10` now work as lambda shorthand for the current item itself, in both method calls and pipe transforms (e.g., `[1,2,3,4].filter(. > 2)`, `[1,2,3] |> map(. * 10)`)
|
|
59
100
|
- Async-safe evaluation for all higher-order array methods — lambda predicates that return Promises are correctly resolved in both top-level and nested contexts via `evaluate()`
|
|
60
101
|
- Documentation for array methods, lambda shorthand, and method chaining in README and website docs
|
|
61
|
-
|
|
62
|
-
## [Unreleased]
|
|
63
|
-
|
|
64
|
-
### Added
|
|
65
|
-
|
|
66
102
|
- Deterministic property-based parser and evaluator invariant tests
|
|
67
103
|
- Random fuzz coverage for malformed parser input
|
|
68
104
|
- Adversarial regression tests for deep nesting, oversized arrays, nested blocked keys, and spread misuse
|
package/README.md
CHANGED
|
@@ -125,6 +125,58 @@ expr.addFunction('lookupTier', async (userId) => {
|
|
|
125
125
|
await expr.evaluate('lookupTier(userId) == "pro"', { userId: 'u_123' })
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### Context-aware functions
|
|
129
|
+
|
|
130
|
+
Register functions that read the evaluation context directly. The function
|
|
131
|
+
receives the evaluation context as its first parameter (typed read-only), so
|
|
132
|
+
you can keep expressions terse and let the function pull what it needs:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { bonsai } from 'bonsai-js'
|
|
136
|
+
|
|
137
|
+
interface AppContext {
|
|
138
|
+
currentUserId: string
|
|
139
|
+
perms: readonly string[]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const app = bonsai<AppContext>()
|
|
143
|
+
|
|
144
|
+
app.addContextFunction('lookupCurrentUserTier', async (ctx) => {
|
|
145
|
+
const row = await db.users.findById(ctx.currentUserId)
|
|
146
|
+
return row?.tier ?? 'free'
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
app.addContextFunction('hasPermission', (ctx, action) =>
|
|
150
|
+
ctx.perms.includes(String(action)))
|
|
151
|
+
|
|
152
|
+
await app.evaluate(
|
|
153
|
+
'lookupCurrentUserTier() == "pro" && hasPermission("admin")',
|
|
154
|
+
{ currentUserId: 'u_123', perms: ['admin', 'write'] },
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The instance is generic over the context type (`bonsai<AppContext>()`), giving
|
|
159
|
+
you end-to-end type safety: `ctx` is typed inside the function, and the call
|
|
160
|
+
site is type-checked against the same shape. If your context type has required
|
|
161
|
+
fields, TypeScript also requires you to pass the `context` argument to
|
|
162
|
+
`evaluate`, `evaluateSync`, and compiled-expression evaluation.
|
|
163
|
+
|
|
164
|
+
The context is passed to your function by reference, not copied or frozen. The
|
|
165
|
+
`Readonly<TCtx>` parameter type signals that you should treat it as read-only:
|
|
166
|
+
TypeScript flags reassigning its top-level fields. Bonsai does not deep-freeze
|
|
167
|
+
it, so nested mutation and writes from untyped JavaScript reach the object you
|
|
168
|
+
passed in. If you need isolation between evaluations, pass a fresh context
|
|
169
|
+
object each time.
|
|
170
|
+
|
|
171
|
+
Pure functions (`addFunction`) and context-aware functions (`addContextFunction`)
|
|
172
|
+
share a single namespace. Registering the same name with either method
|
|
173
|
+
overwrites the prior registration, so re-registering a context-aware name with
|
|
174
|
+
`addFunction` turns it back into a pure function (check `isContextFunction(name)`
|
|
175
|
+
if the kind matters). Functions registered with `addFunction` never receive the
|
|
176
|
+
context; reach for `addContextFunction` when a function needs it. Plugins typed
|
|
177
|
+
against a minimal context requirement can still be applied to instances with a
|
|
178
|
+
wider context shape.
|
|
179
|
+
|
|
128
180
|
### Editor validation
|
|
129
181
|
|
|
130
182
|
```ts
|
|
@@ -151,6 +203,8 @@ Inside array methods, `.` refers to the current item:
|
|
|
151
203
|
|
|
152
204
|
Compound predicates work too: `.age >= 18 && .active`
|
|
153
205
|
|
|
206
|
+
A lambda is built from the accessor plus operators, member access, and methods. The shorthand is itself a function value, so it cannot be passed *into* another function call: `map(myFn(.x))` does not mean `map(item => myFn(item.x))` and will throw a `BonsaiTypeError`. To transform each item through a function, chain instead: `items |> map(.x) |> map(myFn)` (or `items.map(.x).map(myFn)`).
|
|
207
|
+
|
|
154
208
|
### Pipe transforms (via stdlib)
|
|
155
209
|
|
|
156
210
|
```ts
|
|
@@ -244,13 +298,15 @@ const expr = bonsai(options?: BonsaiOptions)
|
|
|
244
298
|
| --- | --- | --- | --- |
|
|
245
299
|
| `timeout` | `number` | `0` | Evaluation timeout in milliseconds. `0` disables the timeout check. |
|
|
246
300
|
| `maxDepth` | `number` | `100` | Maximum evaluation depth before throwing `BonsaiSecurityError('MAX_DEPTH', ...)`. |
|
|
247
|
-
| `maxArrayLength` | `number` | `100000` | Maximum array
|
|
248
|
-
| `
|
|
301
|
+
| `maxArrayLength` | `number` | `100000` | Maximum array size produced during evaluation, including array literals, expanded spread, and array-returning methods (`split`, `map`, `flat`, `concat`, ...). Exceeding it throws `BonsaiSecurityError('MAX_ARRAY_LENGTH', ...)`. |
|
|
302
|
+
| `maxStringLength` | `number` | `100000` | Maximum string size produced by a string-returning method (`padStart`, `padEnd`, `repeat`, `join`, `concat`, `slice`, ...). Applies to the produced length (e.g. `arr.join(sep)` is bounded by its full output, not just the inputs). Exceeding it throws `BonsaiSecurityError('MAX_STRING_LENGTH', ...)`. |
|
|
303
|
+
| `cacheSize` | `number` | `256` | Per-instance cache size for compiled expressions and parsed AST reuse. `0` disables caching. |
|
|
249
304
|
| `allowedProperties` | `string[]` | `undefined` | Whitelist of allowed member/method names. Does not apply to root identifiers or object-literal keys. |
|
|
250
305
|
| `deniedProperties` | `string[]` | `undefined` | Denylist of blocked member/method names. Does not apply to root identifiers or object-literal keys. |
|
|
251
306
|
|
|
252
307
|
Important notes:
|
|
253
308
|
|
|
309
|
+
- Options are validated at construction: out-of-range values (a negative `cacheSize`, a non-positive `maxDepth`, a negative size limit, a negative/non-finite `timeout`) throw a `RangeError`/`TypeError` immediately rather than failing silently later.
|
|
254
310
|
- `allowedProperties` and `deniedProperties` apply to **member access** (`obj.name`) and **method calls** (`str.slice()`), not root identifiers (`name`) or object-literal keys (`{ name: value }`).
|
|
255
311
|
- If you whitelist `user.name`, you must allow both `user` and `name` as member names.
|
|
256
312
|
- Numeric array indices (e.g., `items[0]`) bypass allow/deny lists automatically.
|
|
@@ -361,20 +417,25 @@ evaluateExpression<number>('x * 2', { x: 21 }) // 42
|
|
|
361
417
|
## Instance Methods
|
|
362
418
|
|
|
363
419
|
```ts
|
|
364
|
-
|
|
365
|
-
|
|
420
|
+
type EvaluationContextArgs<TCtx extends object = Record<string, unknown>> =
|
|
421
|
+
{} extends TCtx ? [context?: TCtx] : [context: TCtx]
|
|
422
|
+
|
|
423
|
+
interface BonsaiInstance<TCtx extends object = Record<string, unknown>> {
|
|
424
|
+
use(plugin: BonsaiPlugin<TCtx>): this
|
|
366
425
|
addTransform(name: string, fn: TransformFn): this
|
|
367
426
|
addFunction(name: string, fn: FunctionFn): this
|
|
427
|
+
addContextFunction(name: string, fn: ContextFunctionFn<TCtx>): this
|
|
368
428
|
removeTransform(name: string): boolean
|
|
369
429
|
removeFunction(name: string): boolean
|
|
370
430
|
hasTransform(name: string): boolean
|
|
371
431
|
hasFunction(name: string): boolean
|
|
432
|
+
isContextFunction(name: string): boolean
|
|
372
433
|
listTransforms(): string[]
|
|
373
434
|
listFunctions(): string[]
|
|
374
435
|
clearCache(): void
|
|
375
|
-
compile(expression: string): CompiledExpression
|
|
376
|
-
evaluate<T = unknown>(expression: string,
|
|
377
|
-
evaluateSync<T = unknown>(expression: string,
|
|
436
|
+
compile(expression: string): CompiledExpression<TCtx>
|
|
437
|
+
evaluate<T = unknown>(expression: string, ...args: EvaluationContextArgs<TCtx>): Promise<T>
|
|
438
|
+
evaluateSync<T = unknown>(expression: string, ...args: EvaluationContextArgs<TCtx>): T
|
|
378
439
|
validate(expression: string): ValidationResult
|
|
379
440
|
}
|
|
380
441
|
```
|
|
@@ -382,9 +443,12 @@ interface BonsaiInstance {
|
|
|
382
443
|
Method notes:
|
|
383
444
|
|
|
384
445
|
- `use()` runs a plugin immediately and returns the same instance.
|
|
385
|
-
- `addTransform()` and `
|
|
386
|
-
- `
|
|
446
|
+
- `addTransform()`, `addFunction()`, and `addContextFunction()` overwrite any existing registration with the same name. Pure and context-aware functions share a single namespace.
|
|
447
|
+
- `addContextFunction()` registers a function that receives the live evaluation context as its first argument (typed read-only; passed by reference, not copied or frozen). See [Context-aware functions](#context-aware-functions).
|
|
448
|
+
- `isContextFunction()` returns `true` if the named function was registered via `addContextFunction()`.
|
|
449
|
+
- `listTransforms()` and `listFunctions()` return the currently registered names. `listFunctions()` includes both pure and context-aware functions.
|
|
387
450
|
- `clearCache()` clears the internal AST cache and compiled-expression cache. It does not remove registered transforms/functions.
|
|
451
|
+
- Pass a context type generic to `bonsai<MyContext>()` for end-to-end type safety: `evaluate`, `evaluateSync`, `addContextFunction`, `compile`, and `use` all propagate the type. If `MyContext` has required fields, TypeScript requires a context argument when evaluating.
|
|
388
452
|
|
|
389
453
|
## Extending the Runtime
|
|
390
454
|
|
|
@@ -470,6 +534,12 @@ Modules:
|
|
|
470
534
|
| `dates` | function `now`; transforms `formatDate`, `diffDays` |
|
|
471
535
|
| `all` | registers every stdlib module above |
|
|
472
536
|
|
|
537
|
+
Notes on stdlib semantics:
|
|
538
|
+
|
|
539
|
+
- **`sort`** orders strings by code point (not locale), so results are deterministic across runtimes and locales. Numbers sort numerically.
|
|
540
|
+
- **`min`/`max`** validate that every argument is a number and return `undefined` for no arguments (rather than `Infinity`/`-Infinity`). **`clamp`** requires finite bounds with `min <= max`.
|
|
541
|
+
- **`NaN` is passed through** by the numeric transforms (`sum`, `avg`, `round`, etc.): a `NaN` input yields a `NaN` result (which serializes to `null` in JSON). Validate inputs upstream if you need to reject non-finite numbers.
|
|
542
|
+
|
|
473
543
|
## Error Handling
|
|
474
544
|
|
|
475
545
|
Runtime exports:
|
|
@@ -532,7 +602,8 @@ Bonsai is designed to safely evaluate expressions, but it is not a process sandb
|
|
|
532
602
|
What Bonsai does:
|
|
533
603
|
|
|
534
604
|
- blocks access to `__proto__`, `constructor`, and `prototype` at every access level, even if explicitly allowed
|
|
535
|
-
- enforces `maxDepth`, `maxArrayLength`, and optional `timeout`
|
|
605
|
+
- enforces `maxDepth`, `maxArrayLength`, `maxStringLength`, and optional `timeout`
|
|
606
|
+
- bounds parser recursion so pathologically nested input fails closed with a typed `ExpressionError` instead of overflowing the call stack
|
|
536
607
|
- lets you allowlist or denylist member/method names via `allowedProperties`/`deniedProperties`
|
|
537
608
|
- prevents expressions from reaching globals or importing modules
|
|
538
609
|
- looks up root identifiers via own-property checks only (`Object.hasOwn`), so context prototype chains cannot leak
|
|
@@ -544,7 +615,7 @@ What Bonsai does:
|
|
|
544
615
|
Important operational caveats:
|
|
545
616
|
|
|
546
617
|
- `allowedProperties` and `deniedProperties` apply to member access (`obj.name`) and method calls (`str.slice()`), not root identifiers (`name`) or object-literal keys (`{ name: value }`)
|
|
547
|
-
- `timeout` is cooperative and checked
|
|
618
|
+
- `timeout` is cooperative and checked at evaluator step boundaries (not inside a single native operation); it cannot forcibly interrupt arbitrary synchronous code inside your own custom transforms/functions, so the size limits (`maxArrayLength`, `maxStringLength`) are the primary guard against single-operation resource exhaustion
|
|
548
619
|
- async transforms/functions are bounded only at awaited boundaries
|
|
549
620
|
- custom transforms/functions/plugins are trusted host code
|
|
550
621
|
|
|
@@ -725,8 +796,8 @@ interface Completion {
|
|
|
725
796
|
|
|
726
797
|
Bonsai follows SemVer for the documented package entrypoints `bonsai-js`, `bonsai-js/stdlib`, and `bonsai-js/autocomplete`.
|
|
727
798
|
|
|
728
|
-
- Supported runtimes are Node
|
|
729
|
-
- The packed npm artifact is smoke-tested on Node
|
|
799
|
+
- Supported runtimes are Node 22+ and current Bun releases.
|
|
800
|
+
- The packed npm artifact is smoke-tested on Node 22 and 24.
|
|
730
801
|
- Internal modules under `src/*` are not public API.
|
|
731
802
|
|
|
732
803
|
See [stability policy](./docs/stability-policy.md) for the compatibility boundary and release rules.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as BonsaiInstance, u as InferredTypeName } from "../types-DkXfy5IJ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/autocomplete/tokenizer.d.ts
|
|
4
4
|
/** Known phases where errors can occur during completion. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/autocomplete/tokenizer.ts","../../src/autocomplete/completions.ts","../../src/autocomplete/index.ts"],"mappings":";;;;KAcY,eAAA;AAAA,KAOA,YAAA,IAAgB,KAAA,WAAgB,KAAA,EAAO,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/autocomplete/tokenizer.ts","../../src/autocomplete/completions.ts","../../src/autocomplete/index.ts"],"mappings":";;;;KAcY,eAAA;AAAA,KAOA,YAAA,IAAgB,KAAA,WAAgB,KAAA,EAAO,eAAe;;;KCd7D,gBAAA;EACC,UAAA;EAAwB,YAAA;AAAA;EACxB,UAAA;EAAoB,YAAA;AAAA;AAAA,UAEhB,cAAA;EACR,KAAA;EACA,IAAA;EACA,MAAA;EDO0C;ECL1C,YAAA;AAAA;AAAA,KAGU,UAAA,GAAa,cAAA,GAAiB,gBAAgB;;;UCTzC,mBAAA;EACf,OAAA,GAAU,MAAA;EFGe;AAAA;AAO3B;;;;EEHE,cAAA,GAAiB,MAAA,SAAe,gBAAA;EFGiB;;;EECjD,OAAA,GAAU,YAAA;AAAA;AAAA,UAGK,oBAAA;EACf,QAAA,CAAS,UAAA,UAAoB,MAAA,WAAiB,UAAA;EAC9C,UAAA,CAAW,OAAA,EAAS,MAAM;AAAA;AAAA,iBAcZ,kBAAA,CACd,QAAA,EAAU,cAAA,EACV,OAAA,GAAS,mBAAA,GACR,oBAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { i as ExpressionError, n as BonsaiSecurityError, r as BonsaiTypeError, t as BonsaiReferenceError } from "../errors-
|
|
2
|
-
import {
|
|
1
|
+
import { i as ExpressionError, n as BonsaiSecurityError, r as BonsaiTypeError, t as BonsaiReferenceError } from "../errors-CCZDd1gK.mjs";
|
|
2
|
+
import { a as methodsForReceiverType, o as tokenize, t as BLOCKED_PROPERTIES } from "../execution-context-b6FBfEdv.mjs";
|
|
3
3
|
//#region src/autocomplete/tokenizer.ts
|
|
4
4
|
function tolerantTokenize(expression, cursor, onError) {
|
|
5
5
|
if (expression.length === 0) return {
|
|
@@ -66,7 +66,7 @@ function isCursorInsideString(expression, cursor) {
|
|
|
66
66
|
}
|
|
67
67
|
return inSingle || inDouble || inTemplate && templateDepth === 0;
|
|
68
68
|
}
|
|
69
|
-
const TOKEN_RE = /\?\.|\.\.\.|\|>|\?\?|&&|\|\||[!=<>]=?|[+\-*/%]|\*\*|[.(){}[\],?:]|"(?:[^"\\]|\\.)*"?|'(?:[^'\\]|\\.)*'?|`(?:[^`\\$]|\\.|\$(?!\{))*`?|\d[\d_]*(?:\.\d[\d_]*)?(?:[eE][+-]?\d+)?|[a-zA-Z_$][\w$]*/
|
|
69
|
+
const TOKEN_RE = /\?\.|\.\.\.|\|>|\?\?|&&|\|\||[!=<>]=?|[+\-*/%]|\*\*|[.(){}[\],?:]|"(?:[^"\\]|\\.)*"?|'(?:[^'\\]|\\.)*'?|`(?:[^`\\$]|\\.|\$(?!\{))*`?|\d[\d_]*(?:\.\d[\d_]*)?(?:[eE][+-]?\d+)?|[a-zA-Z_$][\w$]*/gu;
|
|
70
70
|
function regexScan(expression, cursor) {
|
|
71
71
|
const tokens = [];
|
|
72
72
|
const text = expression.slice(0, cursor);
|
|
@@ -121,13 +121,13 @@ function classifyToken(value, start, end) {
|
|
|
121
121
|
start,
|
|
122
122
|
end
|
|
123
123
|
};
|
|
124
|
-
if (/^[a-zA-Z_$]
|
|
124
|
+
if (/^[a-zA-Z_$]/u.test(value)) return {
|
|
125
125
|
type: "Identifier",
|
|
126
126
|
value,
|
|
127
127
|
start,
|
|
128
128
|
end
|
|
129
129
|
};
|
|
130
|
-
if (/^\d
|
|
130
|
+
if (/^\d/u.test(value)) return {
|
|
131
131
|
type: "Number",
|
|
132
132
|
value,
|
|
133
133
|
start,
|
|
@@ -279,56 +279,15 @@ function buildLambdaState(tokens) {
|
|
|
279
279
|
}
|
|
280
280
|
//#endregion
|
|
281
281
|
//#region src/autocomplete/catalog.ts
|
|
282
|
-
/**
|
|
282
|
+
/**
|
|
283
|
+
* Methods offered per receiver type, derived from the shared SAFE_METHODS
|
|
284
|
+
* allowlist (src/safe-methods.ts) so autocomplete and the evaluator can never
|
|
285
|
+
* disagree about what is callable.
|
|
286
|
+
*/
|
|
283
287
|
const METHODS_BY_TYPE = {
|
|
284
|
-
string:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
"trimEnd",
|
|
288
|
-
"toLowerCase",
|
|
289
|
-
"toUpperCase",
|
|
290
|
-
"startsWith",
|
|
291
|
-
"endsWith",
|
|
292
|
-
"includes",
|
|
293
|
-
"indexOf",
|
|
294
|
-
"lastIndexOf",
|
|
295
|
-
"slice",
|
|
296
|
-
"substring",
|
|
297
|
-
"at",
|
|
298
|
-
"replace",
|
|
299
|
-
"replaceAll",
|
|
300
|
-
"split",
|
|
301
|
-
"padStart",
|
|
302
|
-
"padEnd",
|
|
303
|
-
"charAt",
|
|
304
|
-
"charCodeAt",
|
|
305
|
-
"repeat",
|
|
306
|
-
"concat",
|
|
307
|
-
"toString"
|
|
308
|
-
],
|
|
309
|
-
array: [
|
|
310
|
-
"filter",
|
|
311
|
-
"map",
|
|
312
|
-
"find",
|
|
313
|
-
"findIndex",
|
|
314
|
-
"some",
|
|
315
|
-
"every",
|
|
316
|
-
"flatMap",
|
|
317
|
-
"join",
|
|
318
|
-
"flat",
|
|
319
|
-
"includes",
|
|
320
|
-
"indexOf",
|
|
321
|
-
"lastIndexOf",
|
|
322
|
-
"slice",
|
|
323
|
-
"at",
|
|
324
|
-
"concat",
|
|
325
|
-
"toReversed",
|
|
326
|
-
"toSorted",
|
|
327
|
-
"toSpliced",
|
|
328
|
-
"with",
|
|
329
|
-
"toString"
|
|
330
|
-
],
|
|
331
|
-
number: ["toFixed", "toString"]
|
|
288
|
+
string: methodsForReceiverType("string"),
|
|
289
|
+
array: methodsForReceiverType("array"),
|
|
290
|
+
number: methodsForReceiverType("number")
|
|
332
291
|
};
|
|
333
292
|
/** Return type keyed by "receiverType.method" to disambiguate shared method names. */
|
|
334
293
|
const RETURN_TYPES = {
|
|
@@ -374,7 +333,6 @@ const RETURN_TYPES = {
|
|
|
374
333
|
"array.includes": "boolean",
|
|
375
334
|
"array.at": "unknown",
|
|
376
335
|
"array.with": "array",
|
|
377
|
-
"array.toString": "string",
|
|
378
336
|
"number.toFixed": "string",
|
|
379
337
|
"number.toString": "string"
|
|
380
338
|
};
|
|
@@ -560,9 +518,12 @@ function addMethodCompletions(results, env) {
|
|
|
560
518
|
if (!type || !isMethodReceiverType(type)) return;
|
|
561
519
|
const cached = METHOD_COMPLETIONS_BY_TYPE[type];
|
|
562
520
|
if (!cached) return;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
521
|
+
const { allowedProperties, deniedProperties } = env.policy;
|
|
522
|
+
for (const c of cached) {
|
|
523
|
+
if (allowedProperties && !allowedProperties.has(c.label)) continue;
|
|
524
|
+
if (deniedProperties && deniedProperties.has(c.label)) continue;
|
|
525
|
+
results.push(c);
|
|
526
|
+
}
|
|
566
527
|
}
|
|
567
528
|
function addLambdaPropertyCompletions(results, env) {
|
|
568
529
|
if (!env.lambda?.elementProperties) return;
|
|
@@ -686,7 +647,7 @@ function comparator(a, b) {
|
|
|
686
647
|
}
|
|
687
648
|
//#endregion
|
|
688
649
|
//#region src/autocomplete/index.ts
|
|
689
|
-
const VALID_IDENTIFIER = /^[a-zA-Z_$][\w$]
|
|
650
|
+
const VALID_IDENTIFIER = /^[a-zA-Z_$][\w$]*$/u;
|
|
690
651
|
/** Check if an error is an expected Bonsai error (syntax, security, type, or reference). */
|
|
691
652
|
function isExpectedError(err) {
|
|
692
653
|
return err instanceof ExpressionError || err instanceof BonsaiSecurityError || err instanceof BonsaiTypeError || err instanceof BonsaiReferenceError;
|
|
@@ -962,9 +923,9 @@ function probeAcceptedTransforms(instance, inputType, cache, currentTransforms,
|
|
|
962
923
|
return accepted;
|
|
963
924
|
}
|
|
964
925
|
function inferPipeInputType(expression, cursor, instance, context, onError) {
|
|
965
|
-
const pipeMatch = expression.slice(0, cursor).match(/^(
|
|
926
|
+
const pipeMatch = expression.slice(0, cursor).match(/^(?<input>.*)\|>\s*\w*\s*$/su);
|
|
966
927
|
if (!pipeMatch) return void 0;
|
|
967
|
-
const exprBefore = pipeMatch
|
|
928
|
+
const exprBefore = (pipeMatch.groups?.input ?? "").trim();
|
|
968
929
|
if (!exprBefore) return void 0;
|
|
969
930
|
try {
|
|
970
931
|
const result = instance.evaluateSync(exprBefore, context);
|