bonsai-js 0.2.0 → 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/CHANGELOG.md +45 -0
- package/README.md +187 -1
- package/dist/autocomplete/index.d.mts +47 -0
- package/dist/autocomplete/index.d.mts.map +1 -0
- package/dist/autocomplete/index.mjs +1038 -0
- package/dist/autocomplete/index.mjs.map +1 -0
- package/dist/execution-context-DqYC3L92.mjs +564 -0
- package/dist/execution-context-DqYC3L92.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +94 -577
- package/dist/index.mjs.map +1 -1
- package/dist/stdlib/index.d.mts +1 -1
- package/dist/{types-ClpZDF5z.d.mts → types-BhsZZsLY.d.mts} +19 -2
- package/dist/types-BhsZZsLY.d.mts.map +1 -0
- package/package.json +5 -1
- package/dist/types-ClpZDF5z.d.mts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ 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
|
+
## [0.3.0] - 2026-03-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Autocomplete API** (`bonsai-js/autocomplete`): cursor-aware expression completions with property, method, transform, function, and keyword suggestions
|
|
13
|
+
- `getPolicy()` method on `BonsaiInstance` for reading security policy
|
|
14
|
+
- `InferredTypeName`, `PolicySnapshot`, `ResolveResult` shared types
|
|
15
|
+
- Tolerant tokenization for incomplete expressions with regex fallback
|
|
16
|
+
- Type inference from sample context objects with array element type detection
|
|
17
|
+
- Lambda-aware dot classification (lambda-start, lambda member-access, top-level member-access)
|
|
18
|
+
- Security policy filtering in completions (respects allowedProperties/deniedProperties in all paths)
|
|
19
|
+
- Optional chaining (`?.`) support in completions
|
|
20
|
+
- Fuzzy matching with camelCase-aware scoring
|
|
21
|
+
- `onError` callback for debugging autocomplete failures
|
|
22
|
+
- `transformTypes` option to skip auto-probing for performance
|
|
23
|
+
- Eval-based type inference for method chains (e.g., `user.name.trim().`)
|
|
24
|
+
- Nested lambda element inference (e.g., `groups.map(.users.filter(.`)
|
|
25
|
+
- Pre-computed method completion cache for fast method suggestions
|
|
26
|
+
- Autocomplete benchmarks (`benchmarks/autocomplete.bench.ts`)
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Performance**: core `evaluateSync` 1.4–2.4x faster (LRU cache fast path, pooled ExecutionContext, leaf node fast path)
|
|
31
|
+
- **Performance**: autocomplete method completions 3–4x faster via pre-computed completion objects
|
|
32
|
+
- LRU cache uses `lastKey` tracking to skip reordering on repeated hits
|
|
33
|
+
- `ExecutionContext` reusable via `reset()` — avoids per-call allocation in `evaluateSync`
|
|
34
|
+
- Leaf AST nodes (literals, identifiers) skip depth tracking overhead in both sync and async evaluators
|
|
35
|
+
|
|
36
|
+
### Security
|
|
37
|
+
|
|
38
|
+
- Transform names validated as safe identifiers before `evaluateSync` interpolation
|
|
39
|
+
- `resolvePropertyChain` enforces `allowedProperties`/`deniedProperties` policy at every chain step
|
|
40
|
+
- `BLOCKED_NAMES` unified with `BLOCKED_PROPERTIES` from execution context (single source of truth)
|
|
41
|
+
- Catch blocks use `isExpectedError()` checking all 4 Bonsai error types — unexpected errors surface via `onError`
|
|
42
|
+
- Top-level `complete()` catches unexpected errors and returns `[]` (never crashes host)
|
|
43
|
+
|
|
44
|
+
## [0.2.1] - 2026-03-16
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- Native method support for commonly expected string methods: `trim`, `toLowerCase`, `toUpperCase`, `split`, `padStart`, `padEnd`, `concat`, `lastIndexOf`
|
|
49
|
+
- Native method support for additional array methods: `join`, `flat`, `concat`, `lastIndexOf`, `findIndex`, `flatMap`
|
|
50
|
+
- ES2023 non-mutating array methods: `toReversed`, `toSorted`, `toSpliced`, `with`
|
|
51
|
+
- Mutating array methods (`reverse`, `sort`, `push`, `pop`, `splice`) are explicitly blocked to prevent context mutation
|
|
52
|
+
|
|
8
53
|
## [0.2.0] - 2026-03-15
|
|
9
54
|
|
|
10
55
|
### Added
|
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ bun add bonsai-js
|
|
|
27
27
|
npm install bonsai-js
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
[npm](https://www.npmjs.com/package/bonsai-js) · [Playground](https://danfry1.github.io/bonsai-js/playground.html) · [Docs](https://danfry1.github.io/bonsai-js/docs.html)
|
|
31
|
+
|
|
30
32
|
## When to use it
|
|
31
33
|
|
|
32
34
|
- Evaluate expressions from config, database records, or admin tools.
|
|
@@ -183,6 +185,47 @@ expr.evaluateSync('[1, 2, 3].every(. > 0)') // true
|
|
|
183
185
|
|
|
184
186
|
Both styles support async evaluation via `evaluate()`.
|
|
185
187
|
|
|
188
|
+
### Built-in safe methods
|
|
189
|
+
|
|
190
|
+
These methods work via native `.method()` syntax without any imports. Mutating methods (`reverse`, `sort`, `push`, `pop`, `splice`, etc.) are blocked to prevent context mutation.
|
|
191
|
+
|
|
192
|
+
**String methods:**
|
|
193
|
+
|
|
194
|
+
| Method | Example |
|
|
195
|
+
| --- | --- |
|
|
196
|
+
| `trim`, `trimStart`, `trimEnd` | `" hi ".trim()` → `"hi"` |
|
|
197
|
+
| `toLowerCase`, `toUpperCase` | `"Hello".toLowerCase()` → `"hello"` |
|
|
198
|
+
| `startsWith`, `endsWith` | `"hello".startsWith("hel")` → `true` |
|
|
199
|
+
| `includes`, `indexOf`, `lastIndexOf` | `"hello".includes("ell")` → `true` |
|
|
200
|
+
| `slice`, `substring`, `at` | `"hello".slice(1, 3)` → `"el"` |
|
|
201
|
+
| `replace`, `replaceAll` | `"abc".replace("a", "x")` → `"xbc"` |
|
|
202
|
+
| `split` | `"a,b,c".split(",")` → `["a", "b", "c"]` |
|
|
203
|
+
| `padStart`, `padEnd` | `"5".padStart(3, "0")` → `"005"` |
|
|
204
|
+
| `charAt`, `charCodeAt`, `repeat`, `concat` | `"ab".repeat(2)` → `"abab"` |
|
|
205
|
+
|
|
206
|
+
**Array methods (with lambda support):**
|
|
207
|
+
|
|
208
|
+
| Method | Example |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `filter` | `[1,2,3].filter(. > 1)` → `[2, 3]` |
|
|
211
|
+
| `map` | `[1,2,3].map(. * 10)` → `[10, 20, 30]` |
|
|
212
|
+
| `find`, `findIndex` | `[1,2,3].find(. > 1)` → `2` |
|
|
213
|
+
| `some`, `every` | `[1,2,3].some(. > 2)` → `true` |
|
|
214
|
+
| `flatMap` | `[[1],[2,3]].flatMap(.)` |
|
|
215
|
+
|
|
216
|
+
**Array methods (non-callback):**
|
|
217
|
+
|
|
218
|
+
| Method | Example |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `join` | `[1,2,3].join(", ")` → `"1, 2, 3"` |
|
|
221
|
+
| `includes`, `indexOf`, `lastIndexOf` | `[1,2,3].includes(2)` → `true` |
|
|
222
|
+
| `slice`, `at`, `concat`, `flat` | `[1,2,3].concat([4])` → `[1, 2, 3, 4]` |
|
|
223
|
+
| `toReversed`, `toSorted`, `toSpliced`, `with` | `[3,1,2].toSorted()` → `[1, 2, 3]` |
|
|
224
|
+
|
|
225
|
+
**Number methods:** `toFixed`, `toString`
|
|
226
|
+
|
|
227
|
+
**Pipe-only transforms** (require stdlib import): `count`, `first`, `last`, `reverse`, `flatten`, `unique`, `sort`, `upper`, `lower`, `trim`, `sum`, `avg`, `clamp`, and more. See [Standard Library](#standard-library) for the full list.
|
|
228
|
+
|
|
186
229
|
## API Reference
|
|
187
230
|
|
|
188
231
|
### `bonsai(options?)`
|
|
@@ -535,9 +578,152 @@ Bonsai is optimized for repeated evaluation.
|
|
|
535
578
|
|
|
536
579
|
Benchmark guidance and current numbers live in the website docs and benchmark suite. Treat raw benchmark numbers as directional, not part of the API contract.
|
|
537
580
|
|
|
581
|
+
## Autocomplete
|
|
582
|
+
|
|
583
|
+
Bonsai ships a cursor-aware autocomplete engine at `bonsai-js/autocomplete`. It provides ranked, type-aware completion suggestions for any cursor position in an expression — designed for rule builders, expression editors, and admin tools. Tree-shakeable: if you don't import it, it's not in your bundle.
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
import { bonsai } from 'bonsai-js'
|
|
587
|
+
import { strings, arrays } from 'bonsai-js/stdlib'
|
|
588
|
+
import { createAutocomplete } from 'bonsai-js/autocomplete'
|
|
589
|
+
|
|
590
|
+
const expr = bonsai().use(strings).use(arrays)
|
|
591
|
+
|
|
592
|
+
const ac = createAutocomplete(expr, {
|
|
593
|
+
context: { user: { name: 'Alice', age: 25 }, items: [1, 2, 3] },
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
ac.complete('user.', 5)
|
|
597
|
+
// [{ label: 'name', detail: 'string', kind: 'property' },
|
|
598
|
+
// { label: 'age', detail: 'number', kind: 'property' }, ...]
|
|
599
|
+
|
|
600
|
+
ac.complete('user.name.', 10)
|
|
601
|
+
// [{ label: 'trim', detail: 'string → string', insertText: 'trim()', cursorOffset: 5 },
|
|
602
|
+
// { label: 'toUpperCase', detail: 'string → string' }, ...]
|
|
603
|
+
|
|
604
|
+
ac.complete('items |> ', 9)
|
|
605
|
+
// Only array-compatible transforms — string-only transforms automatically excluded.
|
|
606
|
+
|
|
607
|
+
ac.complete('users.filter(.', 14)
|
|
608
|
+
// [{ label: 'name', detail: 'string' }, { label: 'age', detail: 'number' }]
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### What it provides
|
|
612
|
+
|
|
613
|
+
| Context | What you get |
|
|
614
|
+
|---|---|
|
|
615
|
+
| `user.` | Object properties with value types |
|
|
616
|
+
| `user?.name?.` | Optional chaining — same completions as dot access |
|
|
617
|
+
| `user.name.` | Type-appropriate methods with return types |
|
|
618
|
+
| `user.name.trim().` | Methods inferred through chained calls (eval-based) |
|
|
619
|
+
| `user.name.to` | Fuzzy-filtered methods via static type inference |
|
|
620
|
+
| `items \|> ` | Transforms filtered by inferred input type |
|
|
621
|
+
| `users.filter(.` | Lambda element properties with types |
|
|
622
|
+
| `users.filter(.name.` | Lambda member — methods for the element property type |
|
|
623
|
+
| `groups.map(.users.filter(.` | Nested lambda element inference |
|
|
624
|
+
| `us` | Context variables, functions, keywords |
|
|
625
|
+
| `name.tLC` | Fuzzy matching (camelCase-aware) |
|
|
626
|
+
|
|
627
|
+
### How to integrate
|
|
628
|
+
|
|
629
|
+
The API returns pure data — no DOM, no framework dependency. Wire it into any UI:
|
|
630
|
+
|
|
631
|
+
```ts
|
|
632
|
+
// Custom dropdown
|
|
633
|
+
textarea.addEventListener('input', () => {
|
|
634
|
+
ac.setContext(getCurrentContext())
|
|
635
|
+
const completions = ac.complete(textarea.value, textarea.selectionStart)
|
|
636
|
+
showDropdown(completions)
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
// Monaco editor
|
|
640
|
+
const monacoKindMap = {
|
|
641
|
+
variable: monaco.languages.CompletionItemKind.Variable,
|
|
642
|
+
property: monaco.languages.CompletionItemKind.Property,
|
|
643
|
+
method: monaco.languages.CompletionItemKind.Method,
|
|
644
|
+
transform: monaco.languages.CompletionItemKind.Function,
|
|
645
|
+
function: monaco.languages.CompletionItemKind.Function,
|
|
646
|
+
keyword: monaco.languages.CompletionItemKind.Keyword,
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
monaco.languages.registerCompletionItemProvider('bonsai', {
|
|
650
|
+
triggerCharacters: ['.', '|', '('],
|
|
651
|
+
provideCompletionItems(model, position) {
|
|
652
|
+
ac.setContext(getCurrentContext())
|
|
653
|
+
const offset = model.getOffsetAt(position)
|
|
654
|
+
return {
|
|
655
|
+
suggestions: ac.complete(model.getValue(), offset).map(c => ({
|
|
656
|
+
label: c.label,
|
|
657
|
+
kind: monacoKindMap[c.kind],
|
|
658
|
+
insertText: c.insertText ?? c.label,
|
|
659
|
+
detail: c.detail,
|
|
660
|
+
})),
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
})
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Context can be updated dynamically — call `ac.setContext(newData)` whenever the user's data changes.
|
|
667
|
+
|
|
668
|
+
### Options
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
createAutocomplete(instance, {
|
|
672
|
+
// Expression evaluation context — the data your users are writing expressions against
|
|
673
|
+
context: { user: { name: 'Alice' }, items: [1, 2, 3] },
|
|
674
|
+
|
|
675
|
+
// Explicit transform type map — skips auto-probing for better performance.
|
|
676
|
+
// Keys are transform names, values are arrays of accepted input types.
|
|
677
|
+
// When omitted, types are discovered automatically by probing each transform.
|
|
678
|
+
transformTypes: {
|
|
679
|
+
upper: ['string'],
|
|
680
|
+
trim: ['string'],
|
|
681
|
+
count: ['array'],
|
|
682
|
+
sort: ['array'],
|
|
683
|
+
},
|
|
684
|
+
|
|
685
|
+
// Error callback for debugging missing or incorrect completions.
|
|
686
|
+
// Only called for unexpected internal errors — not for expected parse/eval failures.
|
|
687
|
+
onError: (error, phase) => console.warn(`[autocomplete] ${phase}:`, error),
|
|
688
|
+
})
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Security policy
|
|
692
|
+
|
|
693
|
+
Autocomplete respects the same `allowedProperties` and `deniedProperties` policy configured on the Bonsai instance. Completions are filtered to match what the evaluator would actually allow:
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
const expr = bonsai({ allowedProperties: ['name', 'age'] })
|
|
697
|
+
const ac = createAutocomplete(expr, {
|
|
698
|
+
context: { user: { name: 'Alice', secret: 'hidden' } },
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
ac.complete('user.', 5)
|
|
702
|
+
// 'secret' is excluded — only 'name' and 'age' appear
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
This applies to all contexts: property access, lambda element properties, and nested chain resolution. Methods (`trim`, `filter`, etc.) are always shown for their applicable types — they are controlled by `deniedProperties`, not `allowedProperties`.
|
|
706
|
+
|
|
707
|
+
### Completion type
|
|
708
|
+
|
|
709
|
+
```ts
|
|
710
|
+
interface Completion {
|
|
711
|
+
label: string // Display text and default insert text
|
|
712
|
+
kind: 'variable' | 'property' | 'method' | 'transform' | 'function' | 'keyword'
|
|
713
|
+
detail?: string // Type info: 'string', 'string → array', '"Alice"', 'array(3)'
|
|
714
|
+
insertText?: string // Override insert: 'trim()', 'filter(.)', 'min()'
|
|
715
|
+
cursorOffset?: number // Cursor position in insertText (e.g., between parens)
|
|
716
|
+
sortPriority: number // Lower = higher rank
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Error handling
|
|
721
|
+
|
|
722
|
+
`complete()` never throws — it always returns `Completion[]`. If an unexpected internal error occurs, it returns `[]` and reports the error via the `onError` callback. Expected errors (syntax errors from incomplete expressions, security policy blocks, type mismatches) are silently handled as part of normal autocomplete operation.
|
|
723
|
+
|
|
538
724
|
## Stability
|
|
539
725
|
|
|
540
|
-
Bonsai follows SemVer for the documented package entrypoints `bonsai-js` and `bonsai-js/
|
|
726
|
+
Bonsai follows SemVer for the documented package entrypoints `bonsai-js`, `bonsai-js/stdlib`, and `bonsai-js/autocomplete`.
|
|
541
727
|
|
|
542
728
|
- Supported runtimes are Node 20+ and current Bun releases.
|
|
543
729
|
- The packed npm artifact is smoke-tested on Node 20 and 22.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { c as InferredTypeName, n as BonsaiInstance } from "../types-BhsZZsLY.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/autocomplete/tokenizer.d.ts
|
|
4
|
+
/** Known phases where errors can occur during completion. */
|
|
5
|
+
type CompletionPhase = 'tokenize' | 'tryEvalPrefix' | 'inferPipeInputType' | `probeTransform:${string}` | 'complete';
|
|
6
|
+
type ErrorHandler = (error: unknown, phase: CompletionPhase) => void;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/autocomplete/completions.d.ts
|
|
9
|
+
type CompletionInsert = {
|
|
10
|
+
insertText?: undefined;
|
|
11
|
+
cursorOffset?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
insertText: string;
|
|
14
|
+
cursorOffset?: number;
|
|
15
|
+
};
|
|
16
|
+
interface CompletionBase {
|
|
17
|
+
label: string;
|
|
18
|
+
kind: 'variable' | 'property' | 'method' | 'transform' | 'function' | 'keyword';
|
|
19
|
+
detail?: string;
|
|
20
|
+
/** Lower values sort first. Can become negative after fuzzy-match boosting. */
|
|
21
|
+
sortPriority: number;
|
|
22
|
+
}
|
|
23
|
+
type Completion = CompletionBase & CompletionInsert;
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/autocomplete/index.d.ts
|
|
26
|
+
interface AutocompleteOptions {
|
|
27
|
+
context?: Record<string, unknown>;
|
|
28
|
+
/** Map of transform name → accepted input types (e.g., { upper: ['string'], filter: ['array'] }).
|
|
29
|
+
* When set, pipe-transform completions are filtered by the inferred input type.
|
|
30
|
+
* When omitted, auto-probing discovers type compatibility by calling each transform
|
|
31
|
+
* with a sample value — this runs once per type and is cached per instance.
|
|
32
|
+
* For performance-sensitive contexts (e.g., large transform registries), pass this
|
|
33
|
+
* explicitly to avoid the cold-start probe cost. */
|
|
34
|
+
transformTypes?: Record<string, InferredTypeName[]>;
|
|
35
|
+
/** Called when an unexpected internal error occurs during completion.
|
|
36
|
+
* Expected errors (e.g., syntax errors, security blocks, type mismatches) are not reported.
|
|
37
|
+
* Useful for debugging missing or incorrect completions. */
|
|
38
|
+
onError?: ErrorHandler;
|
|
39
|
+
}
|
|
40
|
+
interface AutocompleteInstance {
|
|
41
|
+
complete(expression: string, cursor: number): Completion[];
|
|
42
|
+
setContext(context: Record<string, unknown>): void;
|
|
43
|
+
}
|
|
44
|
+
declare function createAutocomplete(instance: BonsaiInstance, options?: AutocompleteOptions): AutocompleteInstance;
|
|
45
|
+
//#endregion
|
|
46
|
+
export { AutocompleteInstance, AutocompleteOptions, type Completion, createAutocomplete };
|
|
47
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +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,eAAA;;;KCd9C,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,gBAAA;;;UCTzB,mBAAA;EACf,OAAA,GAAU,MAAA;EFGe;;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,MAAA;AAAA;AAAA,iBAcN,kBAAA,CACd,QAAA,EAAU,cAAA,EACV,OAAA,GAAS,mBAAA,GACR,oBAAA"}
|