bonsai-js 1.0.0-rc.3 → 1.0.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 +44 -0
- package/README.md +5 -5
- package/dist/environment.mjs +11 -4
- package/dist/environment.mjs.map +1 -1
- package/dist/query/index.mjs +10 -2
- package/dist/query/index.mjs.map +1 -1
- package/dist/runtime/time.mjs +8 -1
- package/dist/runtime/time.mjs.map +1 -1
- package/package.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,50 @@ From 1.0.0, entries are generated at release time from the bump files in
|
|
|
7
7
|
[`.bumpy/`](./.bumpy/); entries up to 0.5.0 follow
|
|
8
8
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
9
9
|
|
|
10
|
+
## 1.0.0
|
|
11
|
+
<sub>2026-10-19</sub>
|
|
12
|
+
|
|
13
|
+
- *(major)*
|
|
14
|
+
A redesign of the language and the engine. Expressions and host code written for 0.x need migrating; the [migration guide](https://danfry1.github.io/bonsai-js/guide/migrating) covers every change, including results that change without an error.
|
|
15
|
+
|
|
16
|
+
**Language**
|
|
17
|
+
|
|
18
|
+
- Every function is callable as `f(x, a)` or `x.f(a)`. The pipe operator `|>` is removed and its token reserved.
|
|
19
|
+
- The standard library is built in, with JavaScript names (`toUpperCase`, `includes`, `filter`) plus `sum`, `sortBy`, `groupBy`, `unique`, `keys`, `round(x, digits)`, and calendar functions with time zones. `upper`, `lower`, `flatten`, `isString`, `isNumber`, `isArray`, `isNull`, `toBool`, `diffDays`, `charCodeAt`, `toSpliced`, `with`, `substring`, `charAt`, and `concat` are removed.
|
|
20
|
+
- Lambdas: `x => ...` and `(acc, x) => ...` alongside `.field`. The implicit `.` binds to the nearest argument whose parameter takes a function, and only where that function's first parameter is the current item (so not in `reduce`). No-argument `filter`, `some`, and `every` are removed.
|
|
21
|
+
- New: `let` bindings, `try(expr, fallback)`, `has(a.b)`, comments, timestamps and durations (`now() - t > days(30)`), deep `==` on lists and maps. Durations are whole milliseconds (a fraction rounds, halves away from zero), so their arithmetic is exact, within ±(2^53 - 1) ms (about 285,000 years); a duration outside that range is `INVALID_ARGUMENT`, and `new Duration(ms)` throws a `RangeError` for one. Time zones are `"UTC"` or IANA `Area/Location` names in the tz database's spelling; abbreviations, legacy names, other spellings, and offsets are `INVALID_ARGUMENT`.
|
|
22
|
+
- `null` is the single absent value: missing properties and host `undefined` read as `null`, and `x == null` holds for both. A map key holding `undefined` is absent to `==`, `keys`, `values`, `entries`, `has`, `in`, and spread. Ordering comparisons with `null` are `false`.
|
|
23
|
+
- `&&`, `||`, `!`, and `?:` take booleans (`null` counts as false). There is no coercion: `"a" + 1` and `null + 1` are errors. Division and remainder by zero and non-finite results are errors. Chained comparisons are parse errors, and so is `??` beside another binary operator without parentheses (`a ?? 0 > 10`).
|
|
24
|
+
- Results that changed: `round` rounds halves away from zero and honors its digits argument; `toFixed` rounds the written decimal; `avg([])` is `null`; `unique` compares by value; templates render `null` as empty text, dates as ISO-8601, and reject lists and maps; `toString(null)` is `""`; `toNumber` accepts decimal text only (no `0x`, `0b`, or `0o`); `formatNumber` and `formatCurrency` reject a locale the runtime has no data for instead of using the host's default, a locale tag over 64 characters, and a currency that is not a three-letter code, and never show a negative zero (an amount that rounds to zero at the shown precision prints unsigned, so not `-$0.00`); `round` leaves a number of 2^53 or more as it is at zero or more digits; `toFixed` writes every digit of a number of 1e21 or more instead of exponent form; `formatDate` reads `''` inside quotes as a quote and rejects a quote left open; `timestamp()` reads back the six-digit signed years a timestamp renders outside 0000-9999; `sum` and `avg` of durations check the range at each step, as `a + b + c` does; `sum` of a list of durations sums them whether or not the list's type is known; `sum` and `avg` add left to right; `now()` is a timestamp; `sort()` orders numbers numerically; `includes`, `indexOf`, and `in` compare lists and maps by value, and `Date`s at the same instant are equal; `replace` and `replaceAll` insert their replacement text literally (no `$&` patterns).
|
|
25
|
+
- Only plain objects and class instances are read as maps: reads see their own properties, and `keys()`, spread, and `==` see the enumerable ones, as in JavaScript. `Map`, `Set`, `RegExp`, promises, typed arrays, and errors are opaque; a plain object with a `then` method is an ordinary map and is never awaited. Map keys follow JavaScript property order. A computed read of `__proto__`, `constructor`, or `prototype` is `null`; writing one is a `BLOCKED_PROPERTY` error. `groupBy` keys are text, so `1` and `"1"` share a group.
|
|
26
|
+
- `duration("PT1H30M")` reads ISO-8601 duration text, the text a duration renders as (weeks down to milliseconds; years and months have no fixed length and are refused). `min`, `max`, and `avg` take durations as `sum` does, given as a list or one by one (`max(a, b)`). A reserved word is not a method name: `a.not(b)` is a syntax error. Syntax errors suggest `**` for `^` and `!=` for `<>`. A `\u{...}` escape takes any number of leading zeros, as in JavaScript, and `1.e3` (a read of `e3` on `1`) is a check error that says to write `1e3`.
|
|
27
|
+
|
|
28
|
+
**API**
|
|
29
|
+
|
|
30
|
+
- `bonsai({ variables, strict, functions, libraries, limits, cacheSize, clock, validateContext })` creates an immutable environment; `extend()` derives one. `use()`, plugins, `addFunction`, `addContextFunction`, `addTransform`, `remove*`, `has*`, `isContextFunction`, `getPolicy`, and `clearCache` are removed.
|
|
31
|
+
- Declaring `variables` makes an environment strict: undeclared names are check errors unless `strict: false`.
|
|
32
|
+
- Host functions are declared with `fn({ params, returns, run })`, validated on every call, and must be marked `async` to be awaited. With `call: true`, `run` first receives `{ context, signal }`: the read-only context, and a signal that aborts when the evaluation times out or is cancelled while waiting on the call. A result that does not match the declared type, or a promise from a function not marked `async`, is a `HOST_CONTRACT` error that `try()` does not catch.
|
|
33
|
+
- `env.compile(source, { expect })` type-checks and returns a `Program`, cached per source and expected type; `env.check()` replaces `validate()` and returns every diagnostic without throwing. Evaluation options include `now`, the time `now()` returns for that evaluation, and `validateContext`, which turns context validation on or off for that evaluation (context validation rejects `NaN` and infinities where a number is declared), and a `BonsaiLimitError` names the option that bounds it in `limit`. The context type comes from `variables` and the variables of the `libraries` used, instead of `bonsai<Ctx>()`. A schema typed `Record<string, Type>` accepts interface-typed and class contexts, and a variable `extend()` declares again replaces the earlier one in the context type. A context that is not an object is a `TypeError`.
|
|
34
|
+
- `bonsai-js/service` (`createLanguageService`) replaces `bonsai-js/autocomplete`; the `stdlib` and `autocomplete` subpaths are removed. A source that is not a string or an offset that is not a number is a `TypeError`, and a negative or fractional offset a `RangeError`.
|
|
35
|
+
- `print(tree, { calls })` turns a syntax tree back into source, round-tripping with `env.parse`. Syntax tree node types are renamed. `forEachChild` walks a tree and `mapChildren` rewrites one, both visiting children in evaluation order; member and call nodes record the span of their name (`nameStart`, `nameEnd`; optional in trees built by hand).
|
|
36
|
+
- `program.explain(context)` (and `explainSync`, `env.explain`, `env.explainSync`) evaluates and returns a trace of every sub-expression's value, with skipped branches and per-item lambda runs; `reasons()` lists the conditions that decided the result, a negated condition as written (`!("vip" in tags)`, with its own value). Trace text is capped at 120 characters, and no cut in a trace or its rendering splits a surrogate pair. `String(explanation)`, `JSON.stringify(explanation)`, `JSON.stringify(explanation.reasons())`, and `JSON.stringify(explanation.trace)` are bounded and cycle-safe, and any trace, lambda run, or list of them picked out of it serializes as its part of `JSON.stringify(explanation)`; a map shows its first 50 keys and is listed once per explanation, so serializing never grows with its size, and trace records stay plain objects. An explanation serializes to the same frozen snapshot every time, whatever was serialized before. `explain()` refuses a promise-like result as `evaluate()` does, and marks a result refused after evaluation (a promise-like value, a failed `expect` check) on the root of the trace.
|
|
37
|
+
- `program.partial(known, { unknown })` (or `env.partial(source, known)`) evaluates what the known data decides and returns a value, an error, or a residual expression over the unknown variables (its tree is `ast`, as on a program), to pre-evaluate rules once per tenant or request. Data missing from `known`, including a field missing from a known object, and a known object the expression reads whole, is unknown unless an `unknown` list is given; with one, an object above a listed path that `known` leaves out is unknown as a whole, not null. An `unknown` path starts with a variable name and names fields as reads spell them; an empty or blocked segment is a `TypeError`. It takes the same step, time, and cancellation budgets as evaluation; a size limit (`LIST_LIMIT`, `STRING_LIMIT`, `VALUE_DEPTH_LIMIT`, `PATTERN_LIMIT`) in a known part that evaluation may not reach stays in the residual rather than ending `partial()`, and a list literal over `maxListLength` reports that limit before its items, as evaluation does, in async programs too. Known data that throws when read (a getter, a Proxy trap) fails at that read, as in evaluation, so a read behind `&&` or inside `try()` decides as the program does, and no getter runs to decide which variables are given. A residual longer than `maxSourceLength` is a `SOURCE_TOO_LONG` limit error (`toSQL` and `toMongo`, which never evaluate its text, are not bound by it), and a residual can be explained like a program, at the step cost of evaluating it. A residual runs every call with the overload the program chose, and a part whose inputs all turn out known (`(2 ?? row.total) >= 1`) is decided rather than left in the residual. A residual's bindings are the known values themselves (shared with `known`, not copied), and `call: true` host functions in a residual read exactly the context it is given (`readsContext` says when it calls one; that context must then hold every variable `known` gave, or the call is an `INVALID_CONTEXT` error). With `validateContext`, `partial()` validates the known data (all but what lies under a listed unknown path), and a residual validates the paths it reads, each step against its own declared type, so a context holding only `dependsOn` passes and invalid data on those paths fails as it would for the program (the whole context when `readsContext`). `PartialResult` and `ResidualResult` take the context type first, as `Program` does. Partial evaluation keeps memory in proportion to the program and charges the work of indexing and compiling it.
|
|
38
|
+
- `bonsai-js/query` (`toSQL`, `toMongo`) translates a filter over a declared record into a parameterized SQL `WHERE` clause (Postgres, SQLite) or a MongoDB filter that selects exactly the records the filter accepts. This covers the null-safe text forms (`x?.endsWith(k) ?? false`, `(x ?? "").endsWith(k)`), columns with a known default (`(order.total ?? 0) >= 500`), duration columns, and relative dates (`now() - order.placed < days(14)`). A path the filter reads that is missing from `known` is refused rather than read as null (in a typed environment, so is a known object read whole without every declared field), and every refusal says why. A known value that throws when read is refused with the host's error as the `cause`. One `maxSteps`, `timeout`, and `signal` budget covers the whole translation, including the partial evaluation it runs, and in SQL a known value is sent once however often the filter uses it. SQL arithmetic covers unary `-` as well as `+ - *`; in Postgres a record whose arithmetic leaves the double range fails the query with `value out of range` (never a different set of rows), where SQLite excludes it. `params` is typed so `node:sqlite` and Postgres drivers take it without a cast. Known dates and durations are checked as evaluation checks them, so a value evaluation treats as opaque is refused. A comparison whose sides are both known is decided before translating, a known list with repeated entries matches as its entries, and a text function on `(field ?? default)` runs on the default where the field is null, so it is never null. A part that fails whenever it is reached after a read of the record (a list literal over `maxListLength`, say) is refused as `UNTRANSLATABLE` (naming its error), never sent to the database, and a relative date excludes a record whose distance from the known time leaves the duration range, as evaluation fails it. Records are not validated: with `validateContext`, the database must enforce the declared types for exact results. With `callHostFunctions: true`, sync host functions with known inputs are called before translating. `BonsaiTranslationError` is exported from `bonsai-js` as well as `bonsai-js/query`.
|
|
39
|
+
- Errors are `BonsaiError` subclasses with stable codes and `{ start, end }` spans. Diagnostics carry the same `span`, `position`, and `formatted` code frame, plus a `suggestion` for an unknown name; `env.check()` also returns the checked tree, the type of each node, and (when ok) the compiled program. Any `BonsaiError` that a getter or Proxy in the context throws (from another evaluation, a parse, a check, or a translation it runs, or one the host built) is a `HOST_ERROR` with it as `cause`, as for host functions, in evaluation, explanation, validation, and `partial()` alike; only the errors an evaluation raises itself keep their code. Any other thrown value becomes a `HOST_ERROR` too, including one whose inspection throws (a revoked Proxy), so `try()` recovers from it, and every trace node an explanation records for it says `HOST_ERROR`; `explain({ exhaustive: true })` ignores it in the extra work, whatever it is. A stack overflow in a host function is a `HOST_ERROR` like anything else it throws; one in a getter or Proxy trap is `TOO_DEEP` (the engine's error is its `cause`), which `try()` does not recover from and `partial()` leaves to a branch that may not run. Only the engines' own overflow errors count as overflows, not any `RangeError`. Async evaluation never awaits a value from the data: a thenable in the context stays opaque inside every node, lambda, and host call result (an `async` host function's own result is awaited by JavaScript, so it must not return one). Async evaluation and explanation check list limits, expected types, and the deadline in the same order as sync evaluation, so they run the same host calls and report the same error. A host call's signal is aborted with a copy of the evaluation's error, so host code rethrowing it elsewhere is that evaluation's `HOST_ERROR`. `ExpressionError`, `BonsaiTypeError`, `BonsaiReferenceError`, `BonsaiSecurityError`, `formatError`, `formatBonsaiError`, `evaluateExpression`, `tokenize`, and `compile` are removed (`parse(source)` returns the new syntax tree), as are the `allowedProperties` and `deniedProperties` options.
|
|
40
|
+
- `JSON.stringify(error)` gives one documented shape (`BonsaiErrorJSON`: name, code, message, span, position, plus `limit` or `diagnostics`) wherever an error is serialized. Check results, their diagnostics, and partial results are frozen. A program's environment types are checked against `toSQL` and `toMongo` column types. `DiagnosticJSON` types the diagnostics in that JSON, without the `formatted` code frame. `isBonsaiError` also recognizes errors from another copy of the package.
|
|
41
|
+
- Limits move under `limits`; `maxArrayLength` is now `maxListLength`, and `maxDepth` now bounds syntax nesting (`TOO_DEEP`); nested data is bounded by `maxValueDepth` (`VALUE_DEPTH_LIMIT`), counted alike when building, comparing, `unique()`, and validating. Built-ins check `maxListLength` before they build a list, `groupBy` groups included.
|
|
42
|
+
- `maxDepth` is at most 256 and `maxValueDepth` at most 1,024 (a larger value is a `RangeError`), so every stage stays inside the JavaScript call stack; a stack overflow anywhere is a `TOO_DEEP` limit error, never a raw `RangeError`. Host `Duration` values are read by their own frozen length and rendered by the engine, never by their own methods; one built without the constructor is an opaque value.
|
|
43
|
+
- `expect` means "has at least these fields": an extra field in a map literal is an `EXPECTED_TYPE` warning, and the result keeps it. Literal locales, currencies, and duration text are checked before anything runs. In a non-strict environment with declared variables, an undeclared name one edit from a declared one is an `UNKNOWN_VARIABLE` warning with a `suggestion`. Diagnostic messages shorten type text to 120 characters.
|
|
44
|
+
- Spans are balanced: a parenthesized expression's span includes its parentheses, so slicing the source at any node, error, diagnostic, or explanation span gives source that parses on its own. A later computed key or spread record makes earlier map fields it may overwrite (and keys it may leave absent) part of the checked type, and `print()` keeps the parentheses of a right-nested `&&`, `||`, or `??` so the printed tree parses back to the same shape. `createLanguageService(env, { expect })` adds `EXPECTED_TYPE` to the service's diagnostics, its completions work inside `try()` and computed keys with the same types check() gives, and a negative or fractional offset is a `RangeError`. An ordering with a value that may be `null` is a `MAYBE_NULL` warning only where it is written so its `false` becomes a verdict (under `!`, as a `?:` test, in `every` or `none`, or compared with `false`, looking through `?:` branches, `let` bodies, `try`, and `== true`), not where it means "skip" (`users.filter(.age >= 18)`); it is a heuristic over how the comparison is written, and a literal written with an exponent is an `UNSAFE_INTEGER` warning only when the number cannot keep the digits written (`9007199254740993e0`, not `1e308` or an exact `1.180591620717411303424e21`). A computed or `groupBy` key that is always one evaluation blocks (`{["__proto__"]: 1}`, `` {[`constructor`]: 1} ``) is a `BLOCKED_PROPERTY` error at check time, a template without substitutions is typed as its text, and a key a map literal writes is always present when spread, and a spread after an open declared object keeps any key it may lack typed as whatever the object held. Code frames and shortened type text never split a surrogate pair, a CRLF line is shown without its `\r`, and the caret lines up by characters. Completions also work inside brackets, calls, templates, and branches of a `let` value, nested ones included (`let v = try(b ? user.`), and inside `has()`, and detail each field with the type the read has there, narrowing included. A completion costs at most 100 parses, and about 12 of a source near the length limit.
|
|
45
|
+
- `withContext<Ctx>()` builds host functions typed against a context, `Infer` gives the TypeScript type of a `t` type, `formatType` and `isAssignable` print and compare types, and `env.describeFunction` / `env.listFunctions()` (read-only) describe the callable functions for editors and builders. Error classes keep their `name` (and the JSON `name`, typed `BonsaiErrorName`) in minified bundles; a call stack overflow is `TOO_DEEP` with no `limit`, since no option raises it. `ExplanationJSON` types `explanation.toJSON()`, `SQLParam` and `QueryOptions` name the query types, and the standalone `parse(source, limits)` ignores the limits parsing does not use. The [stability policy](https://danfry1.github.io/bonsai-js/guide/stability) says what a minor release may change; the result type parameter of `env.evaluate<R>()` and the other environment calls is an unchecked assertion.
|
|
46
|
+
- Requires Node.js 22 or newer.
|
|
47
|
+
|
|
48
|
+
**Engine**
|
|
49
|
+
|
|
50
|
+
- Expressions compile to closures. Async code is generated only for subtrees that reach an async host function. Compiling an expression that uses time zones loads their data (each zone it names once, and at most 16 per expression), so a first evaluation does not spend its `timeout` on it; a zone that comes from data, or that `partial()` writes into a residual, loads on first use.
|
|
51
|
+
- The package ships one module per source file, so bundlers drop what an app does not reach: importing only `t` adds under 1 KB gzipped, and all the error classes about 1.6 KB. The `bonsai-js/service` entry is about 46 KB gzipped, since it includes the type checker. A minimal browser bundle that evaluates expressions is about 68 KB gzipped, and about 56 KB from `bonsai-js/lite`, which exports the same API without explaining and partial evaluation (`LiteEnvironment`, `LiteProgram`, and `LiteCheckResult` type its environments; the language service accepts them, and `toSQL` and `toMongo`, which translate through partial evaluation, say they cannot take its programs).
|
|
52
|
+
- Every limit is on by default: source size, depth, node count, a deterministic step budget charged by the real cost of each operation, produced string and list sizes, value depth, regular expression pattern length, plus an optional timeout (in milliseconds, fractions allowed; only `0` turns it off) and `AbortSignal`. A built-in call is charged by the length of its string arguments, and an `ABORTED` error carries the signal's reason as its `cause`.
|
|
53
|
+
|
|
10
54
|
## [0.5.0] - 2026-06-02
|
|
11
55
|
|
|
12
56
|
### Security
|
package/README.md
CHANGED
|
@@ -67,12 +67,12 @@ Bonsai is the largest of these in the browser (about 56 KB from `bonsai-js/lite`
|
|
|
67
67
|
## Install
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
npm install bonsai-js
|
|
70
|
+
npm install bonsai-js
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Upgrading from 0.x? The [migration guide](https://danfry1.github.io/bonsai-js/guide/migrating) covers every change.
|
|
74
74
|
|
|
75
|
-
Requires Node.js 22 or newer, current Bun, or a modern browser. The package is ESM; `require('bonsai-js')` works where Node can load ES modules synchronously (22.12 and newer).
|
|
75
|
+
Requires Node.js 22 or newer, current Bun, or a modern browser. The package is ESM; `require('bonsai-js')` works where Node can load ES modules synchronously (22.12 and newer); for the TypeScript settings that allow it, see [Install](https://danfry1.github.io/bonsai-js/guide/install#typescript-settings).
|
|
76
76
|
|
|
77
77
|
In a browser bundle, an app that evaluates expressions adds about 68 KB minified and gzipped: the parser, type checker, compiler, and built-in library, with explaining and partial evaluation included. `bonsai-js/lite` leaves those two out, for about 56 KB. `bonsai-js/query` and `bonsai-js/service` are separate entry points; the language service on its own is about 46 KB, since it includes the type checker. Unused modules are tree-shaken: importing only `t` adds under 1 KB, and all the error classes about 1.6 KB. CI enforces these budgets (`bun run check:size`).
|
|
78
78
|
|
|
@@ -94,7 +94,7 @@ startOfDay(order.placedAt, "Europe/Berlin") // calendar math in time
|
|
|
94
94
|
// comments work too
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
The full reference is [docs/language.md](https://github.com/danfry1/bonsai-js/blob/
|
|
97
|
+
The full reference is [docs/language.md](https://github.com/danfry1/bonsai-js/blob/v1.0.0/docs/language.md). The rules that matter most:
|
|
98
98
|
|
|
99
99
|
- There is one absent value, `null`. Missing properties and host `undefined` read as `null`, so `x == null` means "missing or null". A key holding `undefined` is absent everywhere: `{ a: 1, b: undefined }` equals `{ a: 1 }`, and `"b" in` it is false.
|
|
100
100
|
- `==` compares by value (lists and maps deeply). There is no type coercion: `1 == "1"` is `false` and `"a" + 1` is an error.
|
|
@@ -201,7 +201,7 @@ Every limit except `timeout` is on by default; `limits` changes the budget. `0`
|
|
|
201
201
|
| `maxPatternLength` | 4,096 | regular expression patterns passed to `matches` |
|
|
202
202
|
| `timeout` | none | wall-clock milliseconds per evaluation |
|
|
203
203
|
|
|
204
|
-
The step budget is deterministic: the same expression over the same data uses the same steps on every machine, and at the default budget any expression finishes or fails within about 100 ms on Node (about 150 ms for the slowest shapes we have found). Host functions are trusted code: limits cannot interrupt a synchronous host function that is already running, and a getter or Proxy you put in the context runs when it is read. Set a `timeout` when expressions call slow host functions. See [SECURITY.md](https://github.com/danfry1/bonsai-js/blob/main/SECURITY.md) and [docs/threat-model.md](https://github.com/danfry1/bonsai-js/blob/
|
|
204
|
+
The step budget is deterministic: the same expression over the same data uses the same steps on every machine, and at the default budget any expression finishes or fails within about 100 ms on Node (about 150 ms for the slowest shapes we have found). Host functions are trusted code: limits cannot interrupt a synchronous host function that is already running, and a getter or Proxy you put in the context runs when it is read. Set a `timeout` when expressions call slow host functions. See [SECURITY.md](https://github.com/danfry1/bonsai-js/blob/main/SECURITY.md) and [docs/threat-model.md](https://github.com/danfry1/bonsai-js/blob/v1.0.0/docs/threat-model.md).
|
|
205
205
|
|
|
206
206
|
## Built-in functions
|
|
207
207
|
|
package/dist/environment.mjs
CHANGED
|
@@ -460,19 +460,26 @@ function checkedClock(clock) {
|
|
|
460
460
|
return value;
|
|
461
461
|
};
|
|
462
462
|
}
|
|
463
|
+
/** The most zones one program warms when it is compiled. */
|
|
464
|
+
const WARMED_ZONES = 16;
|
|
463
465
|
/**
|
|
464
466
|
* Loads the time zone data a program's calls need, so its first evaluation
|
|
465
467
|
* does not pay for it inside its deadline. Only built-in functions that take a
|
|
466
468
|
* zone count: a zone written as text is loaded itself, and any other zone
|
|
467
|
-
* argument loads the data every zone shares.
|
|
469
|
+
* argument loads the data every zone shares. Each zone is warmed once, and at
|
|
470
|
+
* most WARMED_ZONES of them, so a source full of zone names compiles quickly.
|
|
468
471
|
*/
|
|
469
472
|
function warmZones(analysis) {
|
|
473
|
+
const seen = /* @__PURE__ */ new Set();
|
|
470
474
|
for (const [call, plan] of analysis.calls) {
|
|
471
475
|
const index = zoneParameter(plan.def);
|
|
472
476
|
const arg = index === void 0 ? void 0 : call.args[index];
|
|
473
477
|
if (arg === void 0) continue;
|
|
474
|
-
|
|
475
|
-
|
|
478
|
+
const zone = arg.type === "Literal" ? arg.value : void 0;
|
|
479
|
+
if (zone !== void 0 && typeof zone !== "string" || seen.has(zone)) continue;
|
|
480
|
+
seen.add(zone);
|
|
481
|
+
warmZone(zone);
|
|
482
|
+
if (seen.size >= WARMED_ZONES) return;
|
|
476
483
|
}
|
|
477
484
|
}
|
|
478
485
|
/** Reads compile() and check() options: only `expect`, which must be a type built with t. */
|
|
@@ -530,7 +537,7 @@ function createEnvironment(settings) {
|
|
|
530
537
|
}
|
|
531
538
|
}
|
|
532
539
|
function makeProgram(source, analysis, expect, constants, residualOf) {
|
|
533
|
-
warmZones(analysis);
|
|
540
|
+
if (residualOf === void 0) warmZones(analysis);
|
|
534
541
|
const guard = expect !== void 0 && analysis.checkResult ? expect : void 0;
|
|
535
542
|
const checked = (result, state) => {
|
|
536
543
|
if (guard !== void 0 && !conforms(result, guard, state)) throw raised(new BonsaiRuntimeError("TYPE_ERROR", `The result does not match the expected type ${formatType(guard)}`, { source }));
|