bonsai-js 1.0.0 → 1.1.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +24 -4
  3. package/dist/artifact.d.mts +47 -0
  4. package/dist/artifact.mjs +139 -0
  5. package/dist/artifact.mjs.map +1 -0
  6. package/dist/check/bind.mjs +117 -0
  7. package/dist/check/bind.mjs.map +1 -0
  8. package/dist/check/checker.mjs +14 -116
  9. package/dist/check/checker.mjs.map +1 -1
  10. package/dist/compile/compiler.mjs +2 -3
  11. package/dist/compile/compiler.mjs.map +1 -1
  12. package/dist/declared.mjs +8 -1
  13. package/dist/declared.mjs.map +1 -1
  14. package/dist/digest.mjs +109 -0
  15. package/dist/digest.mjs.map +1 -0
  16. package/dist/environment.d.mts +1 -1
  17. package/dist/environment.mjs +52 -309
  18. package/dist/environment.mjs.map +1 -1
  19. package/dist/errors.d.mts +21 -2
  20. package/dist/errors.mjs +17 -1
  21. package/dist/errors.mjs.map +1 -1
  22. package/dist/features.mjs +3 -3
  23. package/dist/features.mjs.map +1 -1
  24. package/dist/functions/builtins.mjs +2 -2
  25. package/dist/functions/define.mjs +13 -6
  26. package/dist/functions/define.mjs.map +1 -1
  27. package/dist/index.d.mts +3 -2
  28. package/dist/index.mjs +3 -2
  29. package/dist/lite/index.d.mts +3 -2
  30. package/dist/lite/index.mjs +3 -2
  31. package/dist/lite/index.mjs.map +1 -1
  32. package/dist/load.mjs +308 -0
  33. package/dist/load.mjs.map +1 -0
  34. package/dist/program.mjs +323 -0
  35. package/dist/program.mjs.map +1 -0
  36. package/dist/query/index.mjs +1 -1
  37. package/dist/runtime/index.d.mts +61 -0
  38. package/dist/runtime/index.mjs +95 -0
  39. package/dist/runtime/index.mjs.map +1 -0
  40. package/dist/service/index.mjs +8 -5
  41. package/dist/service/index.mjs.map +1 -1
  42. package/dist/syntax/parser.mjs +1 -1
  43. package/dist/syntax/printer.mjs +1 -1
  44. package/dist/types.mjs +103 -23
  45. package/dist/types.mjs.map +1 -1
  46. package/dist/version.mjs +7 -0
  47. package/dist/version.mjs.map +1 -0
  48. package/package.json +10 -2
@@ -1,17 +1,14 @@
1
1
  import { BonsaiCheckError, BonsaiError, BonsaiLimitError, BonsaiRuntimeError, locate } from "./errors.mjs";
2
2
  import { isName } from "./syntax/lexer.mjs";
3
- import { errorText, isMap } from "./runtime/values.mjs";
4
- import { warmZone } from "./runtime/time.mjs";
5
- import { formatType, isNullable } from "./types.mjs";
6
- import { assertHostSpec, assertType, conforms, describeMismatch, isRecord, overload } from "./functions/define.mjs";
7
- import { BUILTINS, zoneParameter } from "./functions/builtins.mjs";
8
- import { isStackOverflow, tooDeep } from "./runtime/overflow.mjs";
3
+ import { isExact, isNullable, typeData } from "./types.mjs";
4
+ import { assertHostSpec, assertType, isRecord, overload } from "./functions/define.mjs";
5
+ import { BUILTINS } from "./functions/builtins.mjs";
9
6
  import { DEFAULT_PARSE_LIMITS, parse } from "./syntax/parser.mjs";
10
7
  import { analyze } from "./check/checker.mjs";
11
- import { escaping, isOwn, raised } from "./runtime/attribution.mjs";
12
- import { compileProgram } from "./compile/compiler.mjs";
13
- import { DEFAULT_RUNTIME_LIMITS, MAX_VALUE_DEPTH_LIMIT, State } from "./runtime/state.mjs";
14
- import { recordDeclared } from "./declared.mjs";
8
+ import { raised } from "./runtime/attribution.mjs";
9
+ import { DEFAULT_RUNTIME_LIMITS, MAX_VALUE_DEPTH_LIMIT } from "./runtime/state.mjs";
10
+ import { recordCompiled, recordDeclared } from "./declared.mjs";
11
+ import { assertKeys, numberOption, programRunner, timeoutOption, warmZones } from "./program.mjs";
15
12
  //#region src/environment.ts
16
13
  /**
17
14
  * Declares a host function.
@@ -107,24 +104,6 @@ function mergeVariables(base, options) {
107
104
  for (const [k, v] of Object.entries(options.variables ?? {})) add(k, v, "the variables option");
108
105
  return out === void 0 ? void 0 : Object.freeze(out);
109
106
  }
110
- /** A number option: TypeError when it is not a number, RangeError when out of range. */
111
- function numberOption(name, value, fallback, minimum) {
112
- if (value === void 0) return fallback;
113
- if (typeof value !== "number") throw new TypeError(`${name} must be a number`);
114
- if (!Number.isInteger(value) || value < minimum) throw new RangeError(minimum === 0 ? `${name} must be a non-negative integer (0 disables it)` : `${name} must be a positive integer`);
115
- return value;
116
- }
117
- /**
118
- * A timeout in milliseconds: 0 turns it off; any other non-negative number,
119
- * fractions included, is a limit, so a budget computed down to its last
120
- * fraction of a millisecond stays a limit rather than becoming 0.
121
- */
122
- function timeoutOption(name, value, fallback) {
123
- if (value === void 0) return fallback;
124
- if (typeof value !== "number") throw new TypeError(`${name} must be a number`);
125
- if (!Number.isFinite(value) || value < 0) throw new RangeError(`${name} must be a non-negative number of milliseconds (0 disables it)`);
126
- return value;
127
- }
128
107
  const OPTION_KEYS = /* @__PURE__ */ new Set([
129
108
  "variables",
130
109
  "strict",
@@ -145,10 +124,6 @@ const LIBRARY_KEYS = /* @__PURE__ */ new Set([
145
124
  "functions",
146
125
  "variables"
147
126
  ]);
148
- function assertKeys(value, allowed, what) {
149
- if (!isRecord(value)) throw new TypeError(`${what} must be an object`);
150
- for (const key of Object.keys(value)) if (!allowed.has(key)) throw new TypeError(`Unknown ${what.toLowerCase()} key "${key}" (expected one of: ${[...allowed].join(", ")})`);
151
- }
152
127
  /** Environment options come from configuration: mistakes there are programming errors. */
153
128
  function assertOptions(options) {
154
129
  assertKeys(options, OPTION_KEYS, "Options");
@@ -253,7 +228,6 @@ var ProgramCache = class {
253
228
  }
254
229
  }
255
230
  };
256
- const EMPTY_CONTEXT = Object.freeze({});
257
231
  const NO_DIAGNOSTICS = Object.freeze([]);
258
232
  /**
259
233
  * What check() found. `ok`, `type`, and `diagnostics` are the JSON form; the
@@ -292,66 +266,6 @@ var CheckSuccess = class extends CheckFailure {
292
266
  return this.#program();
293
267
  }
294
268
  };
295
- const EVALUATE_OPTION_KEYS = /* @__PURE__ */ new Set([
296
- "timeout",
297
- "maxSteps",
298
- "signal",
299
- "now",
300
- "validateContext"
301
- ]);
302
- /** Whether a value is a valid Date (an object that only inherits from Date.prototype is not). */
303
- function isValidDate(value) {
304
- try {
305
- return value instanceof Date && !Number.isNaN(Date.prototype.getTime.call(value));
306
- } catch {
307
- return false;
308
- }
309
- }
310
- /** Whether a value has an AbortSignal's members; a signal whose getters throw does not. */
311
- function looksLikeSignal(signal) {
312
- try {
313
- const s = signal;
314
- return typeof s.aborted === "boolean" && typeof s.addEventListener === "function" && typeof s.removeEventListener === "function";
315
- } catch {
316
- return false;
317
- }
318
- }
319
- /** Validates per-evaluation options the way `limits` are validated; nothing fails open. */
320
- function evaluationLimits(options, settings) {
321
- if (options === void 0) return {
322
- maxSteps: settings.runtimeLimits.maxSteps,
323
- timeout: settings.timeout,
324
- signal: void 0,
325
- now: void 0,
326
- validateContext: void 0
327
- };
328
- let o;
329
- try {
330
- assertKeys(options, EVALUATE_OPTION_KEYS, "Evaluate option");
331
- const read = options;
332
- o = {
333
- maxSteps: read.maxSteps,
334
- timeout: read.timeout,
335
- signal: read.signal,
336
- now: read.now,
337
- validateContext: read.validateContext
338
- };
339
- } catch (error) {
340
- if (error instanceof TypeError || error instanceof RangeError) throw error;
341
- throw new TypeError(`Evaluate options could not be read: ${errorText(error)}`, { cause: error });
342
- }
343
- const signal = o.signal;
344
- if (signal !== void 0 && (typeof signal !== "object" || signal === null || !looksLikeSignal(signal))) throw new TypeError("signal must be an AbortSignal");
345
- if (o.now !== void 0 && !isValidDate(o.now)) throw new TypeError("now must be a valid Date");
346
- if (o.validateContext !== void 0 && typeof o.validateContext !== "boolean") throw new TypeError("validateContext must be a boolean");
347
- return {
348
- maxSteps: numberOption("maxSteps", o.maxSteps, settings.runtimeLimits.maxSteps, 0),
349
- timeout: timeoutOption("timeout", o.timeout, settings.timeout),
350
- signal,
351
- now: o.now,
352
- validateContext: o.validateContext
353
- };
354
- }
355
269
  /** Freezes a syntax tree (or any tree of plain objects and arrays) in place, without recursion. */
356
270
  function deepFreeze(root) {
357
271
  const seen = /* @__PURE__ */ new Set();
@@ -365,87 +279,6 @@ function deepFreeze(root) {
365
279
  }
366
280
  return root;
367
281
  }
368
- function validationBudget(source, limits) {
369
- return {
370
- maxDepth: limits.maxDepth,
371
- remaining: limits.maxSteps > 0 ? limits.maxSteps : Number.POSITIVE_INFINITY,
372
- deadline: limits.timeout > 0 ? performance.now() + limits.timeout : 0,
373
- onExhausted: (reason) => {
374
- if (reason === "depth") throw raised(new BonsaiLimitError("VALUE_DEPTH_LIMIT", `Context nests deeper than ${limits.maxDepth}`, { source }));
375
- if (reason === "time") throw raised(new BonsaiLimitError("TIMEOUT", "Context validation timed out", { source }));
376
- throw raised(new BonsaiLimitError("STEP_LIMIT", "Context validation exceeded the step limit", { source }));
377
- }
378
- };
379
- }
380
- function invalidContext(problem, source) {
381
- if (problem !== void 0) throw raised(new BonsaiRuntimeError("INVALID_CONTEXT", `Invalid context: ${problem}`, { source }));
382
- }
383
- function validateContext(ctx, variables, source, limits) {
384
- const budget = validationBudget(source, limits);
385
- for (const [name, type] of Object.entries(variables)) {
386
- const value = Object.hasOwn(ctx, name) ? ctx[name] : void 0;
387
- invalidContext(describeMismatch(value, type, name, budget), source);
388
- }
389
- }
390
- /**
391
- * Why the value read along `segments` (from index `at`) does not conform, or
392
- * undefined. Each step is judged by its own declared type: an absent or null
393
- * value is fine only where that type is nullable; a declared field is followed
394
- * (a missing required one fails); an undeclared key of an object (objects are
395
- * open) or an absent record entry ends the check; past a type that is not a
396
- * map, and at the end of the path, the value is checked whole, as a context is.
397
- * Fields the path does not name are not checked.
398
- */
399
- function describeAlong(value, type, segments, at, path, budget) {
400
- if (at === segments.length) return describeMismatch(value, type, path, budget);
401
- if (--budget.remaining < 0) budget.onExhausted("steps");
402
- if (type.kind === "union") {
403
- let first;
404
- for (const member of type.types) {
405
- const problem = describeAlong(value, member, segments, at, path, budget);
406
- if (problem === void 0) return void 0;
407
- first ??= problem;
408
- }
409
- return first;
410
- }
411
- const actual = value === void 0 ? null : value;
412
- if (type.kind !== "map" || !isMap(actual)) return describeMismatch(value, type, path, budget);
413
- const key = segments[at];
414
- const field = Object.hasOwn(actual, key) ? actual[key] : void 0;
415
- if (Object.hasOwn(type.fields, key)) return describeAlong(field, type.fields[key], segments, at + 1, `${path}.${key}`, budget);
416
- if (type.rest === void 0 || field === void 0) return void 0;
417
- return describeAlong(field, type.rest, segments, at + 1, `${path}.${key}`, budget);
418
- }
419
- /**
420
- * Validates the values a residual reads: each path in `dependsOn`, step by
421
- * step against the types declared along it (see describeAlong), so a context
422
- * holding exactly those paths passes and invalid data on them fails as it
423
- * would for the program.
424
- */
425
- function validatePaths(ctx, variables, paths, source, limits) {
426
- const budget = validationBudget(source, limits);
427
- for (const path of paths) {
428
- const segments = path.split(".");
429
- const name = segments[0];
430
- if (!Object.hasOwn(variables, name)) continue;
431
- invalidContext(describeAlong(Object.hasOwn(ctx, name) ? ctx[name] : void 0, variables[name], segments, 1, name, budget), source);
432
- }
433
- }
434
- /**
435
- * Reading host data (a getter, a Proxy trap, a `then` hook) can throw. Such a
436
- * failure is a HOST_ERROR, never a raw JavaScript error. Only the errors this
437
- * evaluation raised pass through: any other Bonsai error (from another
438
- * evaluation, from parsing, checking, or translating, or built by the host)
439
- * that host data threw is the host data's failure too.
440
- */
441
- function hostDataFailure(error, source) {
442
- if (isOwn(error)) return error;
443
- if (isStackOverflow(error)) return raised(tooDeep(source, error));
444
- return raised(new BonsaiRuntimeError("HOST_ERROR", `Reading host data failed: ${errorText(error)}`, {
445
- source,
446
- cause: error
447
- }));
448
- }
449
282
  const systemClock = () => /* @__PURE__ */ new Date();
450
283
  /** A host clock whose results are checked: now() must be a valid timestamp. */
451
284
  function checkedClock(clock) {
@@ -460,53 +293,41 @@ function checkedClock(clock) {
460
293
  return value;
461
294
  };
462
295
  }
463
- /** The most zones one program warms when it is compiled. */
464
- const WARMED_ZONES = 16;
465
- /**
466
- * Loads the time zone data a program's calls need, so its first evaluation
467
- * does not pay for it inside its deadline. Only built-in functions that take a
468
- * zone count: a zone written as text is loaded itself, and any other zone
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.
471
- */
472
- function warmZones(analysis) {
296
+ /** Whether a type has an exact map type anywhere in it. */
297
+ function holdsExact(type) {
298
+ const stack = [type];
473
299
  const seen = /* @__PURE__ */ new Set();
474
- for (const [call, plan] of analysis.calls) {
475
- const index = zoneParameter(plan.def);
476
- const arg = index === void 0 ? void 0 : call.args[index];
477
- if (arg === void 0) continue;
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;
300
+ while (stack.length > 0) {
301
+ const next = stack.pop();
302
+ if (seen.has(next)) continue;
303
+ seen.add(next);
304
+ if (isExact(next)) return true;
305
+ if (next.kind === "list") stack.push(next.element);
306
+ else if (next.kind === "union") stack.push(...next.types);
307
+ else if (next.kind === "map") {
308
+ stack.push(...Object.values(next.fields));
309
+ if (next.rest !== void 0) stack.push(next.rest);
310
+ }
483
311
  }
312
+ return false;
484
313
  }
485
- /** Reads compile() and check() options: only `expect`, which must be a type built with t. */
486
- function compileExpect(options) {
314
+ /** Reads compile(), check(), and the runtime's load() options: only `expect`, which must be a type built with t. Returns a frozen copy of it. */
315
+ function expectOption(options, what) {
487
316
  if (options === void 0) return void 0;
488
- if (typeof options !== "object" || options === null || Array.isArray(options)) throw new TypeError("Compile options must be an object");
317
+ if (typeof options !== "object" || options === null || Array.isArray(options)) throw new TypeError(`${what} options must be an object`);
489
318
  let expect;
490
319
  try {
491
- for (const key of Object.keys(options)) if (key !== "expect") throw new TypeError(`Unknown compile option key "${key}" (expected one of: expect)`);
320
+ for (const key of Object.keys(options)) if (key !== "expect") throw new TypeError(`Unknown ${what.toLowerCase()} option key "${key}" (expected one of: expect)`);
492
321
  expect = options.expect;
493
322
  } catch (error) {
494
323
  if (error instanceof TypeError) throw error;
495
- throw new TypeError("Compile options could not be read", { cause: error });
496
- }
497
- if (expect !== void 0) assertType(expect, "Compile option \"expect\"");
498
- return expect;
499
- }
500
- function contextOf(value) {
501
- if (value === void 0 || value === null) return EMPTY_CONTEXT;
502
- let map;
503
- try {
504
- map = typeof value === "object" && isMap(value);
505
- } catch (error) {
506
- throw new BonsaiRuntimeError("HOST_ERROR", "Reading the evaluation context failed", { cause: error });
324
+ throw new TypeError(`${what} options could not be read`, { cause: error });
507
325
  }
508
- if (!map) throw new TypeError("The evaluation context must be a plain object or class instance");
509
- return value;
326
+ if (expect === void 0) return void 0;
327
+ assertType(expect, `${what} option "expect"`);
328
+ const own = typeData(expect);
329
+ if (own !== expect) assertType(own, `${what} option "expect"`);
330
+ return own;
510
331
  }
511
332
  function createEnvironment(settings) {
512
333
  const checkEnv = {
@@ -539,101 +360,14 @@ function createEnvironment(settings) {
539
360
  function makeProgram(source, analysis, expect, constants, residualOf) {
540
361
  if (residualOf === void 0) warmZones(analysis);
541
362
  const guard = expect !== void 0 && analysis.checkResult ? expect : void 0;
542
- const checked = (result, state) => {
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 }));
544
- };
545
- const defaultLimits = evaluationLimits(void 0, settings);
546
- const limitsOf = (options) => options === void 0 ? defaultLimits : evaluationLimits(options, settings);
547
- let syncCode;
548
- let asyncCode;
549
- let pooled;
550
- let pooledInUse = false;
551
- const compileOptions = constants === void 0 ? {} : {
363
+ const { prepare, checked, settle, runSync, runAsync, compileOptions } = programRunner({
364
+ source,
365
+ analysis,
366
+ guard,
367
+ settings,
552
368
  constants,
553
- shapes: residualOf?.shapes
554
- };
555
- const syncProgram = () => syncCode ??= compileProgram(analysis, "sync", compileOptions);
556
- const asyncProgram = () => asyncCode ??= analysis.async ? compileProgram(analysis, "async", compileOptions) : syncProgram();
557
- function prepare(state, ctx, limits, locals) {
558
- state.reset(ctx, source, locals, limits.maxSteps, limits.timeout, limits.signal);
559
- if (limits.now !== void 0) state.nowValue = limits.now;
560
- const residual = residualOf;
561
- if (residual !== void 0 && residual.readsContext) {
562
- const roots = residual.knownRoots;
563
- state.charge(roots.length);
564
- const declared = settings.strict ? settings.variables : void 0;
565
- const missing = roots.find((name) => {
566
- if (declared !== void 0 && !Object.hasOwn(declared, name)) return false;
567
- const descriptor = Object.getOwnPropertyDescriptor(ctx, name);
568
- return descriptor === void 0 || "value" in descriptor && descriptor.value === void 0;
569
- });
570
- if (missing !== void 0) throw raised(new BonsaiRuntimeError("INVALID_CONTEXT", `Invalid context: ${missing} is missing; this residual calls a call: true host function, so pass the full context, as for the program`, { source }));
571
- }
572
- const validation = limits.validateContext ?? settings.validateContext ? settings.variables : void 0;
573
- if (validation !== void 0) {
574
- const validationLimits = {
575
- maxDepth: settings.runtimeLimits.maxValueDepth,
576
- maxSteps: limits.maxSteps,
577
- timeout: limits.timeout
578
- };
579
- if (residual === void 0 || residual.readsContext) validateContext(ctx, validation, source, validationLimits);
580
- else validatePaths(ctx, validation, residual.dependsOn, source, validationLimits);
581
- }
582
- }
583
- function runSync(context, options) {
584
- if (analysis.async) throw escaping(new BonsaiRuntimeError("ASYNC_IN_SYNC", "This expression calls an async function; use evaluate() instead of evaluateSync()", { source }));
585
- const code = syncProgram();
586
- const limits = limitsOf(options);
587
- const ctx = contextOf(context);
588
- let state;
589
- const reuse = !pooledInUse;
590
- if (reuse) {
591
- pooled ??= new State(settings.runtimeLimits, settings.clock);
592
- state = pooled;
593
- pooledInUse = true;
594
- } else state = new State(settings.runtimeLimits, settings.clock);
595
- try {
596
- prepare(state, ctx, limits, code.localCount);
597
- const result = code.run(state);
598
- checked(result, state);
599
- state.checkTime();
600
- return result;
601
- } catch (error) {
602
- throw escaping(hostDataFailure(error, source));
603
- } finally {
604
- state.release();
605
- if (reuse) pooledInUse = false;
606
- }
607
- }
608
- async function runAsync(context, options) {
609
- if (!analysis.async) {
610
- const result = runSync(context, options);
611
- try {
612
- return settle(result);
613
- } catch (error) {
614
- throw escaping(hostDataFailure(error, source));
615
- }
616
- }
617
- const code = asyncProgram();
618
- const limits = limitsOf(options);
619
- const ctx = contextOf(context);
620
- const state = new State(settings.runtimeLimits, settings.clock);
621
- try {
622
- prepare(state, ctx, limits, code.localCount);
623
- const result = (await code.run(state)).v;
624
- checked(result, state);
625
- state.checkTime();
626
- return settle(result);
627
- } catch (error) {
628
- throw escaping(hostDataFailure(error, source));
629
- } finally {
630
- state.release();
631
- }
632
- }
633
- function settle(result) {
634
- if (result !== null && (typeof result === "object" || typeof result === "function") && typeof result.then === "function") throw raised(new BonsaiRuntimeError("TYPE_ERROR", "evaluate() cannot return a promise-like value; use evaluateSync()", { source }));
635
- return result;
636
- }
369
+ residualOf
370
+ });
637
371
  const features = settings.features?.program({
638
372
  source,
639
373
  analysis,
@@ -674,6 +408,13 @@ function createEnvironment(settings) {
674
408
  evaluateSync: (...args) => runSync(args[0], args[1])
675
409
  });
676
410
  recordDeclared(program, settings.variables);
411
+ recordCompiled(program, {
412
+ source,
413
+ analysis,
414
+ expect,
415
+ lookup: checkEnv.lookup,
416
+ variables: settings.variables
417
+ });
677
418
  return program;
678
419
  }
679
420
  function compile(source, expect) {
@@ -684,10 +425,12 @@ function createEnvironment(settings) {
684
425
  }
685
426
  /**
686
427
  * compile(), through the program cache: programs are immutable, so the same
687
- * source with an equal expected type (types are plain JSON data) shares one.
428
+ * source with an equal expected type shares one. `expect` is expectOption's
429
+ * plain copy, so its JSON is the type the program is checked against.
688
430
  */
689
431
  function cachedCompile(source, expect) {
690
432
  if (typeof source !== "string") throw new TypeError("An expression must be a string");
433
+ if (expect !== void 0 && holdsExact(expect)) return compile(source, expect);
691
434
  const expectKey = expect === void 0 ? void 0 : JSON.stringify(expect);
692
435
  const key = expectKey === void 0 ? source : `${source}\u0000${expectKey}`;
693
436
  const entry = cache.get(key);
@@ -724,7 +467,7 @@ function createEnvironment(settings) {
724
467
  strict: settings.strict,
725
468
  parse: parseSource,
726
469
  check(source, options) {
727
- const expect = compileExpect(options);
470
+ const expect = expectOption(options, "Compile");
728
471
  let analysis;
729
472
  try {
730
473
  analysis = analyzeSource(source, expect);
@@ -745,7 +488,7 @@ function createEnvironment(settings) {
745
488
  let program;
746
489
  return Object.freeze(new CheckSuccess(type, diagnostics, analysis, () => program ??= makeProgram(source, analysis, expect)));
747
490
  },
748
- compile: (source, options) => cachedCompile(source, compileExpect(options)),
491
+ compile: (source, options) => cachedCompile(source, expectOption(options, "Compile")),
749
492
  ...settings.features === void 0 ? {} : { partial: (source, known, options) => cached(source).partial(known, options) },
750
493
  evaluate(source, ...args) {
751
494
  try {
@@ -794,6 +537,6 @@ function environmentWith(features, options) {
794
537
  });
795
538
  }
796
539
  //#endregion
797
- export { EVALUATE_OPTION_KEYS, contextOf, environmentWith, evaluationLimits, fn, hostDataFailure, internalsOf, invalidContext, isValidDate, validationBudget, withContext };
540
+ export { ProgramCache, environmentWith, expectOption, fn, internalsOf, settingsFrom, withContext };
798
541
 
799
542
  //# sourceMappingURL=environment.mjs.map