@markii/lua 0.12.1 → 0.13.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.
@@ -24,7 +24,7 @@ export interface CacheEntry {
24
24
  }
25
25
  /**
26
26
  * Host-provided cache primitive backing `cache.get(key, ttl, fn)`. Real
27
- * persistence (bundle `cache/`, IndexedDB, whatever the host uses) is the
27
+ * persistence (bundle `.cache/`, IndexedDB, whatever the host uses) is the
28
28
  * host's concern; this package only defines the read-if-fresh-else-run-fn
29
29
  * contract.
30
30
  */
@@ -422,7 +422,7 @@ net.patch = function(url, body) return __smd_net_patch_blocked(url, body):await(
422
422
  // JSON-safe result is handed to `config.cache!.set` — so
423
423
  // `CacheEntry.value`'s STORAGE shape is unchanged (still whatever
424
424
  // plain value the host's `CacheProvider` already expects; e.g. a
425
- // bundle's `cache/*.json` file), and a script's own scalar values
425
+ // bundle's `.cache/*.json` file), and a script's own scalar values
426
426
  // (numbers, strings, booleans) round-trip exactly as before.
427
427
  // - Either enforcement failing raises the existing, already-classified
428
428
  // `MARSHAL_ERROR_TAG` error (`sandbox.ts` already recognizes it as
@@ -438,7 +438,7 @@ net.patch = function(url, body) return __smd_net_patch_blocked(url, body):await(
438
438
  // to text and handed to Lua — mirrors `net.fetch_json`'s own
439
439
  // pre-check exactly (adversarial finding B2). A host-stored value is
440
440
  // exactly as untrusted as a remote fetch body — a bundle's
441
- // `cache/*.json` file, for instance, can be edited by anything with
441
+ // `.cache/*.json` file, for instance, can be edited by anything with
442
442
  // write access to the bundle, not just this sandbox's own WRITE side
443
443
  // below — so without this check, a 300k-element cached array reached
444
444
  // the script completely uncapped even though the FETCH path was
@@ -557,7 +557,7 @@ end
557
557
  }
558
558
  // --- bundle -------------------------------------------------------------
559
559
  // Delegates entirely to the injected `ScriptView` (`@markii/bundle`), which
560
- // already enforces the path-jail and the read/write:cache/ split (spec
560
+ // already enforces the path-jail and the read/write:.cache/ split (spec
561
561
  // §11). This module adds nothing on top except the tier gate for
562
562
  // `bundle.write` (a tier-blocked stub under 'auto' — read-only tier) and
563
563
  // the byte<->Lua-string conversion.
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ export type { CacheEntry, CacheProvider, CapabilityConfig, CapabilityDenial, Cap
8
8
  export { DEFAULT_MAX_FETCH_BYTES, bytesToLuaString, buildCapabilities, isNetProviderDenial, luaStringToBytes, netProviderDenial, } from './capabilities.js';
9
9
  export type { DocConfig } from './doc.js';
10
10
  export { buildDoc } from './doc.js';
11
+ export type { JsonTableConfig } from './json-table.js';
12
+ export { buildJsonTable } from './json-table.js';
11
13
  export type { MarshalLimits } from './marshal.js';
12
14
  export { DEFAULT_MARSHAL_LIMITS, buildMarshalPrelude, checkJsonWithinLimits, finalizeMarshaledValue, wrapUserCode, } from './marshal.js';
13
15
  export type { PackModuleResolver, RequireConfig } from './require.js';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export { ALLOWED_GLOBALS, DENIED_GLOBALS, createEmptyLuaEngine, } from './global
8
8
  export { DEFAULT_LIMITS, installLimits } from './limits.js';
9
9
  export { DEFAULT_MAX_FETCH_BYTES, bytesToLuaString, buildCapabilities, isNetProviderDenial, luaStringToBytes, netProviderDenial, } from './capabilities.js';
10
10
  export { buildDoc } from './doc.js';
11
+ export { buildJsonTable } from './json-table.js';
11
12
  export { DEFAULT_MARSHAL_LIMITS, buildMarshalPrelude, checkJsonWithinLimits, finalizeMarshaledValue, wrapUserCode, } from './marshal.js';
12
13
  export { buildRequire } from './require.js';
13
14
  export { runScript } from './sandbox.js';
@@ -0,0 +1,88 @@
1
+ import { type MarshalLimits } from './marshal.js';
2
+ export interface JsonTableConfig {
3
+ /**
4
+ * Depth/node budget applied to a `json.decode` input and, via
5
+ * `__smd_marshal_root`, to a `json.encode` value. The SAME `MarshalLimits`
6
+ * `./sandbox` already uses for the marshal prelude and `./capabilities`
7
+ * uses for `net.fetch_json` -- never a second, independently-tuned cap.
8
+ */
9
+ limits: MarshalLimits;
10
+ /**
11
+ * Byte-size cap on the TEXT handed to `json.decode`, checked before the
12
+ * text is JSON-parsed at all. The SAME cap `net.fetch_json` applies to a
13
+ * fetched response body (`./capabilities`' `maxFetchBytes`), reused here
14
+ * rather than forked.
15
+ */
16
+ maxFetchBytes: number;
17
+ }
18
+ /**
19
+ * Builds the raw JS globals and trusted Lua prelude for the `json` table:
20
+ * `json.decode(text)` and `json.encode(value)` (GitHub issue #40, slice 3a).
21
+ *
22
+ * `json` is pure computation with no I/O -- it never touches the network,
23
+ * the bundle filesystem, or the clock -- so `./sandbox` injects this
24
+ * unconditionally, for every tier, with no capability grant required. This
25
+ * mirrors how `./marshal`'s and `./doc`'s preludes are already injected
26
+ * unconditionally; `json` joins that same "always present" set rather than
27
+ * being wired through `./capabilities`' tier/grant machinery, which exists
28
+ * specifically to gate effectful or ambient-authority operations this
29
+ * table has none of.
30
+ *
31
+ * ## `json.decode`: reuses the EXISTING decoder and EXISTING budget
32
+ *
33
+ * The actual parse is `./json-decode`'s `__smd_json_decode` -- the same
34
+ * trusted, in-Lua JSON decoder `net.fetch_json`/`cache.get` already use in
35
+ * `./capabilities` (see that module's doc comment for why decoding must
36
+ * happen entirely in Lua rather than trusting wasmoon's own JS-object
37
+ * conversion). This function never writes a second decoder.
38
+ *
39
+ * Before that decoder ever sees the text, a JS-side precheck applies the
40
+ * exact same two-part budget `net.fetch_json` applies to a fetched body:
41
+ * the `maxFetchBytes` byte-size cap (an ordinary, untagged `error()`,
42
+ * `kind: 'runtime'` -- no `ScriptMarshalReason` value fits "too many
43
+ * bytes"), and `./marshal`'s `checkJsonWithinLimits` (the very function
44
+ * `./capabilities` calls) against the `JSON.parse`d value, tagged
45
+ * `MARK_MARSHAL:depth`/`MARK_MARSHAL:nodes` so `./sandbox`'s
46
+ * `classifyRuntimeError` reports it as `kind: 'marshal'`, `reason: 'depth'
47
+ * | 'nodes'` -- the same classification a `json.encode` budget violation
48
+ * gets (see below), so a script sees one consistent failure shape for "too
49
+ * deep or too wide" regardless of which direction it happened. Text that
50
+ * fails to `JSON.parse` at all is NOT diagnosed here in JS: it is
51
+ * handed straight to `__smd_json_decode`, which raises its own "malformed"
52
+ * error -- duplicating that diagnosis in two places risks the two
53
+ * disagreeing, and the Lua decoder's message is the one already documented.
54
+ *
55
+ * ## `json.encode`: reuses the EXISTING marshal walk
56
+ *
57
+ * `json.encode(value)` runs `value` through `__smd_marshal_root`
58
+ * (`./marshal`'s `buildMarshalPrelude`, already injected once per engine
59
+ * before this prelude runs) -- the SAME depth/node-capped, cycle-detecting,
60
+ * type-checking walk a script's own top-level return value already goes
61
+ * through. No second walk is written. The capped, marker-tagged result then
62
+ * crosses to JS as a single bounded function argument (safe by construction:
63
+ * the walk has already capped it, exactly as `./capabilities`' `cache.get`
64
+ * write path already relies on for the same reason), where
65
+ * `finalizeMarshaledValue` -- the same JS-side pass `./sandbox` runs on a
66
+ * script's return value -- strips the array marker and rejects a
67
+ * non-finite number, before the result is `JSON.stringify`d. A cycle,
68
+ * excess depth/nodes, a non-string table key, or a function/userdata/thread
69
+ * all raise the walk's existing `MARK_MARSHAL:<reason>` tag, so `json.encode`
70
+ * failures classify exactly like a script's own return-value marshal
71
+ * failures: `kind: 'marshal'`, with `reason` one of `./marshal`'s existing
72
+ * `ScriptMarshalReason` values.
73
+ *
74
+ * ## Rebinding safety
75
+ *
76
+ * `json.decode`/`json.encode` themselves close over `type`, `error`,
77
+ * `__smd_json_decode`, and `__smd_marshal_root` captured into locals AT
78
+ * PRELUDE-DEFINITION TIME -- before any untrusted script code runs -- so a
79
+ * later script reassigning any of those globals (or `json` itself) cannot
80
+ * neuter the type check or reach past the reused decoder/marshal walk. This
81
+ * is the same discipline `./json-decode` (finding A1) and `./capabilities`
82
+ * (findings A2/D1) already apply to their own internal calls into these
83
+ * exact two functions.
84
+ */
85
+ export declare function buildJsonTable(config: JsonTableConfig): {
86
+ rawGlobals: Record<string, (...args: never[]) => Promise<unknown>>;
87
+ preludeLua: string;
88
+ };
@@ -0,0 +1,151 @@
1
+ import { MARSHAL_ERROR_TAG } from './errors.js';
2
+ import { buildJsonDecodePrelude } from './json-decode.js';
3
+ import { checkJsonWithinLimits, finalizeMarshaledValue, } from './marshal.js';
4
+ /**
5
+ * Builds the raw JS globals and trusted Lua prelude for the `json` table:
6
+ * `json.decode(text)` and `json.encode(value)` (GitHub issue #40, slice 3a).
7
+ *
8
+ * `json` is pure computation with no I/O -- it never touches the network,
9
+ * the bundle filesystem, or the clock -- so `./sandbox` injects this
10
+ * unconditionally, for every tier, with no capability grant required. This
11
+ * mirrors how `./marshal`'s and `./doc`'s preludes are already injected
12
+ * unconditionally; `json` joins that same "always present" set rather than
13
+ * being wired through `./capabilities`' tier/grant machinery, which exists
14
+ * specifically to gate effectful or ambient-authority operations this
15
+ * table has none of.
16
+ *
17
+ * ## `json.decode`: reuses the EXISTING decoder and EXISTING budget
18
+ *
19
+ * The actual parse is `./json-decode`'s `__smd_json_decode` -- the same
20
+ * trusted, in-Lua JSON decoder `net.fetch_json`/`cache.get` already use in
21
+ * `./capabilities` (see that module's doc comment for why decoding must
22
+ * happen entirely in Lua rather than trusting wasmoon's own JS-object
23
+ * conversion). This function never writes a second decoder.
24
+ *
25
+ * Before that decoder ever sees the text, a JS-side precheck applies the
26
+ * exact same two-part budget `net.fetch_json` applies to a fetched body:
27
+ * the `maxFetchBytes` byte-size cap (an ordinary, untagged `error()`,
28
+ * `kind: 'runtime'` -- no `ScriptMarshalReason` value fits "too many
29
+ * bytes"), and `./marshal`'s `checkJsonWithinLimits` (the very function
30
+ * `./capabilities` calls) against the `JSON.parse`d value, tagged
31
+ * `MARK_MARSHAL:depth`/`MARK_MARSHAL:nodes` so `./sandbox`'s
32
+ * `classifyRuntimeError` reports it as `kind: 'marshal'`, `reason: 'depth'
33
+ * | 'nodes'` -- the same classification a `json.encode` budget violation
34
+ * gets (see below), so a script sees one consistent failure shape for "too
35
+ * deep or too wide" regardless of which direction it happened. Text that
36
+ * fails to `JSON.parse` at all is NOT diagnosed here in JS: it is
37
+ * handed straight to `__smd_json_decode`, which raises its own "malformed"
38
+ * error -- duplicating that diagnosis in two places risks the two
39
+ * disagreeing, and the Lua decoder's message is the one already documented.
40
+ *
41
+ * ## `json.encode`: reuses the EXISTING marshal walk
42
+ *
43
+ * `json.encode(value)` runs `value` through `__smd_marshal_root`
44
+ * (`./marshal`'s `buildMarshalPrelude`, already injected once per engine
45
+ * before this prelude runs) -- the SAME depth/node-capped, cycle-detecting,
46
+ * type-checking walk a script's own top-level return value already goes
47
+ * through. No second walk is written. The capped, marker-tagged result then
48
+ * crosses to JS as a single bounded function argument (safe by construction:
49
+ * the walk has already capped it, exactly as `./capabilities`' `cache.get`
50
+ * write path already relies on for the same reason), where
51
+ * `finalizeMarshaledValue` -- the same JS-side pass `./sandbox` runs on a
52
+ * script's return value -- strips the array marker and rejects a
53
+ * non-finite number, before the result is `JSON.stringify`d. A cycle,
54
+ * excess depth/nodes, a non-string table key, or a function/userdata/thread
55
+ * all raise the walk's existing `MARK_MARSHAL:<reason>` tag, so `json.encode`
56
+ * failures classify exactly like a script's own return-value marshal
57
+ * failures: `kind: 'marshal'`, with `reason` one of `./marshal`'s existing
58
+ * `ScriptMarshalReason` values.
59
+ *
60
+ * ## Rebinding safety
61
+ *
62
+ * `json.decode`/`json.encode` themselves close over `type`, `error`,
63
+ * `__smd_json_decode`, and `__smd_marshal_root` captured into locals AT
64
+ * PRELUDE-DEFINITION TIME -- before any untrusted script code runs -- so a
65
+ * later script reassigning any of those globals (or `json` itself) cannot
66
+ * neuter the type check or reach past the reused decoder/marshal walk. This
67
+ * is the same discipline `./json-decode` (finding A1) and `./capabilities`
68
+ * (findings A2/D1) already apply to their own internal calls into these
69
+ * exact two functions.
70
+ */
71
+ export function buildJsonTable(config) {
72
+ const { limits, maxFetchBytes } = config;
73
+ const rawGlobals = {};
74
+ rawGlobals.__smd_json_decode_precheck_raw = (async (text) => {
75
+ if (typeof text === 'string' && text.length > maxFetchBytes) {
76
+ // No `ScriptMarshalReason` value fits "too many bytes" (that taxonomy
77
+ // is depth/nodes/cycle/type/key-type/non-finite-number/nul-byte, none
78
+ // of which is a byte-size cap), so this is left untagged: an ordinary
79
+ // Lua error, classified `kind: 'runtime'` by `./sandbox` like any
80
+ // other `error()` call. It is still a clean, bounded, descriptive
81
+ // failure -- never a hang, never a crash -- which is what matters.
82
+ throw new Error(`json.decode: input exceeds the ${maxFetchBytes}-byte limit`);
83
+ }
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(text);
87
+ }
88
+ catch {
89
+ // Not valid JSON text: let `__smd_json_decode` (the Lua decoder)
90
+ // raise its own "malformed" error rather than diagnosing this twice,
91
+ // in two places, with two possibly-inconsistent messages.
92
+ return true;
93
+ }
94
+ const check = checkJsonWithinLimits(parsed, limits);
95
+ if (!check.ok) {
96
+ throw new Error(`${MARSHAL_ERROR_TAG}:${check.reason}: json.decode input ${check.message}`);
97
+ }
98
+ return true;
99
+ });
100
+ rawGlobals.__smd_json_encode_raw = (async (marshaledValue) => {
101
+ const finalized = finalizeMarshaledValue(marshaledValue);
102
+ if (!finalized.ok) {
103
+ throw new Error(`${MARSHAL_ERROR_TAG}:${finalized.reason}`);
104
+ }
105
+ const text = JSON.stringify(finalized.value);
106
+ // `JSON.stringify` only returns `undefined` for a value it cannot
107
+ // represent -- already excluded by `finalizeMarshaledValue` above -- so
108
+ // this is a defensive fallback that is never expected to trigger.
109
+ return text === undefined ? 'null' : text;
110
+ });
111
+ const preludeLua = `
112
+ ${buildJsonDecodePrelude(limits)}
113
+
114
+ -- Captured into locals HERE, at prelude-definition time -- see the module
115
+ -- doc comment's "Rebinding safety" section.
116
+ local __smd_json_type = type
117
+ local __smd_json_error = error
118
+ local __smd_json_decode_precheck = __smd_json_decode_precheck_raw
119
+ local __smd_json_encode_finish = __smd_json_encode_raw
120
+ local __smd_json_decode_fn = __smd_json_decode
121
+ local __smd_json_marshal_root = __smd_marshal_root
122
+ __smd_json_decode_precheck_raw = nil
123
+ __smd_json_encode_raw = nil
124
+ -- \`__smd_json_decode\` (defined by the injected \`./json-decode\` prelude
125
+ -- above) is only ever needed as a GLOBAL long enough for callers to capture
126
+ -- it into their own local -- exactly like \`__smd_net_get_json_decode\` and
127
+ -- \`__smd_cache_json_decode\` already do in \`./capabilities\`. This prelude
128
+ -- runs AFTER any of those (see \`./sandbox\`'s injection order), so every
129
+ -- earlier consumer has already captured its own reference by this point;
130
+ -- nilling the global here keeps \`json\` from being the run that leaves a
131
+ -- private \`__smd_\` name reachable on every single run (it is now injected
132
+ -- UNCONDITIONALLY, unlike \`net\`/\`cache\`, which only define this global
133
+ -- when actually configured) -- see the pass-3 residue probes
134
+ -- (\`require-pass3.probe.test.ts\`, \`doc.probe.test.ts\`), which assert NO
135
+ -- \`__smd_\`-prefixed global survives except \`__smd_marshal_root\`.
136
+ __smd_json_decode = nil
137
+
138
+ json = json or {}
139
+ json.decode = function(text)
140
+ if __smd_json_type(text) ~= "string" then
141
+ __smd_json_error("json.decode expects a string argument")
142
+ end
143
+ __smd_json_decode_precheck(text):await()
144
+ return __smd_json_decode_fn(text)
145
+ end
146
+ json.encode = function(value)
147
+ return __smd_json_encode_finish(__smd_json_marshal_root(value)):await()
148
+ end
149
+ `;
150
+ return { rawGlobals, preludeLua };
151
+ }
package/dist/sandbox.d.ts CHANGED
@@ -63,7 +63,10 @@ export type RunScriptResult = {
63
63
  * the real `require` global, sharing the same `bundle`/denial-recording
64
64
  * wiring (§8's bundle-local and pack-namespaced module sources).
65
65
  * 4. `./marshal` — inject the trusted node/depth-capped marshal walk that
66
- * the wrapped user code's return value is piped through.
66
+ * the wrapped user code's return value is piped through, then
67
+ * `./json-table` — the `json.decode`/`json.encode` table, unconditional
68
+ * and ungated (pure computation, no capability grant), reusing that
69
+ * same marshal walk and `./json-decode`'s existing decoder.
67
70
  * 5. A dedicated child thread (NOT `engine.doString`, which creates its
68
71
  * own internal thread we'd have no handle to — see `./limits`'s "hooks
69
72
  * are per-thread" note) gets the instruction/wall-clock hook installed,
package/dist/sandbox.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { LuaReturn } from 'wasmoon';
2
- import { buildCapabilities, } from './capabilities.js';
2
+ import { buildCapabilities, DEFAULT_MAX_FETCH_BYTES, } from './capabilities.js';
3
3
  import { buildDoc } from './doc.js';
4
4
  import { MARSHAL_ERROR_TAG, ScriptLimitError } from './errors.js';
5
5
  import { createEmptyLuaEngine } from './globals.js';
6
+ import { buildJsonTable } from './json-table.js';
6
7
  import { DEFAULT_LIMITS, installLimits } from './limits.js';
7
8
  import { buildMarshalPrelude, DEFAULT_MARSHAL_LIMITS, finalizeMarshaledValue, wrapUserCode, } from './marshal.js';
8
9
  import { buildRequire } from './require.js';
@@ -109,6 +110,20 @@ function extractMarshalReason(message) {
109
110
  return 'key-type';
110
111
  case 'nul-byte':
111
112
  return 'nul-byte';
113
+ case 'non-finite-number':
114
+ // Unlike the other cases above (all raised from INSIDE the Lua-side
115
+ // `__smd_marshal` walk itself), a non-finite number is normally
116
+ // caught by `finalizeMarshaledValue` running directly in JS on a
117
+ // successful run's already-returned value -- it never crosses back
118
+ // through Lua as a thrown, tagged error on that path, so this case
119
+ // was unreached before `./json-table` existed. `json.encode` reuses
120
+ // `finalizeMarshaledValue` itself (the same JS-side check, the same
121
+ // reason) but calls it from inside a host function invoked FROM Lua,
122
+ // so its rejection DOES round-trip through a thrown Lua error and
123
+ // land here -- this case keeps that reused reason classified
124
+ // identically to the return-value path, instead of falling through
125
+ // to the generic 'type' default.
126
+ return 'non-finite-number';
112
127
  case 'type':
113
128
  return 'type';
114
129
  default:
@@ -179,7 +194,10 @@ function classifyRuntimeError(err) {
179
194
  * the real `require` global, sharing the same `bundle`/denial-recording
180
195
  * wiring (§8's bundle-local and pack-namespaced module sources).
181
196
  * 4. `./marshal` — inject the trusted node/depth-capped marshal walk that
182
- * the wrapped user code's return value is piped through.
197
+ * the wrapped user code's return value is piped through, then
198
+ * `./json-table` — the `json.decode`/`json.encode` table, unconditional
199
+ * and ungated (pure computation, no capability grant), reusing that
200
+ * same marshal walk and `./json-decode`'s existing decoder.
183
201
  * 5. A dedicated child thread (NOT `engine.doString`, which creates its
184
202
  * own internal thread we'd have no handle to — see `./limits`'s "hooks
185
203
  * are per-thread" note) gets the instruction/wall-clock hook installed,
@@ -261,6 +279,19 @@ export async function runScript(options) {
261
279
  throw new Error(`sandbox assembly left a code-loading primitive reachable (${String(loadResidue)}); refusing to run`);
262
280
  }
263
281
  await engine.doString(buildMarshalPrelude(marshalLimits));
282
+ // ./json-table: the `json` table (GitHub issue #40, slice 3a). Pure
283
+ // computation, no I/O -- injected unconditionally, for every tier, with
284
+ // no capability grant, same as the marshal/doc preludes below and
285
+ // above it. Wired AFTER the marshal prelude because `json.encode`
286
+ // reuses `__smd_marshal_root`, defined by that prelude.
287
+ const jsonTable = buildJsonTable({
288
+ limits: marshalLimits,
289
+ maxFetchBytes: options.maxFetchBytes ?? DEFAULT_MAX_FETCH_BYTES,
290
+ });
291
+ for (const [name, fn] of Object.entries(jsonTable.rawGlobals)) {
292
+ engine.global.set(name, fn);
293
+ }
294
+ await engine.doString(jsonTable.preludeLua);
264
295
  // ./doc: the note-scoped read-only view (GitHub issue #33). Wired
265
296
  // AFTER the marshal prelude and, like `require`, wired unconditionally
266
297
  // — with no `options.doc` it is an empty listing, never an absent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/lua",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Sandboxed Lua 5.4 (wasmoon) execution runtime for Mark's document scripting: an empty-env global whitelist, two-tier capability-gated net/cache/bundle access, instruction-count/wall-clock/memory limits, and depth/size-capped Lua<->JS marshaling.",
5
5
  "keywords": [
6
6
  "markdown",
@@ -44,8 +44,8 @@
44
44
  "lint": "eslint ."
45
45
  },
46
46
  "dependencies": {
47
- "@markii/bundle": "^0.12.1",
48
- "@markii/runtime": "^0.12.1",
47
+ "@markii/bundle": "^0.13.0",
48
+ "@markii/runtime": "^0.13.0",
49
49
  "wasmoon": "^1.16.0"
50
50
  }
51
51
  }