jseption 0.0.3 → 0.0.4

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/README.md CHANGED
@@ -104,6 +104,82 @@ checksum only catches accidental corruption; bytecode that passes through storag
104
104
  service you do not control needs a signature you verify before loading. See
105
105
  `docs/BYTECODE.md` for the format and the measurements.
106
106
 
107
+ ## Running TypeScript
108
+
109
+ TypeScript runs the way Node runs it with type stripping: the erasable syntax is
110
+ removed and the rest runs as JavaScript, with no type checking. Pass
111
+ `JS_EVAL_FLAG_STRIP_TYPES` to `JS_Eval`; the command line strips `.ts`, `.mts` and
112
+ `.cts` files, and `--strip-types` applies it to `-e`.
113
+
114
+ ```ts
115
+ JS_Eval(ctx, 'const id = <T,>(x: T): T => x; id<number>(41) + 1', 'app.ts',
116
+ JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRIP_TYPES); // 42
117
+ ```
118
+
119
+ The stripping is built into the parser, so error positions and stacks are those
120
+ of the JavaScript with the types blanked, and plain JavaScript compiles as fast as
121
+ before. As in Node, `enum`, namespaces with values, parameter properties,
122
+ `import x = require(...)` and `export =` are rejected because they need generated
123
+ code. `docs/TYPESCRIPT.md` lists what is stripped and how ambiguous syntax is read.
124
+
125
+ ## JSX and TSX
126
+
127
+ `JS_EVAL_FLAG_JSX` compiles JSX in the parser, as TypeScript compiles it. With
128
+ `JS_EVAL_FLAG_STRIP_TYPES` as well, it compiles TSX. There is no build step.
129
+
130
+ * **Modules** use the automatic runtime (`jsx: "react-jsx"`). Elements are calls of
131
+ `jsx`, `jsxs` and `Fragment`, imported from `react/jsx-runtime`. A
132
+ `/** @jsxImportSource x */` pragma imports them from `x/jsx-runtime` instead.
133
+ * **Scripts** use the classic runtime (`React.createElement`, or the `@jsx` and
134
+ `@jsxFrag` pragmas).
135
+ * **The module loader** compiles `.tsx` ids (or a `type: 'tsx'` result) as TSX and
136
+ `.jsx` ids as JSX.
137
+ * **The command line** runs `.tsx` and `.jsx` files as modules, and `--jsx` applies
138
+ the flag to `-e`.
139
+
140
+ ```ts
141
+ JS_SetModuleLoader(rt, { load: (id) => files[id] }); // '/app.tsx', 'react/jsx-runtime', ...
142
+ await guestPromiseToHost(ctx, JS_ImportModule(ctx, '/app.tsx'));
143
+ ```
144
+
145
+ `docs/JSX.md` covers the runtimes, pragmas, text and entity rules, and what is not
146
+ supported.
147
+
148
+ ## Modules
149
+
150
+ ES modules work as in QuickJS: `import` and `export` in every form, live bindings,
151
+ cycles, `export *` with its ambiguity rules, namespace objects, top-level `await`,
152
+ dynamic `import()`, `import.meta`, and JSON modules (`with { type: "json" }`). The
153
+ embedder decides what a specifier means. With no loader set, nothing can be imported,
154
+ so the sandbox stays closed by default.
155
+
156
+ ```ts
157
+ import { JS_SetModuleLoader, JS_ImportModule, guestPromiseToHost, drainJobs } from 'jseption';
158
+
159
+ JS_SetModuleLoader(rt, {
160
+ // optional; the default resolves ./, ../ and / against the importing module, URL style
161
+ resolve: (specifier, referrer) => new URL(specifier, referrer ?? location.href).href,
162
+ // source text, module bytecode, or { source, url, type, exports }, or a promise of one
163
+ load: async (url) => ({ source: await (await fetch(url)).text(), url }),
164
+ });
165
+ const ns = await guestPromiseToHost(ctx, JS_ImportModule(ctx, 'https://example.test/app.js'));
166
+ ```
167
+
168
+ The loader may be asynchronous: the whole graph is fetched first, in parallel where
169
+ possible, then linked and evaluated. A synchronous loader keeps everything synchronous.
170
+ `load` can return a host module, `{ exports: { fetch } }`, whose values are marshalled
171
+ into the guest, so guest code can `import { fetch } from "host:net"`. It can also
172
+ return bytecode from `JS_WriteObject`, so a precompiled graph loads without parsing.
173
+ `JS_EvalModule(ctx, source, url)` evaluates source text as a module.
174
+ `JS_Eval` with `JS_EVAL_TYPE_MODULE` keeps QuickJS's behaviour and returns the
175
+ evaluation promise.
176
+
177
+ The command line runs `.mjs` and `.mts` files as modules, detects `.js` and `.ts` files
178
+ that start with `import` or `export`, and takes `-m` to force module mode or `--script`
179
+ to force a script. Its loader reads relative and absolute paths from disk; bare
180
+ specifiers are not resolved. `docs/MODULES.md` describes the design and the loader
181
+ contract.
182
+
107
183
  ## Passing values between host and guest
108
184
 
109
185
  Guest values are host values, but the sandbox forbids handing the guest an object whose
@@ -137,8 +213,9 @@ src/atoms.ts atom table (JS_ATOM_*), tagged integer atoms, atom -> proper
137
213
  src/values.ts JSValue, object metadata, bytecode & frame structures
138
214
  src/opcodes.ts opcode table generated from quickjs-opcode.h
139
215
  src/quickjs.ts runtime, context, property layer over Reflect, calls, conversions, jobs
140
- src/parser.ts tokenizer, parser, bytecode emitter, variable/label resolution, eval
216
+ src/parser.ts tokenizer, parser (with TypeScript type stripping and JSX), bytecode emitter, variable/label resolution, eval
141
217
  src/bytecode.ts bytecode serialization (JS_WriteObject / JS_ReadObject)
218
+ src/module.ts ES modules: loader, linking, evaluation, namespaces, import(), import.meta
142
219
  src/interpreter.ts JS_CallInternal, closures, generators, async functions (fallback tier)
143
220
  src/jit.ts bytecode -> host JavaScript compiler (stack-to-register, relooper)
144
221
  src/jit_runtime.ts the helpers generated code calls into (the only engine touchpoint)
@@ -214,9 +291,9 @@ Differences from the C implementation:
214
291
  * Memory management relies on the host garbage collector: there is no reference
215
292
  counting, `JS_FreeValue`/`JS_DupValue` are no-ops, and `WeakRef` / `FinalizationRegistry`
216
293
  use the host weak references.
217
- * ES modules (`import` / `export`), `Atomics` and `SharedArrayBuffer` threads are not
218
- ported. `JS_WriteObject` / `JS_ReadObject` serialize compiled scripts (bytecode
219
- function trees) only, not arbitrary values, in a jseption-specific format.
294
+ * `Atomics` and `SharedArrayBuffer` threads are not ported. `JS_WriteObject` /
295
+ `JS_ReadObject` serialize compiled scripts and modules only, not arbitrary values,
296
+ in a jseption-specific format.
220
297
  * Number formatting and parsing use the host conversions (they are specified by
221
298
  ECMAScript, so the results are identical).
222
299
 
package/dist/bytecode.js CHANGED
@@ -35,7 +35,8 @@
35
35
  */
36
36
  import { JS_ATOM_END, JS_ATOM_raw, JS_ATOM_TAG_INT, JS_ATOM_TYPE_STRING, __JS_AtomIsTaggedInt } from "./atoms.js";
37
37
  import { JS_GetFunctionData, JS_ThrowSyntaxError, JS_ThrowTypeError, JS_NewArray, JS_DefinePropertyValue, JS_DefinePropertyValueUint32, JS_IsException, JS_PROP_ENUMERABLE, hooks } from "./quickjs.js";
38
- import { JSClosureVar, JSFunctionBytecode, JSVarDef } from "./values.js";
38
+ import { JSClosureVar, JSFunctionBytecode, JSVarDef, JSModuleDef, JSReqModuleEntry, JSExportEntry, JSStarExportEntry, JSImportEntry } from "./values.js";
39
+ import { js_module_set_function, hooks_bytecode } from "./module.js";
39
40
  import { short_opcode_info } from "./opcodes.js";
40
41
  import { JSEPTION_VERSION } from "./version.js";
41
42
  /* keep each function's source text (Function.prototype.toString); off by default */
@@ -320,6 +321,51 @@ class BCWriter {
320
321
  for (const v of b.cpool)
321
322
  this.value(v);
322
323
  }
324
+ /* a compiled module (JS_WriteModule): its requests, entries and body function */
325
+ module(m) {
326
+ const out = this.out;
327
+ out.u8(11 /* BCTag.MODULE */);
328
+ this.atom(m.module_name);
329
+ out.leb(m.req_module_entries.length);
330
+ for (const rme of m.req_module_entries) {
331
+ this.atom(rme.module_name);
332
+ if (rme.attributes === null) {
333
+ out.leb(0);
334
+ }
335
+ else {
336
+ const keys = Object.keys(rme.attributes);
337
+ out.leb(keys.length + 1);
338
+ for (const k of keys) {
339
+ out.leb(this.string_ref(k));
340
+ out.leb(this.string_ref(rme.attributes[k]));
341
+ }
342
+ }
343
+ }
344
+ out.leb(m.export_entries.length);
345
+ for (const me of m.export_entries) {
346
+ out.u8(me.export_type);
347
+ if (me.export_type === 0 /* JSExportType.LOCAL */) {
348
+ out.leb(me.var_idx);
349
+ }
350
+ else {
351
+ out.leb(me.req_module_idx);
352
+ this.atom(me.local_name);
353
+ }
354
+ this.atom(me.export_name);
355
+ }
356
+ out.leb(m.star_export_entries.length);
357
+ for (const se of m.star_export_entries)
358
+ out.leb(se.req_module_idx);
359
+ out.leb(m.import_entries.length);
360
+ for (const mi of m.import_entries) {
361
+ out.leb(mi.var_idx);
362
+ out.u8(mi.is_star ? 1 : 0);
363
+ this.atom(mi.import_name);
364
+ out.leb(mi.req_module_idx);
365
+ }
366
+ out.u8(m.has_tla ? 1 : 0);
367
+ this.function(m.func_bytecode);
368
+ }
323
369
  /* the byte code with its atom operands renumbered (JS_WriteFunctionBytecode) */
324
370
  byte_code(b) {
325
371
  const len = b.byte_code_len;
@@ -453,6 +499,25 @@ function checksum(buf, end) {
453
499
  bytecode function that does not close over variables). Returns null with a pending
454
500
  exception on failure. */
455
501
  export function JS_WriteObject(ctx, obj, flags = 0) {
502
+ if (obj instanceof JSModuleDef) {
503
+ /* a module compiled with JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY */
504
+ if (obj.kind !== 0 /* JSModuleKind.JS */ || obj.func_bytecode === null) {
505
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: only JavaScript modules can be written');
506
+ return null;
507
+ }
508
+ const w = new BCWriter(ctx, flags);
509
+ try {
510
+ w.module(obj);
511
+ return w.finish(w.out);
512
+ }
513
+ catch (e) {
514
+ if (e instanceof BCWriteError) {
515
+ JS_ThrowTypeError(ctx, 'JS_WriteObject: ' + e.message);
516
+ return null;
517
+ }
518
+ throw e;
519
+ }
520
+ }
456
521
  const fd = JS_GetFunctionData(obj);
457
522
  if (fd === null) {
458
523
  JS_ThrowTypeError(ctx, 'JS_WriteObject: not a bytecode function (expected the result of JS_EVAL_FLAG_COMPILE_ONLY)');
@@ -810,6 +875,93 @@ class BCReader {
810
875
  this.depth--;
811
876
  return b;
812
877
  }
878
+ /* a compiled module (JS_ReadModule) */
879
+ module() {
880
+ const m = new JSModuleDef(this.atom());
881
+ m.realm = this.ctx;
882
+ const nreq = this.count();
883
+ for (let i = 0; i < nreq; i++) {
884
+ const name = this.atom();
885
+ const n = this.leb();
886
+ let attrs = null;
887
+ if (n > 0) {
888
+ if (n - 1 > this.end - this.pos)
889
+ this.truncated();
890
+ attrs = Object.create(null);
891
+ for (let j = 0; j < n - 1; j++) {
892
+ const k = this.string();
893
+ attrs[k] = this.string();
894
+ }
895
+ }
896
+ m.req_module_entries.push(new JSReqModuleEntry(name, attrs));
897
+ }
898
+ const req_idx = () => {
899
+ const i = this.leb();
900
+ if (i >= nreq)
901
+ this.fail(`invalid module request index ${i}`);
902
+ return i;
903
+ };
904
+ const nexp = this.count();
905
+ const local_exports = [];
906
+ for (let i = 0; i < nexp; i++) {
907
+ const type = this.u8();
908
+ let me;
909
+ if (type === 0 /* JSExportType.LOCAL */) {
910
+ const var_idx = this.leb();
911
+ local_exports.push(var_idx);
912
+ me = new JSExportEntry(0, this.atom(), 0 /* JSExportType.LOCAL */);
913
+ me.var_idx = var_idx;
914
+ me.local_name = me.export_name;
915
+ }
916
+ else if (type === 1 /* JSExportType.INDIRECT */) {
917
+ const idx = req_idx();
918
+ const local_name = this.atom();
919
+ me = new JSExportEntry(local_name, this.atom(), 1 /* JSExportType.INDIRECT */);
920
+ me.req_module_idx = idx;
921
+ }
922
+ else {
923
+ this.fail('invalid export entry');
924
+ }
925
+ m.export_entries.push(me);
926
+ }
927
+ const nstar = this.count();
928
+ for (let i = 0; i < nstar; i++)
929
+ m.star_export_entries.push(new JSStarExportEntry(req_idx()));
930
+ const nimp = this.count();
931
+ const imports = [];
932
+ for (let i = 0; i < nimp; i++) {
933
+ const var_idx = this.leb();
934
+ const is_star = this.u8();
935
+ if (is_star > 1)
936
+ this.fail('invalid import entry');
937
+ const mi = new JSImportEntry(var_idx, is_star === 1, this.atom());
938
+ mi.req_module_idx = req_idx();
939
+ m.import_entries.push(mi);
940
+ imports.push({ var_idx, is_star: is_star === 1 });
941
+ }
942
+ const tla = this.u8();
943
+ if (tla > 1)
944
+ this.fail('invalid module flags');
945
+ m.has_tla = tla === 1;
946
+ if (this.u8() !== 9 /* BCTag.FUNCTION_BYTECODE */)
947
+ this.fail('the module body is not a function');
948
+ const b = this.function();
949
+ if (b.func_kind !== 2 /* JSFunctionKind.ASYNC */)
950
+ this.fail('the module body is not an async function');
951
+ /* the entries index the closure variables of the body */
952
+ const ncv = b.closure_var.length;
953
+ for (const v of local_exports)
954
+ if (v >= ncv)
955
+ this.fail(`invalid export variable index ${v}`);
956
+ for (const { var_idx, is_star } of imports) {
957
+ if (var_idx >= ncv)
958
+ this.fail(`invalid import variable index ${var_idx}`);
959
+ if (b.closure_var[var_idx].is_local !== is_star)
960
+ this.fail('invalid import variable');
961
+ }
962
+ js_module_set_function(m, b);
963
+ return m;
964
+ }
813
965
  /* patch the atom operands and check the instruction boundaries and the constant pool
814
966
  indices (JS_ReadFunctionBytecode) */
815
967
  fixup_byte_code(b) {
@@ -891,7 +1043,14 @@ export function JS_ReadObject(ctx, buf) {
891
1043
  r.header();
892
1044
  r.string_table();
893
1045
  r.atom_table();
894
- if (r.u8() !== 9 /* BCTag.FUNCTION_BYTECODE */)
1046
+ const tag = r.u8();
1047
+ if (tag === 11 /* BCTag.MODULE */) {
1048
+ const m = r.module();
1049
+ if (r.pos !== r.end)
1050
+ r.fail('trailing bytes after the root value');
1051
+ return m;
1052
+ }
1053
+ if (tag !== 9 /* BCTag.FUNCTION_BYTECODE */)
895
1054
  r.fail('the root value is not a function');
896
1055
  b = r.function();
897
1056
  if (r.pos !== r.end)
@@ -908,4 +1067,5 @@ export function JS_ReadObject(ctx, buf) {
908
1067
  return JS_ThrowSyntaxError(ctx, 'JS_ReadObject: the root function has closure variables');
909
1068
  return hooks.js_closure(ctx, b, null, null);
910
1069
  }
1070
+ hooks_bytecode.read = (ctx, bytes) => JS_ReadObject(ctx, bytes);
911
1071
  //# sourceMappingURL=bytecode.js.map