@hew-lang/sandbox-vm 0.6.1 → 0.6.3

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 (52) hide show
  1. package/README.md +48 -4
  2. package/cli/test-runner.mjs +4 -1
  3. package/dist/interpreter/interpreter.d.ts +1 -1
  4. package/dist/interpreter/interpreter.js +1 -1
  5. package/dist/interpreter/parity-runner.js +1 -1
  6. package/dist/interpreter/run-program.d.ts +3 -2
  7. package/dist/interpreter/run-program.js +5 -3
  8. package/dist/interpreter/schema-validator.d.ts +9 -4
  9. package/dist/interpreter/schema-validator.js +15 -49
  10. package/dist/interpreter/strings.d.ts +21 -0
  11. package/dist/interpreter/strings.js +191 -0
  12. package/dist/interpreter/types.d.ts +6 -0
  13. package/dist/interpreter/unicode-data.d.ts +4 -0
  14. package/dist/interpreter/unicode-data.js +1673 -0
  15. package/dist/interpreter/unicode.d.ts +3 -0
  16. package/dist/interpreter/unicode.js +35 -0
  17. package/dist/interpreter/v1/actor-identity.d.ts +3 -0
  18. package/dist/interpreter/v1/actor-identity.js +7 -0
  19. package/dist/interpreter/v1/actor-waits.d.ts +6 -0
  20. package/dist/interpreter/v1/actor-waits.js +33 -0
  21. package/dist/interpreter/v1/browser-directory.d.ts +107 -0
  22. package/dist/interpreter/v1/browser-directory.js +288 -0
  23. package/dist/interpreter/v1/calendar.d.ts +4 -0
  24. package/dist/interpreter/v1/calendar.js +64 -0
  25. package/dist/interpreter/v1/codecs.js +8 -11
  26. package/dist/interpreter/v1/exec.d.ts +2 -2
  27. package/dist/interpreter/v1/exec.js +1761 -489
  28. package/dist/interpreter/v1/instructions.generated.d.ts +1135 -0
  29. package/dist/interpreter/v1/instructions.generated.js +106 -0
  30. package/dist/interpreter/v1/package-graph.d.ts +2 -0
  31. package/dist/interpreter/v1/package-graph.js +729 -0
  32. package/dist/interpreter/v1/package-validator.generated.d.ts +3 -0
  33. package/dist/interpreter/v1/package-validator.generated.js +75902 -0
  34. package/dist/interpreter/v1/package.d.ts +5 -645
  35. package/dist/interpreter/v1/package.js +0 -12
  36. package/dist/interpreter/v1/pipes.d.ts +1 -0
  37. package/dist/interpreter/v1/pipes.js +9 -6
  38. package/dist/interpreter/v1/scheduler.d.ts +8 -1
  39. package/dist/interpreter/v1/scheduler.js +50 -11
  40. package/dist/interpreter/v1/shims.d.ts +260 -7
  41. package/dist/interpreter/v1/shims.js +647 -175
  42. package/dist/interpreter/v1/structural.d.ts +2 -28
  43. package/dist/interpreter/v1/timestamp.d.ts +7 -0
  44. package/dist/interpreter/v1/timestamp.js +54 -0
  45. package/dist/interpreter/v1/validate.d.ts +8 -2
  46. package/dist/interpreter/v1/validate.js +284 -200
  47. package/dist/interpreter/v1/value-operations.d.ts +11 -3
  48. package/dist/interpreter/v1/value-operations.js +70 -18
  49. package/dist/interpreter/values.d.ts +30 -8
  50. package/dist/interpreter/values.js +31 -5
  51. package/dist/interpreter/worker.js +4 -3
  52. package/package.json +4 -4
package/README.md CHANGED
@@ -5,8 +5,9 @@
5
5
  borrows, cleanup, checked failures, actor protocols and suspension edges.
6
6
 
7
7
  Actors, tasks and pipes use resumable frames on a deterministic scheduler.
8
- Timers advance virtual time. Native filesystem and network operations are
9
- refused before execution; other missing operations are reported separately as
8
+ Timers advance virtual time. Hosts can supply real origin-private filesystem
9
+ (OPFS) directory handles; network and ambient filesystem operations are refused
10
+ before execution. Other missing operations are reported separately as
10
11
  `not_implemented`. The loader is the capability admission authority.
11
12
 
12
13
  ## Browser API
@@ -17,16 +18,43 @@ import { runBytecode } from "@hew-lang/sandbox-vm";
17
18
 
18
19
  const { diagnostics, bytecode } = JSON.parse(compileToSandboxBytecode(source));
19
20
  if (bytecode) {
20
- const trace = runBytecode(bytecode, { replay: { step_budget: 100000 } });
21
+ const trace = await runBytecode(bytecode, {
22
+ replay: { step_budget: 100000 },
23
+ });
21
24
  }
22
25
  ```
23
26
 
24
27
  Initialize the Wasm compiler using its generated default export before calling
25
28
  it. `runProgram(source, stdin)` is also available for existing page bridges;
26
- install `globalThis.compileToSandboxBytecode` first. Its result includes
29
+ install `globalThis.compileToSandboxBytecode` first. Both entrypoints return a Promise. The page result includes
27
30
  `stdout`, `exit_code`, `diagnostics`, `status`, `sandbox_rejections`,
28
31
  `compiler_version` and `hew_version`.
29
32
 
33
+ To grant filesystem authority, pass only the directories the program needs:
34
+
35
+ ```js
36
+ const trace = await runBytecode(bytecode, {
37
+ directoryRoots: { read: inputDirectory, write: outputDirectory },
38
+ signal: controller.signal,
39
+ });
40
+ ```
41
+
42
+ Both roots are optional and default to no authority. The handles must belong to
43
+ this origin's OPFS; external picker-backed handles are refused because their
44
+ symlink confinement is not standardized. Existing Hew `fs.open_read_dir(".")`
45
+ and `fs.open_write_dir(".")` acquire the supplied roots. Relative traversal stays
46
+ beneath the current directory owner, including after narrowing with `subdir`.
47
+ Children remain usable after closing a parent. Each `file.write` publishes its
48
+ bytes before it returns, even while the file owner remains open.
49
+
50
+ `AbortSignal` requests cancellation through the source's authored cleanup edges.
51
+ Filesystem acquisition publishes its owner before observing cancellation, as on
52
+ native hosts. An in-progress file operation drains or aborts its browser writer
53
+ before cancellation releases its inputs. Pure execution retains the deterministic
54
+ virtual scheduler and runs until a host wait or completion. Pass the same options
55
+ as the fourth argument to `runProgram(source, stdin, files, options)`; signals are
56
+ for direct calls rather than worker message payloads.
57
+
30
58
  A `sandbox_rejected` result includes rejection entries with `category`, `code`,
31
59
  `capability`, `message` and `span`. Categories are `native_only`,
32
60
  `not_implemented` and `invalid_package`. Compiler errors, language panics,
@@ -48,8 +76,24 @@ The [package contract](bytecode/package-v1.md) describes the SIR projection.
48
76
  Source fixtures are compiled by the public compiler and executed to record
49
77
  deterministic traces. Tests also compare executable sources with native Hew.
50
78
 
79
+ With `hew-wasm/pkg` and `hew-sandbox-vm/dist` built, serve the repository on
80
+ localhost and open `hew-sandbox-vm/test/browser/directory-handles.html` to run the
81
+ real browser filesystem controls. They compile the ordinary Hew source fixture,
82
+ use a temporary OPFS directory, verify disk contents, confinement and cancellation,
83
+ then remove their entries. The page accepts a `compiler` module URL query parameter
84
+ to reuse another built compiler package.
85
+
86
+ `RunOptions.argv` supplies process arguments, including the executable name at
87
+ index zero. It defaults to `["main.hew"]` and is copied for each run. For example,
88
+ `runBytecode(bytecode, { argv: ["greet", "--", "Zoë", ""] })` supplies those exact
89
+ strings to `std.os.args()`. The VM never reads Node or browser process arguments.
90
+
51
91
  Typed serialization executes the shared Rust codecs in `@hew-lang/wasm`.
52
92
  Pass its initialized `executeCodec` export as `runBytecode`'s `codecBridge`
53
93
  option. The Node test host loads that package automatically; in a source
54
94
  checkout, run `make wasm` to build `hew-wasm/pkg` first. An explicit
55
95
  `HEW_WASM_MODULE` module URL can select another build of the same package.
96
+
97
+ UTC calendar operations also require the initialized `executeCalendar`
98
+ export as `calendarBridge`. Both hosts use the shared Chrono calendar engine;
99
+ the bridge preserves integer seconds and nanoseconds exactly.
@@ -22,6 +22,7 @@ try {
22
22
  const args = parseArgs(process.argv.slice(2));
23
23
  const packageValue = JSON.parse(fs.readFileSync(args.path, "utf8"));
24
24
  let codecBridge;
25
+ let calendarBridge;
25
26
  if (packageValue.codecs?.length) {
26
27
  const local = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../hew-wasm/pkg/hew_wasm.js");
27
28
  const location = process.env.HEW_WASM_MODULE ?? (fs.existsSync(local) ? pathToFileURL(local).href : "@hew-lang/wasm");
@@ -31,11 +32,13 @@ try {
31
32
  await wasm.default({ module_or_path: fs.readFileSync(new URL(resolved.replace(/\.js$/, "_bg.wasm"))) });
32
33
  }
33
34
  codecBridge = wasm.executeCodec ?? wasm.default?.executeCodec;
35
+ calendarBridge = wasm.executeCalendar ?? wasm.default?.executeCalendar;
34
36
  if (typeof codecBridge !== "function") throw new Error("hew-wasm executeCodec export is missing; rebuild the Wasm package");
35
37
  }
36
38
  const propertyPath = reportPath ? path.join(path.dirname(reportPath), "property.cbor") : null;
37
- const trace = runBytecode(packageValue, {
39
+ const trace = await runBytecode(packageValue, {
38
40
  codecBridge,
41
+ calendarBridge,
39
42
  propertyInput: propertyPath && fs.existsSync(propertyPath) ? [...fs.readFileSync(propertyPath)] : [],
40
43
  fixtureId: "hew-test",
41
44
  traceId: "trace:hew-test",
@@ -1,2 +1,2 @@
1
1
  import type { RunOptions, SandboxTrace } from "./types.js";
2
- export declare function runBytecode(input: unknown, options?: RunOptions): SandboxTrace;
2
+ export declare function runBytecode(input: unknown, options?: RunOptions): Promise<SandboxTrace>;
@@ -1,7 +1,7 @@
1
1
  import { validateBytecodePackage } from "./schema-validator.js";
2
2
  import { runPackageV1 } from "./v1/exec.js";
3
3
  import { TraceBuilder } from "./trace.js";
4
- export function runBytecode(input, options = {}) {
4
+ export async function runBytecode(input, options = {}) {
5
5
  let bytecode;
6
6
  try {
7
7
  bytecode = validateBytecodePackage(input);
@@ -45,7 +45,7 @@ function parseArgs(argv) {
45
45
  try {
46
46
  const args = parseArgs(process.argv.slice(2));
47
47
  const bytecode = JSON.parse(fs.readFileSync(args.bytecodePath, "utf8"));
48
- const trace = runBytecode(bytecode, {
48
+ const trace = await runBytecode(bytecode, {
49
49
  replay: {
50
50
  seed: args.seed,
51
51
  step_budget: DEFAULT_STEP_BUDGET,
@@ -1,5 +1,5 @@
1
1
  import type { PackageV1 } from "./v1/package.js";
2
- import type { JsonValue, RuntimeStatus, SandboxRejection } from "./types.js";
2
+ import type { RunOptions, JsonValue, RuntimeStatus, SandboxRejection } from "./types.js";
3
3
  export interface Diagnostic {
4
4
  severity: string;
5
5
  phase: string;
@@ -24,7 +24,8 @@ type SandboxCompiler = (source: string, profile?: string, files?: Map<string, st
24
24
  declare global {
25
25
  var __hewSandboxCompileToSandboxBytecode: SandboxCompiler | undefined;
26
26
  var __hewSandboxExecuteCodec: ((request: string) => string) | undefined;
27
+ var __hewSandboxExecuteCalendar: ((request: string) => string) | undefined;
27
28
  var compileToSandboxBytecode: SandboxCompiler | undefined;
28
29
  }
29
- export declare function runProgram(source: string, stdin: string, files?: Map<string, string | Uint8Array>): RunProgramResult;
30
+ export declare function runProgram(source: string, stdin: string, files?: Map<string, string | Uint8Array>, options?: RunOptions): Promise<RunProgramResult>;
30
31
  export {};
@@ -1,6 +1,6 @@
1
1
  import { runBytecode } from "./interpreter.js";
2
2
  const SANDBOX_PROFILE = "sandbox-vm-export";
3
- export function runProgram(source, stdin, files) {
3
+ export async function runProgram(source, stdin, files, options = {}) {
4
4
  const compileOutput = compileSource(source, files);
5
5
  if (hasErrorDiagnostics(compileOutput.diagnostics) ||
6
6
  compileOutput.bytecode === null) {
@@ -15,9 +15,11 @@ export function runProgram(source, stdin, files) {
15
15
  };
16
16
  }
17
17
  const bytecode = compileOutput.bytecode;
18
- const trace = runBytecode(bytecode, {
18
+ const trace = await runBytecode(bytecode, {
19
+ ...options,
19
20
  fixtureId: "page-run",
20
- codecBridge: globalThis.__hewSandboxExecuteCodec,
21
+ codecBridge: options.codecBridge ?? globalThis.__hewSandboxExecuteCodec,
22
+ calendarBridge: options.calendarBridge ?? globalThis.__hewSandboxExecuteCalendar,
21
23
  traceId: "trace:page-run",
22
24
  replay: { inputs: [{ kind: "stdin", data: stdin }] },
23
25
  });
@@ -1,8 +1,13 @@
1
- import { type PackageV1 } from "./v1/package.js";
1
+ import type { PackageV1 } from "./v1/package.js";
2
+ declare const checkedPackage: unique symbol;
3
+ export type CheckedPackage = PackageV1 & {
4
+ readonly [checkedPackage]: true;
5
+ };
2
6
  export declare class BytecodeValidationError extends Error {
3
7
  readonly errors: string[];
4
8
  constructor(errors: string[]);
5
9
  }
6
- /** Check the package envelope before capability admission traverses its tables.
7
- * Operation support belongs to v1/validate, with one shared executor registry. */
8
- export declare function validateBytecodePackage(input: unknown): PackageV1;
10
+ /** Decode an isolated package before capability admission or any host effect.
11
+ * Cloning prevents a caller or host callback from changing the checked input. */
12
+ export declare function validateBytecodePackage(input: unknown): CheckedPackage;
13
+ export {};
@@ -1,4 +1,5 @@
1
- import { isPackageV1 } from "./v1/package.js";
1
+ import { validPackage } from "./v1/package-validator.generated.js";
2
+ import { validatePackageGraph } from "./v1/package-graph.js";
2
3
  export class BytecodeValidationError extends Error {
3
4
  errors;
4
5
  constructor(errors) {
@@ -7,55 +8,20 @@ export class BytecodeValidationError extends Error {
7
8
  this.name = "BytecodeValidationError";
8
9
  }
9
10
  }
10
- /** Check the package envelope before capability admission traverses its tables.
11
- * Operation support belongs to v1/validate, with one shared executor registry. */
11
+ /** Decode an isolated package before capability admission or any host effect.
12
+ * Cloning prevents a caller or host callback from changing the checked input. */
12
13
  export function validateBytecodePackage(input) {
13
- if (!isPackageV1(input))
14
- throw new BytecodeValidationError(["expected hew.sandbox.bytecode.v1"]);
15
- const errors = [];
16
- for (const key of ["profile", "hew_version", "compiler_version"]) {
17
- if (typeof input[key] !== "string")
18
- errors.push(`${key} must be a string`);
19
- }
20
- for (const key of [
21
- "functions",
22
- "strings",
23
- "bytes",
24
- "regex_patterns",
25
- "aggregates",
26
- "variants",
27
- "runtime_families",
28
- "externs",
29
- "suspend_kinds",
30
- "value_capabilities",
31
- "closures",
32
- "vtables",
33
- ]) {
34
- if (!Array.isArray(input[key]))
35
- errors.push(`${key} must be an array`);
36
- }
37
- if (errors.length)
38
- throw new BytecodeValidationError(errors);
39
- for (const fn of input.functions) {
40
- if (!fn ||
41
- !Number.isInteger(fn.id) ||
42
- !Array.isArray(fn.params) ||
43
- !Array.isArray(fn.places) ||
44
- !Array.isArray(fn.blocks)) {
45
- errors.push("malformed function");
46
- continue;
47
- }
48
- for (const block of fn.blocks) {
49
- if (!block ||
50
- !Number.isInteger(block.id) ||
51
- !Array.isArray(block.params) ||
52
- !Array.isArray(block.ops) ||
53
- !block.term ||
54
- typeof block.term.op !== "string")
55
- errors.push("malformed block");
14
+ try {
15
+ const decoded = structuredClone(input);
16
+ if (!validPackage(decoded)) {
17
+ const reasons = (validPackage.errors ?? []).map((issue) => `${issue.instancePath || "/"} ${issue.message ?? "invalid value"}`);
18
+ throw new Error(reasons.join("; ") || "malformed sandbox bytecode package");
56
19
  }
20
+ const pkg = decoded;
21
+ validatePackageGraph(pkg);
22
+ return pkg;
23
+ }
24
+ catch (error) {
25
+ throw new BytecodeValidationError([String(error)]);
57
26
  }
58
- if (errors.length)
59
- throw new BytecodeValidationError(errors);
60
- return input;
61
27
  }
@@ -0,0 +1,21 @@
1
+ export declare class IndexedString {
2
+ readonly kind = "string";
3
+ private storage;
4
+ private constructor();
5
+ static fromText(text: string): IndexedString;
6
+ private live;
7
+ get value(): string;
8
+ get scalarLength(): number;
9
+ get byteLength(): number;
10
+ encoded(): Uint8Array;
11
+ clone(): IndexedString;
12
+ release(): void;
13
+ scalarOffset(index: number): number | undefined;
14
+ at(index: number): string | undefined;
15
+ slice(start: number, end: number): IndexedString | undefined;
16
+ private scalarIndex;
17
+ find(needle: IndexedString, start?: number): number | undefined;
18
+ /** Consume this receiver, retaining the suffix's borrowed snapshot. */
19
+ append(suffix: IndexedString): IndexedString;
20
+ }
21
+ export declare function stringValue(value: string): IndexedString;
@@ -0,0 +1,191 @@
1
+ /** The VM's owned UTF-8 string storage. Scalar positions use the same
2
+ * bounded-stride index as native strings; no prefix is rescanned on access. */
3
+ const INDEX_STRIDE = 32;
4
+ const ENCODER = new TextEncoder();
5
+ const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
6
+ function scalarWidth(first) {
7
+ return first < 0x80 ? 1 : first < 0xe0 ? 2 : first < 0xf0 ? 3 : 4;
8
+ }
9
+ function indexStorage(storage, byte, scalar) {
10
+ const offsets = storage.offsets;
11
+ while (byte < storage.byteLength) {
12
+ if (scalar % INDEX_STRIDE === 0)
13
+ offsets[scalar / INDEX_STRIDE] = byte;
14
+ byte += scalarWidth(storage.bytes[byte]);
15
+ scalar++;
16
+ }
17
+ }
18
+ function newStorage(bytes, capacity = bytes.length) {
19
+ const buffer = new Uint8Array(capacity);
20
+ buffer.set(bytes);
21
+ let scalars = 0;
22
+ for (let at = 0; at < bytes.length; at += scalarWidth(bytes[at]))
23
+ scalars++;
24
+ const storage = {
25
+ bytes: buffer,
26
+ byteLength: bytes.length,
27
+ scalarLength: scalars,
28
+ offsets: scalars === bytes.length ? null : [],
29
+ owners: 1,
30
+ };
31
+ if (storage.offsets)
32
+ indexStorage(storage, 0, 0);
33
+ return storage;
34
+ }
35
+ export class IndexedString {
36
+ kind = "string";
37
+ storage;
38
+ constructor(storage) {
39
+ this.storage = storage;
40
+ }
41
+ static fromText(text) {
42
+ return new IndexedString(newStorage(ENCODER.encode(text)));
43
+ }
44
+ live() {
45
+ if (!this.storage)
46
+ throw new Error("cannot read a released string");
47
+ return this.storage;
48
+ }
49
+ get value() {
50
+ return DECODER.decode(this.encoded());
51
+ }
52
+ get scalarLength() {
53
+ return this.live().scalarLength;
54
+ }
55
+ get byteLength() {
56
+ return this.live().byteLength;
57
+ }
58
+ encoded() {
59
+ const storage = this.live();
60
+ return storage.bytes.subarray(0, storage.byteLength);
61
+ }
62
+ clone() {
63
+ const storage = this.live();
64
+ storage.owners++;
65
+ return new IndexedString(storage);
66
+ }
67
+ release() {
68
+ if (this.storage) {
69
+ this.storage.owners--;
70
+ this.storage = null;
71
+ }
72
+ }
73
+ scalarOffset(index) {
74
+ const storage = this.live();
75
+ if (!Number.isSafeInteger(index) ||
76
+ index < 0 ||
77
+ index > storage.scalarLength)
78
+ return undefined;
79
+ if (index === storage.scalarLength)
80
+ return storage.byteLength;
81
+ if (!storage.offsets)
82
+ return index;
83
+ let byte = storage.offsets[Math.floor(index / INDEX_STRIDE)];
84
+ for (let tail = index % INDEX_STRIDE; tail > 0; tail--)
85
+ byte += scalarWidth(storage.bytes[byte]);
86
+ return byte;
87
+ }
88
+ at(index) {
89
+ if (index === this.scalarLength)
90
+ return undefined;
91
+ const byte = this.scalarOffset(index);
92
+ if (byte === undefined)
93
+ return undefined;
94
+ const storage = this.live();
95
+ return DECODER.decode(storage.bytes.subarray(byte, byte + scalarWidth(storage.bytes[byte])));
96
+ }
97
+ slice(start, end) {
98
+ const from = this.scalarOffset(start);
99
+ const to = this.scalarOffset(end);
100
+ if (from === undefined || to === undefined || from > to)
101
+ return undefined;
102
+ return new IndexedString(newStorage(this.live().bytes.subarray(from, to)));
103
+ }
104
+ scalarIndex(byte) {
105
+ const storage = this.live();
106
+ if (!storage.offsets)
107
+ return byte;
108
+ if (byte === storage.byteLength)
109
+ return storage.scalarLength;
110
+ let low = 0;
111
+ let high = storage.offsets.length;
112
+ while (low + 1 < high) {
113
+ const mid = Math.floor((low + high) / 2);
114
+ if (storage.offsets[mid] <= byte)
115
+ low = mid;
116
+ else
117
+ high = mid;
118
+ }
119
+ let scalar = low * INDEX_STRIDE;
120
+ for (let at = storage.offsets[low]; at < byte; at += scalarWidth(storage.bytes[at]))
121
+ scalar++;
122
+ return scalar;
123
+ }
124
+ find(needle, start = 0) {
125
+ const from = this.scalarOffset(start);
126
+ if (from === undefined)
127
+ return undefined;
128
+ const bytes = this.encoded();
129
+ const pattern = needle.encoded();
130
+ if (pattern.length === 0)
131
+ return start;
132
+ // KMP searches only the suffix requested by the caller. Decoding the
133
+ // complete value for String.indexOf would rescan its prefix on every call.
134
+ const prefixes = new Array(pattern.length).fill(0);
135
+ for (let i = 1, matched = 0; i < pattern.length; i++) {
136
+ while (matched && pattern[i] !== pattern[matched])
137
+ matched = prefixes[matched - 1];
138
+ if (pattern[i] === pattern[matched])
139
+ matched++;
140
+ prefixes[i] = matched;
141
+ }
142
+ for (let i = from, matched = 0; i < bytes.length; i++) {
143
+ while (matched && bytes[i] !== pattern[matched])
144
+ matched = prefixes[matched - 1];
145
+ if (bytes[i] === pattern[matched])
146
+ matched++;
147
+ if (matched === pattern.length)
148
+ return this.scalarIndex(i + 1 - matched);
149
+ }
150
+ return undefined;
151
+ }
152
+ /** Consume this receiver, retaining the suffix's borrowed snapshot. */
153
+ append(suffix) {
154
+ const bytes = suffix.encoded();
155
+ const added = suffix.scalarLength;
156
+ if (!bytes.length)
157
+ return this;
158
+ let storage = this.live();
159
+ const oldLength = storage.byteLength;
160
+ const oldScalars = storage.scalarLength;
161
+ const newLength = oldLength + bytes.length;
162
+ const capacity = newLength > storage.bytes.length
163
+ ? Math.max(newLength, storage.bytes.length * 2, 16)
164
+ : storage.bytes.length;
165
+ if (storage.owners !== 1) {
166
+ storage.owners--;
167
+ storage = newStorage(this.encoded(), capacity);
168
+ this.storage = storage;
169
+ }
170
+ else if (capacity !== storage.bytes.length) {
171
+ const buffer = new Uint8Array(capacity);
172
+ buffer.set(storage.bytes.subarray(0, oldLength));
173
+ storage.bytes = buffer;
174
+ }
175
+ storage.bytes.set(bytes, oldLength);
176
+ storage.byteLength = newLength;
177
+ storage.scalarLength += added;
178
+ if (storage.scalarLength !== newLength) {
179
+ if (storage.offsets)
180
+ indexStorage(storage, oldLength, oldScalars);
181
+ else {
182
+ storage.offsets = [];
183
+ indexStorage(storage, 0, 0);
184
+ }
185
+ }
186
+ return this;
187
+ }
188
+ }
189
+ export function stringValue(value) {
190
+ return IndexedString.fromText(value);
191
+ }
@@ -117,7 +117,13 @@ export interface SandboxTrace {
117
117
  };
118
118
  }
119
119
  export interface RunOptions {
120
+ /** Explicit browser filesystem authority; absent roots grant no access. */
121
+ directoryRoots?: import("./v1/browser-directory.js").DirectoryRoots;
122
+ signal?: AbortSignal;
123
+ /** Supplied process arguments including argv[0]. Defaults to ["main.hew"]. */
124
+ argv?: readonly string[];
120
125
  codecBridge?: (request: string) => string;
126
+ calendarBridge?: (request: string) => string;
121
127
  propertyInput?: number[];
122
128
  fixtureId?: string;
123
129
  traceId?: string;
@@ -0,0 +1,4 @@
1
+ export declare const CATEGORY_RANGES: readonly number[];
2
+ export declare const UPPER_MAPPING: readonly number[];
3
+ export declare const LOWER_MAPPING: readonly number[];
4
+ export declare const TITLE_MAPPING: readonly number[];