@ai-ecoverse/slicc-kernel 1.27.0 → 1.28.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/dist/testing.js CHANGED
@@ -1,3 +1,98 @@
1
+ // src/kernel/net/loopback-names.ts
2
+ var HOST_LOOPBACK = "host.slicc.internal";
3
+ var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
4
+ var PAGE_LOOPBACK = "kernel.localhost";
5
+ var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
6
+
7
+ // src/kernel/net/routes.ts
8
+ var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
9
+ function ipv4Bytes(text) {
10
+ const parts = text.split(".");
11
+ if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
12
+ const bytes = parts.map(Number);
13
+ return bytes.every((b) => b <= 255) ? bytes : void 0;
14
+ }
15
+ function ipv6Groups(text) {
16
+ if (!text.includes(":")) return void 0;
17
+ let canonical;
18
+ try {
19
+ canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);
20
+ } catch {
21
+ return void 0;
22
+ }
23
+ const [head, tail] = canonical.split("::");
24
+ const part = (s) => s ? s.split(":").map((h) => Number.parseInt(h, 16)) : [];
25
+ const front = part(head);
26
+ const back = part(tail);
27
+ return tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
28
+ }
29
+ function toBits(values, width) {
30
+ return values.reduce((acc, v) => acc << BigInt(width) | BigInt(v), 0n);
31
+ }
32
+ function addressBits(host) {
33
+ const v4 = ipv4Bytes(host);
34
+ if (v4) return { v6: false, bits: toBits(v4, 8) };
35
+ const v6 = ipv6Groups(host);
36
+ return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
37
+ }
38
+ function parsePrefix(text) {
39
+ const parts = text.trim().split("/");
40
+ if (parts.length > 2) return void 0;
41
+ const [address = "", size] = parts;
42
+ const parsed = addressBits(address);
43
+ if (!parsed) return void 0;
44
+ const max = parsed.v6 ? 128 : 32;
45
+ const length = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
46
+ if (length < 0 || length > max) return void 0;
47
+ return { ...parsed, length };
48
+ }
49
+ function within(prefix, addr) {
50
+ if (prefix.v6 !== addr.v6) return false;
51
+ const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
52
+ return addr.bits >> shift === prefix.bits >> shift;
53
+ }
54
+ var RESERVED = [
55
+ "0.0.0.0/8",
56
+ "127.0.0.0/8",
57
+ "169.254.0.0/16",
58
+ "224.0.0.0/4",
59
+ "240.0.0.0/4",
60
+ "::/96",
61
+ "::ffff:0:0/96",
62
+ "fe80::/10",
63
+ "ff00::/8"
64
+ ].map((p) => parsePrefix(p));
65
+ var Routes = class {
66
+ table = [];
67
+ exit = false;
68
+ ipv6;
69
+ constructor(ipv6 = false) {
70
+ this.ipv6 = ipv6;
71
+ }
72
+ set(table) {
73
+ const parsed = (table.prefixes ?? []).map(parsePrefix);
74
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
75
+ if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
76
+ this.table = parsed;
77
+ this.exit = table.exit === true;
78
+ }
79
+ classify(host) {
80
+ const name = host.toLowerCase().replace(/\.$/, "");
81
+ if (name === "localhost" || name.endsWith(".localhost") || name === "0.0.0.0") {
82
+ return "loopback";
83
+ }
84
+ if (name === HOST_LOOPBACK || name === HOST_ADDRESS) return "host";
85
+ const addr = addressBits(name);
86
+ if (!addr) return "unreachable";
87
+ if (addr.v6 && addr.bits === 1n) return "loopback";
88
+ if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
89
+ if (RESERVED.some((p) => within(p, addr))) return "reserved";
90
+ if (addr.v6 && !this.ipv6) return "unreachable";
91
+ if (this.exit || this.table.some((p) => within(p, addr))) return "uplink";
92
+ return "unreachable";
93
+ }
94
+ };
95
+
1
96
  // src/kernel/net/uplink.ts
2
97
  function answerOf(result) {
3
98
  const answer = Array.isArray(result) ? { addresses: result } : result;
@@ -7,17 +102,118 @@ function answerOf(result) {
7
102
  ...typeof answer?.ttl === "number" && answer.ttl >= 0 ? { ttl: answer.ttl } : {}
8
103
  };
9
104
  }
105
+ var NEVER = new Routes(true);
10
106
 
11
107
  // src/testing.ts
108
+ var failure = (code, message) => Object.assign(new Error(message), { code });
109
+ var Stream = class {
110
+ queue = [];
111
+ ended = false;
112
+ broken;
113
+ waiters = [];
114
+ size = 0;
115
+ wake() {
116
+ const waiters = this.waiters;
117
+ this.waiters = [];
118
+ for (const wake of waiters) wake();
119
+ }
120
+ push(bytes) {
121
+ if (this.broken) return Promise.reject(this.broken);
122
+ if (this.ended) return Promise.reject(failure("EPIPE", "the stream is closed"));
123
+ return new Promise((taken, lost) => {
124
+ this.queue.push({ bytes: bytes.slice(), taken, lost });
125
+ this.size += bytes.length;
126
+ this.wake();
127
+ });
128
+ }
129
+ end() {
130
+ this.ended = true;
131
+ this.wake();
132
+ }
133
+ fail(error) {
134
+ this.broken = error;
135
+ for (const pending of this.queue.splice(0)) pending.lost(error);
136
+ this.size = 0;
137
+ this.wake();
138
+ }
139
+ async read() {
140
+ while (this.queue.length === 0 && !this.ended && !this.broken) {
141
+ await new Promise((resolve) => this.waiters.push(resolve));
142
+ }
143
+ if (this.broken) throw this.broken;
144
+ const next = this.queue.shift();
145
+ if (!next) return null;
146
+ this.size -= next.bytes.length;
147
+ next.taken();
148
+ this.wake();
149
+ return next.bytes;
150
+ }
151
+ async drained() {
152
+ while (this.size > 0) await new Promise((resolve) => this.waiters.push(resolve));
153
+ }
154
+ };
155
+ function named(host, port) {
156
+ return `${host.includes(":") ? `[${host}]` : host}:${port}`;
157
+ }
158
+ function hang(signal) {
159
+ return new Promise((_, reject) => {
160
+ signal.addEventListener("abort", () => reject(failure("ECONNABORTED", "dial aborted")), {
161
+ once: true
162
+ });
163
+ });
164
+ }
12
165
  function fakeUplink(options = {}) {
13
166
  const asked = [];
167
+ const dialled = [];
168
+ let nextPort = 4e4;
14
169
  return {
15
170
  traits: { tcp: true, udp: false, ipv6: options.ipv6 === true },
16
171
  ...options.routes ? { routes: options.routes } : {},
17
172
  asked,
173
+ dialled,
18
174
  resolve: async (name, family) => {
19
175
  asked.push({ name, family });
20
176
  return answerOf(options.names?.[name] ?? []);
177
+ },
178
+ dial: async ({ host, port, signal }) => {
179
+ dialled.push({ host, port });
180
+ const peer = options.peers?.[named(host, port)];
181
+ if (!peer) throw failure("ECONNREFUSED", `nothing listens on ${named(host, port)}`);
182
+ if ("error" in peer) throw failure(peer.error, `${named(host, port)}: ${peer.error}`);
183
+ if ("hang" in peer) return hang(signal);
184
+ const up = new Stream();
185
+ const down = new Stream();
186
+ const closed = Promise.withResolvers();
187
+ peer({
188
+ read: () => up.read(),
189
+ write: (bytes) => void down.push(bytes).catch(() => void 0),
190
+ end: () => down.end(),
191
+ reset: (code = "ECONNRESET") => {
192
+ const error = failure(code, `the peer reset the connection (${code})`);
193
+ up.fail(error);
194
+ down.fail(error);
195
+ },
196
+ get buffered() {
197
+ return down.size;
198
+ },
199
+ drained: () => down.drained(),
200
+ closed: closed.promise
201
+ });
202
+ return {
203
+ localAddr: named(options.address ?? "100.100.100.100", nextPort++),
204
+ remoteAddr: named(host, port),
205
+ read: () => down.read(),
206
+ write: async (bytes) => {
207
+ await up.push(bytes);
208
+ return bytes.length;
209
+ },
210
+ closeWrite: () => up.end(),
211
+ close: () => {
212
+ up.end();
213
+ down.end();
214
+ closed.resolve();
215
+ }
216
+ };
21
217
  }
22
218
  };
23
219
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/kernel/net/uplink.ts", "../src/testing.ts"],
4
- "sourcesContent": ["import type { RouteTable } from './routes.ts';\n\nexport interface UplinkTraits {\n tcp: true;\n udp: false;\n ipv6: boolean;\n}\n\nexport type ResolveFamily = 0 | 4 | 6;\n\nexport interface ResolveAnswer {\n addresses: string[];\n ttl?: number;\n}\n\nexport interface NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes?: RouteTable;\n resolve(\n name: string,\n family: ResolveFamily,\n signal?: AbortSignal\n ): Promise<string[] | ResolveAnswer>;\n}\n\nexport type UplinkCall =\n | { uplink: 'resolve'; uid: number; name: string; family: ResolveFamily }\n | { uplink: 'cancel'; uid: number };\n\nexport type UplinkReply =\n | { uplink: 'resolved'; uid: number; addresses: string[]; ttl?: number }\n | { uplink: 'error'; uid: number; message: string; code?: string };\n\nexport interface UplinkPeer {\n postMessage(message: UplinkReply): void;\n}\n\nexport interface UplinkPort {\n postMessage(message: UplinkCall): void;\n}\n\nexport function answerOf(result: string[] | ResolveAnswer): ResolveAnswer {\n const answer = Array.isArray(result) ? { addresses: result } : result;\n const addresses = Array.isArray(answer?.addresses)\n ? answer.addresses.filter((a): a is string => typeof a === 'string')\n : [];\n return {\n addresses,\n ...(typeof answer?.ttl === 'number' && answer.ttl >= 0 ? { ttl: answer.ttl } : {}),\n };\n}\n\nfunction failure(uid: number, e: unknown): UplinkReply {\n const code = (e as { code?: unknown } | null)?.code;\n return {\n uplink: 'error',\n uid,\n message: e instanceof Error ? e.message : String(e),\n ...(typeof code === 'string' ? { code } : {}),\n };\n}\n\nexport interface UplinkServer {\n answer(call: UplinkCall): void;\n close(): void;\n}\n\nexport function serveUplink(peer: UplinkPeer, uplink: NetworkUplink): UplinkServer {\n const pending = new Map<number, AbortController>();\n const answer = (call: UplinkCall) => {\n if (call.uplink === 'cancel') return pending.get(call.uid)?.abort();\n const abort = new AbortController();\n pending.set(call.uid, abort);\n void (async () => {\n try {\n const answer = answerOf(await uplink.resolve(call.name, call.family, abort.signal));\n peer.postMessage({ uplink: 'resolved', uid: call.uid, ...answer });\n } catch (e) {\n peer.postMessage(failure(call.uid, e));\n } finally {\n pending.delete(call.uid);\n }\n })();\n };\n return {\n answer,\n close: () => {\n for (const abort of pending.values()) abort.abort();\n },\n };\n}\n\nexport class RemoteUplink implements NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes: RouteTable | undefined;\n private readonly port: UplinkPort;\n private readonly waiting = new Map<number, PromiseWithResolvers<ResolveAnswer>>();\n private nextId = 0;\n\n constructor(port: UplinkPort, traits: UplinkTraits, routes?: RouteTable) {\n this.port = port;\n this.traits = traits;\n this.routes = routes;\n }\n\n receive(reply: UplinkReply): void {\n const waiter = this.waiting.get(reply.uid);\n if (!waiter) return;\n this.waiting.delete(reply.uid);\n if (reply.uplink === 'error') {\n waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));\n } else waiter.resolve(answerOf(reply));\n }\n\n resolve(name: string, family: ResolveFamily, signal?: AbortSignal): Promise<ResolveAnswer> {\n const uid = ++this.nextId;\n const waiter = Promise.withResolvers<ResolveAnswer>();\n this.waiting.set(uid, waiter);\n const abort = () => {\n this.waiting.delete(uid);\n this.port.postMessage({ uplink: 'cancel', uid });\n waiter.reject(signal?.reason);\n };\n signal?.addEventListener('abort', abort, { once: true });\n const settled = () => signal?.removeEventListener('abort', abort);\n waiter.promise.then(settled, settled);\n this.port.postMessage({ uplink: 'resolve', uid, name, family });\n return waiter.promise;\n }\n}\n", "import type { RouteTable } from './kernel/net/routes.ts';\nimport {\n answerOf,\n type NetworkUplink,\n type ResolveAnswer,\n type ResolveFamily,\n} from './kernel/net/uplink.ts';\n\nexport type { NetworkUplink, ResolveAnswer, ResolveFamily, RouteTable };\n\nexport interface FakeUplinkOptions {\n names?: Readonly<Record<string, string[] | ResolveAnswer>>;\n routes?: RouteTable;\n ipv6?: boolean;\n}\n\nexport interface FakeUplink extends NetworkUplink {\n readonly asked: Array<{ name: string; family: ResolveFamily }>;\n}\n\nexport function fakeUplink(options: FakeUplinkOptions = {}): FakeUplink {\n const asked: FakeUplink['asked'] = [];\n return {\n traits: { tcp: true, udp: false, ipv6: options.ipv6 === true },\n ...(options.routes ? { routes: options.routes } : {}),\n asked,\n resolve: async (name, family) => {\n asked.push({ name, family });\n return answerOf(options.names?.[name] ?? []);\n },\n };\n}\n"],
5
- "mappings": ";AAyCO,SAAS,SAAS,QAAiD;AACxE,QAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,EAAE,WAAW,OAAO,IAAI;AAC/D,QAAM,YAAY,MAAM,QAAQ,QAAQ,SAAS,IAC7C,OAAO,UAAU,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IACjE,CAAC;AACL,SAAO;AAAA,IACL;AAAA,IACA,GAAI,OAAO,QAAQ,QAAQ,YAAY,OAAO,OAAO,IAAI,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,EAClF;AACF;;;AC9BO,SAAS,WAAW,UAA6B,CAAC,GAAe;AACtE,QAAM,QAA6B,CAAC;AACpC,SAAO;AAAA,IACL,QAAQ,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,QAAQ,SAAS,KAAK;AAAA,IAC7D,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACnD;AAAA,IACA,SAAS,OAAO,MAAM,WAAW;AAC/B,YAAM,KAAK,EAAE,MAAM,OAAO,CAAC;AAC3B,aAAO,SAAS,QAAQ,QAAQ,IAAI,KAAK,CAAC,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;",
3
+ "sources": ["../src/kernel/net/loopback-names.ts", "../src/kernel/net/routes.ts", "../src/kernel/net/uplink.ts", "../src/testing.ts"],
4
+ "sourcesContent": ["export const HOST_LOOPBACK = 'host.slicc.internal';\n\nexport const HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2] as const;\n\nexport const PAGE_LOOPBACK = 'kernel.localhost';\n\nexport const PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;\n\nexport function toHostLoopback(href: string): string {\n const url = new URL(href);\n if (url.hostname.replace(/\\.$/, '').toLowerCase() !== HOST_LOOPBACK) return href;\n url.hostname = '127.0.0.1';\n return url.href;\n}\n\nexport function hostsFile(): string {\n return `127.0.0.1 localhost\\n::1 localhost ip6-localhost\\n${HOST_LOOPBACK_ADDRESS.join('.')} ${HOST_LOOPBACK}\\n`;\n}\n", "import { HOST_LOOPBACK, HOST_LOOPBACK_ADDRESS } from './loopback-names.ts';\n\nexport interface RouteTable {\n prefixes: readonly string[];\n exit: boolean;\n}\n\nexport type Destination = 'loopback' | 'host' | 'reserved' | 'uplink' | 'unreachable';\n\ninterface Prefix {\n v6: boolean;\n bits: bigint;\n length: number;\n}\n\nconst HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join('.');\n\nexport function ipv4Bytes(text: string): number[] | undefined {\n const parts = text.split('.');\n if (parts.length !== 4 || !parts.every((p) => /^\\d{1,3}$/.test(p))) return undefined;\n const bytes = parts.map(Number);\n return bytes.every((b) => b <= 255) ? bytes : undefined;\n}\n\nexport function ipv6Groups(text: string): number[] | undefined {\n if (!text.includes(':')) return undefined;\n let canonical: string;\n try {\n canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);\n } catch {\n return undefined;\n }\n const [head, tail] = canonical.split('::');\n const part = (s: string | undefined) =>\n s ? s.split(':').map((h) => Number.parseInt(h, 16)) : [];\n const front = part(head);\n const back = part(tail);\n return tail === undefined\n ? front\n : [...front, ...new Array<number>(8 - front.length - back.length).fill(0), ...back];\n}\n\nfunction toBits(values: number[], width: number): bigint {\n return values.reduce((acc, v) => (acc << BigInt(width)) | BigInt(v), 0n);\n}\n\nfunction addressBits(host: string): { v6: boolean; bits: bigint } | undefined {\n const v4 = ipv4Bytes(host);\n if (v4) return { v6: false, bits: toBits(v4, 8) };\n const v6 = ipv6Groups(host);\n return v6 ? { v6: true, bits: toBits(v6, 16) } : undefined;\n}\n\nfunction parsePrefix(text: string): Prefix | undefined {\n const parts = text.trim().split('/');\n if (parts.length > 2) return undefined;\n const [address = '', size] = parts;\n const parsed = addressBits(address);\n if (!parsed) return undefined;\n const max = parsed.v6 ? 128 : 32;\n const length = size === undefined ? max : /^\\d{1,3}$/.test(size) ? Number(size) : -1;\n if (length < 0 || length > max) return undefined;\n return { ...parsed, length };\n}\n\nfunction within(prefix: Prefix, addr: { v6: boolean; bits: bigint }): boolean {\n if (prefix.v6 !== addr.v6) return false;\n const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);\n return addr.bits >> shift === prefix.bits >> shift;\n}\n\nconst RESERVED = [\n '0.0.0.0/8',\n '127.0.0.0/8',\n '169.254.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n '::/96',\n '::ffff:0:0/96',\n 'fe80::/10',\n 'ff00::/8',\n].map((p) => parsePrefix(p) as Prefix);\n\nexport class Routes {\n private table: Prefix[] = [];\n private exit = false;\n readonly ipv6: boolean;\n\n constructor(ipv6 = false) {\n this.ipv6 = ipv6;\n }\n\n set(table: RouteTable): void {\n const parsed = (table.prefixes ?? []).map(parsePrefix);\n const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);\n if (bad !== undefined) throw new Error(`not an address prefix: ${bad}`);\n this.table = parsed as Prefix[];\n this.exit = table.exit === true;\n }\n\n classify(host: string): Destination {\n const name = host.toLowerCase().replace(/\\.$/, '');\n if (name === 'localhost' || name.endsWith('.localhost') || name === '0.0.0.0') {\n return 'loopback';\n }\n if (name === HOST_LOOPBACK || name === HOST_ADDRESS) return 'host';\n const addr = addressBits(name);\n if (!addr) return 'unreachable';\n if (addr.v6 && addr.bits === 1n) return 'loopback';\n if (!addr.v6 && addr.bits >> 24n === 127n) return 'loopback';\n if (RESERVED.some((p) => within(p, addr))) return 'reserved';\n if (addr.v6 && !this.ipv6) return 'unreachable';\n if (this.exit || this.table.some((p) => within(p, addr))) return 'uplink';\n return 'unreachable';\n }\n}\n", "import { Routes, type RouteTable } from './routes.ts';\n\nexport interface UplinkTraits {\n tcp: true;\n udp: false;\n ipv6: boolean;\n}\n\nexport type ResolveFamily = 0 | 4 | 6;\n\nexport interface ResolveAnswer {\n addresses: string[];\n ttl?: number;\n}\n\nexport interface UplinkConn {\n readonly localAddr: string;\n readonly remoteAddr: string;\n read(): Promise<Uint8Array | null>;\n write(bytes: Uint8Array): Promise<number>;\n closeWrite(): void;\n close(): void;\n}\n\nexport interface UplinkDial {\n network: 'tcp';\n host: string;\n port: number;\n signal: AbortSignal;\n}\n\nexport interface NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes?: RouteTable;\n resolve(\n name: string,\n family: ResolveFamily,\n signal?: AbortSignal\n ): Promise<string[] | ResolveAnswer>;\n dial(req: UplinkDial): Promise<UplinkConn>;\n}\n\nexport type UplinkCall =\n | { uplink: 'resolve'; uid: number; name: string; family: ResolveFamily }\n | { uplink: 'dial'; uid: number; host: string; port: number }\n | { uplink: 'cancel'; uid: number }\n | { uplink: 'read'; uid: number; cid: number }\n | { uplink: 'write'; uid: number; cid: number; bytes: Uint8Array }\n | { uplink: 'close-write'; cid: number }\n | { uplink: 'close'; cid: number };\n\nexport type UplinkReply =\n | { uplink: 'resolved'; uid: number; addresses: string[]; ttl?: number }\n | { uplink: 'dialled'; uid: number; cid: number; localAddr: string; remoteAddr: string }\n | { uplink: 'data'; uid: number; bytes: Uint8Array | null }\n | { uplink: 'written'; uid: number; n: number }\n | { uplink: 'error'; uid: number; message: string; code?: string };\n\nexport interface UplinkPeer {\n postMessage(message: UplinkReply, transfer?: Transferable[]): void;\n}\n\nexport interface UplinkPort {\n postMessage(message: UplinkCall, transfer?: Transferable[]): void;\n}\n\nexport interface UplinkServer {\n answer(call: UplinkCall): void;\n close(): void;\n}\n\nexport function answerOf(result: string[] | ResolveAnswer): ResolveAnswer {\n const answer = Array.isArray(result) ? { addresses: result } : result;\n const addresses = Array.isArray(answer?.addresses)\n ? answer.addresses.filter((a): a is string => typeof a === 'string')\n : [];\n return {\n addresses,\n ...(typeof answer?.ttl === 'number' && answer.ttl >= 0 ? { ttl: answer.ttl } : {}),\n };\n}\n\nfunction failure(uid: number, e: unknown): UplinkReply {\n const code = (e as { code?: unknown } | null)?.code;\n return {\n uplink: 'error',\n uid,\n message: e instanceof Error ? e.message : String(e),\n ...(typeof code === 'string' ? { code } : {}),\n };\n}\n\nconst NEVER = new Routes(true);\n\nexport function serveUplink(peer: UplinkPeer, uplink: NetworkUplink): UplinkServer {\n const conns = new Map<number, UplinkConn>();\n const pending = new Map<number, AbortController>();\n let nextCid = 0;\n let closed = false;\n const run = (uid: number, work: (signal: AbortSignal) => Promise<UplinkReply | undefined>) => {\n const abort = new AbortController();\n pending.set(uid, abort);\n void work(abort.signal)\n .then(\n (reply) => reply && peer.postMessage(reply, transferOf(reply)),\n (e: unknown) => peer.postMessage(failure(uid, e))\n )\n .finally(() => pending.delete(uid));\n };\n const conn = (cid: number): UplinkConn => {\n const found = conns.get(cid);\n if (!found) throw Object.assign(new Error('no such uplink connection'), { code: 'EBADF' });\n return found;\n };\n const dial = async (call: Extract<UplinkCall, { uplink: 'dial' }>, signal: AbortSignal) => {\n const kind = NEVER.classify(call.host);\n if (kind === 'loopback' || kind === 'host' || kind === 'reserved') {\n throw Object.assign(new Error(`the uplink does not carry ${call.host}`), {\n code: 'ENETUNREACH',\n });\n }\n const { host, port } = call;\n const opened = await uplink.dial({ network: 'tcp', host, port, signal });\n if (signal.aborted || closed) {\n opened.close();\n return undefined;\n }\n const cid = ++nextCid;\n conns.set(cid, opened);\n const { localAddr, remoteAddr } = opened;\n return { uplink: 'dialled' as const, uid: call.uid, cid, localAddr, remoteAddr };\n };\n const drop = (cid: number) => {\n const found = conns.get(cid);\n conns.delete(cid);\n found?.close();\n };\n return {\n answer(call) {\n switch (call.uplink) {\n case 'resolve':\n return run(call.uid, async (signal) => ({\n uplink: 'resolved',\n uid: call.uid,\n ...answerOf(await uplink.resolve(call.name, call.family, signal)),\n }));\n case 'dial':\n return run(call.uid, (signal) => dial(call, signal));\n case 'cancel':\n return pending.get(call.uid)?.abort();\n case 'read':\n return run(call.uid, async () => ({\n uplink: 'data',\n uid: call.uid,\n bytes: (await conn(call.cid).read())?.slice() ?? null,\n }));\n case 'write':\n return run(call.uid, async () => ({\n uplink: 'written',\n uid: call.uid,\n n: await conn(call.cid).write(call.bytes),\n }));\n case 'close-write':\n return conns.get(call.cid)?.closeWrite();\n default:\n return drop(call.cid);\n }\n },\n close() {\n closed = true;\n for (const abort of pending.values()) abort.abort();\n for (const cid of [...conns.keys()]) drop(cid);\n },\n };\n}\n\nfunction transferOf(reply: UplinkReply): Transferable[] | undefined {\n if (reply.uplink !== 'data' || !reply.bytes) return undefined;\n return [reply.bytes.buffer as ArrayBuffer];\n}\n\ntype Answer = Exclude<UplinkReply, { uplink: 'error' }>;\n\nexport class RemoteUplink implements NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes: RouteTable | undefined;\n private readonly port: UplinkPort;\n private readonly waiting = new Map<number, PromiseWithResolvers<Answer>>();\n private nextId = 0;\n\n constructor(port: UplinkPort, traits: UplinkTraits, routes?: RouteTable) {\n this.port = port;\n this.traits = traits;\n this.routes = routes;\n }\n\n receive(reply: UplinkReply): void {\n const waiter = this.waiting.get(reply.uid);\n if (!waiter) return;\n this.waiting.delete(reply.uid);\n if (reply.uplink === 'error') {\n waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));\n } else waiter.resolve(reply);\n }\n\n private ask<T extends Answer>(\n call: Extract<UplinkCall, { uid: number }>,\n signal?: AbortSignal,\n transfer?: Transferable[]\n ): Promise<T> {\n const waiter = Promise.withResolvers<Answer>();\n this.waiting.set(call.uid, waiter);\n const abort = () => {\n this.waiting.delete(call.uid);\n this.port.postMessage({ uplink: 'cancel', uid: call.uid });\n waiter.reject(signal?.reason);\n };\n signal?.addEventListener('abort', abort, { once: true });\n const settled = () => signal?.removeEventListener('abort', abort);\n waiter.promise.then(settled, settled);\n this.port.postMessage(call, transfer);\n return waiter.promise as Promise<T>;\n }\n\n async resolve(name: string, family: ResolveFamily, signal?: AbortSignal) {\n const reply = await this.ask<Extract<Answer, { uplink: 'resolved' }>>(\n { uplink: 'resolve', uid: ++this.nextId, name, family },\n signal\n );\n return answerOf(reply);\n }\n\n async dial(req: UplinkDial): Promise<UplinkConn> {\n req.signal.throwIfAborted();\n const { host, port } = req;\n const opened = await this.ask<Extract<Answer, { uplink: 'dialled' }>>(\n { uplink: 'dial', uid: ++this.nextId, host, port },\n req.signal\n );\n const { cid, localAddr, remoteAddr } = opened;\n return {\n localAddr,\n remoteAddr,\n read: async () => {\n const got = await this.ask<Extract<Answer, { uplink: 'data' }>>({\n uplink: 'read',\n uid: ++this.nextId,\n cid,\n });\n return got.bytes;\n },\n write: async (bytes) => {\n const copy = bytes.slice();\n const call = { uplink: 'write' as const, uid: ++this.nextId, cid, bytes: copy };\n const done = await this.ask<Extract<Answer, { uplink: 'written' }>>(call, undefined, [\n copy.buffer,\n ]);\n return done.n;\n },\n closeWrite: () => this.port.postMessage({ uplink: 'close-write', cid }),\n close: () => this.port.postMessage({ uplink: 'close', cid }),\n };\n }\n}\n", "import type { RouteTable } from './kernel/net/routes.ts';\nimport {\n answerOf,\n type NetworkUplink,\n type ResolveAnswer,\n type ResolveFamily,\n type UplinkConn,\n type UplinkDial,\n} from './kernel/net/uplink.ts';\n\nexport type { NetworkUplink, ResolveAnswer, ResolveFamily, RouteTable, UplinkConn, UplinkDial };\n\nexport interface FakePeerSocket {\n read(): Promise<Uint8Array | null>;\n write(bytes: Uint8Array): void;\n end(): void;\n reset(code?: string): void;\n readonly buffered: number;\n drained(): Promise<void>;\n readonly closed: Promise<void>;\n}\n\nexport type FakePeer = ((socket: FakePeerSocket) => void) | { error: string } | { hang: true };\n\nexport interface FakeUplinkOptions {\n names?: Readonly<Record<string, string[] | ResolveAnswer>>;\n routes?: RouteTable;\n ipv6?: boolean;\n peers?: Readonly<Record<string, FakePeer>>;\n address?: string;\n}\n\nexport interface FakeUplink extends NetworkUplink {\n readonly asked: Array<{ name: string; family: ResolveFamily }>;\n readonly dialled: Array<{ host: string; port: number }>;\n}\n\nconst failure = (code: string, message: string) => Object.assign(new Error(message), { code });\n\nclass Stream {\n private readonly queue: Array<{\n bytes: Uint8Array;\n taken: () => void;\n lost: (error: Error) => void;\n }> = [];\n private ended = false;\n private broken: Error | undefined;\n private waiters: Array<() => void> = [];\n size = 0;\n\n private wake(): void {\n const waiters = this.waiters;\n this.waiters = [];\n for (const wake of waiters) wake();\n }\n\n push(bytes: Uint8Array): Promise<void> {\n if (this.broken) return Promise.reject(this.broken);\n if (this.ended) return Promise.reject(failure('EPIPE', 'the stream is closed'));\n return new Promise((taken, lost) => {\n this.queue.push({ bytes: bytes.slice(), taken, lost });\n this.size += bytes.length;\n this.wake();\n });\n }\n\n end(): void {\n this.ended = true;\n this.wake();\n }\n\n fail(error: Error): void {\n this.broken = error;\n for (const pending of this.queue.splice(0)) pending.lost(error);\n this.size = 0;\n this.wake();\n }\n\n async read(): Promise<Uint8Array | null> {\n while (this.queue.length === 0 && !this.ended && !this.broken) {\n await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n if (this.broken) throw this.broken;\n const next = this.queue.shift();\n if (!next) return null;\n this.size -= next.bytes.length;\n next.taken();\n this.wake();\n return next.bytes;\n }\n\n async drained(): Promise<void> {\n while (this.size > 0) await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n}\n\nfunction named(host: string, port: number): string {\n return `${host.includes(':') ? `[${host}]` : host}:${port}`;\n}\n\nfunction hang(signal: AbortSignal): Promise<never> {\n return new Promise((_, reject) => {\n signal.addEventListener('abort', () => reject(failure('ECONNABORTED', 'dial aborted')), {\n once: true,\n });\n });\n}\n\nexport function fakeUplink(options: FakeUplinkOptions = {}): FakeUplink {\n const asked: FakeUplink['asked'] = [];\n const dialled: FakeUplink['dialled'] = [];\n let nextPort = 40000;\n return {\n traits: { tcp: true, udp: false, ipv6: options.ipv6 === true },\n ...(options.routes ? { routes: options.routes } : {}),\n asked,\n dialled,\n resolve: async (name, family) => {\n asked.push({ name, family });\n return answerOf(options.names?.[name] ?? []);\n },\n dial: async ({ host, port, signal }) => {\n dialled.push({ host, port });\n const peer = options.peers?.[named(host, port)];\n if (!peer) throw failure('ECONNREFUSED', `nothing listens on ${named(host, port)}`);\n if ('error' in peer) throw failure(peer.error, `${named(host, port)}: ${peer.error}`);\n if ('hang' in peer) return hang(signal);\n const up = new Stream();\n const down = new Stream();\n const closed = Promise.withResolvers<void>();\n peer({\n read: () => up.read(),\n write: (bytes) => void down.push(bytes).catch(() => undefined),\n end: () => down.end(),\n reset: (code = 'ECONNRESET') => {\n const error = failure(code, `the peer reset the connection (${code})`);\n up.fail(error);\n down.fail(error);\n },\n get buffered() {\n return down.size;\n },\n drained: () => down.drained(),\n closed: closed.promise,\n });\n return {\n localAddr: named(options.address ?? '100.100.100.100', nextPort++),\n remoteAddr: named(host, port),\n read: () => down.read(),\n write: async (bytes) => {\n await up.push(bytes);\n return bytes.length;\n },\n closeWrite: () => up.end(),\n close: () => {\n up.end();\n down.end();\n closed.resolve();\n },\n };\n },\n };\n}\n"],
5
+ "mappings": ";AAAO,IAAM,gBAAgB;AAEtB,IAAM,wBAAwB,CAAC,IAAI,GAAG,GAAG,CAAC;AAE1C,IAAM,gBAAgB;AAEtB,IAAM,oBAAoB,iBAAiB,aAAa;;;ACS/D,IAAM,eAAe,sBAAsB,KAAK,GAAG;AAE5C,SAAS,UAAU,MAAoC;AAC5D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,WAAW,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,EAAG,QAAO;AAC3E,QAAM,QAAQ,MAAM,IAAI,MAAM;AAC9B,SAAO,MAAM,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ;AAChD;AAEO,SAAS,WAAW,MAAoC;AAC7D,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO;AAChC,MAAI;AACJ,MAAI;AACF,gBAAY,IAAI,IAAI,WAAW,IAAI,IAAI,EAAE,SAAS,MAAM,GAAG,EAAE;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,CAAC,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI;AACzC,QAAM,OAAO,CAAC,MACZ,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC;AACzD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,OAAO,KAAK,IAAI;AACtB,SAAO,SAAS,SACZ,QACA,CAAC,GAAG,OAAO,GAAG,IAAI,MAAc,IAAI,MAAM,SAAS,KAAK,MAAM,EAAE,KAAK,CAAC,GAAG,GAAG,IAAI;AACtF;AAEA,SAAS,OAAO,QAAkB,OAAuB;AACvD,SAAO,OAAO,OAAO,CAAC,KAAK,MAAO,OAAO,OAAO,KAAK,IAAK,OAAO,CAAC,GAAG,EAAE;AACzE;AAEA,SAAS,YAAY,MAAyD;AAC5E,QAAM,KAAK,UAAU,IAAI;AACzB,MAAI,GAAI,QAAO,EAAE,IAAI,OAAO,MAAM,OAAO,IAAI,CAAC,EAAE;AAChD,QAAM,KAAK,WAAW,IAAI;AAC1B,SAAO,KAAK,EAAE,IAAI,MAAM,MAAM,OAAO,IAAI,EAAE,EAAE,IAAI;AACnD;AAEA,SAAS,YAAY,MAAkC;AACrD,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,GAAG;AACnC,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,CAAC,UAAU,IAAI,IAAI,IAAI;AAC7B,QAAM,SAAS,YAAY,OAAO;AAClC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,MAAM,OAAO,KAAK,MAAM;AAC9B,QAAM,SAAS,SAAS,SAAY,MAAM,YAAY,KAAK,IAAI,IAAI,OAAO,IAAI,IAAI;AAClF,MAAI,SAAS,KAAK,SAAS,IAAK,QAAO;AACvC,SAAO,EAAE,GAAG,QAAQ,OAAO;AAC7B;AAEA,SAAS,OAAO,QAAgB,MAA8C;AAC5E,MAAI,OAAO,OAAO,KAAK,GAAI,QAAO;AAClC,QAAM,QAAQ,QAAQ,OAAO,KAAK,MAAM,MAAM,OAAO,MAAM;AAC3D,SAAO,KAAK,QAAQ,UAAU,OAAO,QAAQ;AAC/C;AAEA,IAAM,WAAW;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,IAAI,CAAC,MAAM,YAAY,CAAC,CAAW;AAE9B,IAAM,SAAN,MAAa;AAAA,EACV,QAAkB,CAAC;AAAA,EACnB,OAAO;AAAA,EACN;AAAA,EAET,YAAY,OAAO,OAAO;AACxB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,IAAI,OAAyB;AAC3B,UAAM,UAAU,MAAM,YAAY,CAAC,GAAG,IAAI,WAAW;AACrD,UAAM,OAAO,MAAM,YAAY,CAAC,GAAG,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;AAC5D,QAAI,QAAQ,OAAW,OAAM,IAAI,MAAM,0BAA0B,GAAG,EAAE;AACtE,SAAK,QAAQ;AACb,SAAK,OAAO,MAAM,SAAS;AAAA,EAC7B;AAAA,EAEA,SAAS,MAA2B;AAClC,UAAM,OAAO,KAAK,YAAY,EAAE,QAAQ,OAAO,EAAE;AACjD,QAAI,SAAS,eAAe,KAAK,SAAS,YAAY,KAAK,SAAS,WAAW;AAC7E,aAAO;AAAA,IACT;AACA,QAAI,SAAS,iBAAiB,SAAS,aAAc,QAAO;AAC5D,UAAM,OAAO,YAAY,IAAI;AAC7B,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,MAAM,KAAK,SAAS,GAAI,QAAO;AACxC,QAAI,CAAC,KAAK,MAAM,KAAK,QAAQ,QAAQ,KAAM,QAAO;AAClD,QAAI,SAAS,KAAK,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAG,QAAO;AAClD,QAAI,KAAK,MAAM,CAAC,KAAK,KAAM,QAAO;AAClC,QAAI,KAAK,QAAQ,KAAK,MAAM,KAAK,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAG,QAAO;AACjE,WAAO;AAAA,EACT;AACF;;;AC5CO,SAAS,SAAS,QAAiD;AACxE,QAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,EAAE,WAAW,OAAO,IAAI;AAC/D,QAAM,YAAY,MAAM,QAAQ,QAAQ,SAAS,IAC7C,OAAO,UAAU,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IACjE,CAAC;AACL,SAAO;AAAA,IACL;AAAA,IACA,GAAI,OAAO,QAAQ,QAAQ,YAAY,OAAO,OAAO,IAAI,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,EAClF;AACF;AAYA,IAAM,QAAQ,IAAI,OAAO,IAAI;;;ACvD7B,IAAM,UAAU,CAAC,MAAc,YAAoB,OAAO,OAAO,IAAI,MAAM,OAAO,GAAG,EAAE,KAAK,CAAC;AAE7F,IAAM,SAAN,MAAa;AAAA,EACM,QAIZ,CAAC;AAAA,EACE,QAAQ;AAAA,EACR;AAAA,EACA,UAA6B,CAAC;AAAA,EACtC,OAAO;AAAA,EAEC,OAAa;AACnB,UAAM,UAAU,KAAK;AACrB,SAAK,UAAU,CAAC;AAChB,eAAW,QAAQ,QAAS,MAAK;AAAA,EACnC;AAAA,EAEA,KAAK,OAAkC;AACrC,QAAI,KAAK,OAAQ,QAAO,QAAQ,OAAO,KAAK,MAAM;AAClD,QAAI,KAAK,MAAO,QAAO,QAAQ,OAAO,QAAQ,SAAS,sBAAsB,CAAC;AAC9E,WAAO,IAAI,QAAQ,CAAC,OAAO,SAAS;AAClC,WAAK,MAAM,KAAK,EAAE,OAAO,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC;AACrD,WAAK,QAAQ,MAAM;AACnB,WAAK,KAAK;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAEA,MAAY;AACV,SAAK,QAAQ;AACb,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,KAAK,OAAoB;AACvB,SAAK,SAAS;AACd,eAAW,WAAW,KAAK,MAAM,OAAO,CAAC,EAAG,SAAQ,KAAK,KAAK;AAC9D,SAAK,OAAO;AACZ,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,MAAM,OAAmC;AACvC,WAAO,KAAK,MAAM,WAAW,KAAK,CAAC,KAAK,SAAS,CAAC,KAAK,QAAQ;AAC7D,YAAM,IAAI,QAAc,CAAC,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE;AACA,QAAI,KAAK,OAAQ,OAAM,KAAK;AAC5B,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAI,CAAC,KAAM,QAAO;AAClB,SAAK,QAAQ,KAAK,MAAM;AACxB,SAAK,MAAM;AACX,SAAK,KAAK;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,KAAK,OAAO,EAAG,OAAM,IAAI,QAAc,CAAC,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,EACvF;AACF;AAEA,SAAS,MAAM,MAAc,MAAsB;AACjD,SAAO,GAAG,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI;AAC3D;AAEA,SAAS,KAAK,QAAqC;AACjD,SAAO,IAAI,QAAQ,CAAC,GAAG,WAAW;AAChC,WAAO,iBAAiB,SAAS,MAAM,OAAO,QAAQ,gBAAgB,cAAc,CAAC,GAAG;AAAA,MACtF,MAAM;AAAA,IACR,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,WAAW,UAA6B,CAAC,GAAe;AACtE,QAAM,QAA6B,CAAC;AACpC,QAAM,UAAiC,CAAC;AACxC,MAAI,WAAW;AACf,SAAO;AAAA,IACL,QAAQ,EAAE,KAAK,MAAM,KAAK,OAAO,MAAM,QAAQ,SAAS,KAAK;AAAA,IAC7D,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACnD;AAAA,IACA;AAAA,IACA,SAAS,OAAO,MAAM,WAAW;AAC/B,YAAM,KAAK,EAAE,MAAM,OAAO,CAAC;AAC3B,aAAO,SAAS,QAAQ,QAAQ,IAAI,KAAK,CAAC,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM,OAAO,EAAE,MAAM,MAAM,OAAO,MAAM;AACtC,cAAQ,KAAK,EAAE,MAAM,KAAK,CAAC;AAC3B,YAAM,OAAO,QAAQ,QAAQ,MAAM,MAAM,IAAI,CAAC;AAC9C,UAAI,CAAC,KAAM,OAAM,QAAQ,gBAAgB,sBAAsB,MAAM,MAAM,IAAI,CAAC,EAAE;AAClF,UAAI,WAAW,KAAM,OAAM,QAAQ,KAAK,OAAO,GAAG,MAAM,MAAM,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE;AACpF,UAAI,UAAU,KAAM,QAAO,KAAK,MAAM;AACtC,YAAM,KAAK,IAAI,OAAO;AACtB,YAAM,OAAO,IAAI,OAAO;AACxB,YAAM,SAAS,QAAQ,cAAoB;AAC3C,WAAK;AAAA,QACH,MAAM,MAAM,GAAG,KAAK;AAAA,QACpB,OAAO,CAAC,UAAU,KAAK,KAAK,KAAK,KAAK,EAAE,MAAM,MAAM,MAAS;AAAA,QAC7D,KAAK,MAAM,KAAK,IAAI;AAAA,QACpB,OAAO,CAAC,OAAO,iBAAiB;AAC9B,gBAAM,QAAQ,QAAQ,MAAM,kCAAkC,IAAI,GAAG;AACrE,aAAG,KAAK,KAAK;AACb,eAAK,KAAK,KAAK;AAAA,QACjB;AAAA,QACA,IAAI,WAAW;AACb,iBAAO,KAAK;AAAA,QACd;AAAA,QACA,SAAS,MAAM,KAAK,QAAQ;AAAA,QAC5B,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,aAAO;AAAA,QACL,WAAW,MAAM,QAAQ,WAAW,mBAAmB,UAAU;AAAA,QACjE,YAAY,MAAM,MAAM,IAAI;AAAA,QAC5B,MAAM,MAAM,KAAK,KAAK;AAAA,QACtB,OAAO,OAAO,UAAU;AACtB,gBAAM,GAAG,KAAK,KAAK;AACnB,iBAAO,MAAM;AAAA,QACf;AAAA,QACA,YAAY,MAAM,GAAG,IAAI;AAAA,QACzB,OAAO,MAAM;AACX,aAAG,IAAI;AACP,eAAK,IAAI;AACT,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-kernel",
3
- "version": "1.27.0",
3
+ "version": "1.28.0",
4
4
  "description": "SLICC's wasm-realm kernel: run Emscripten programs in the browser against OPFS, with fork, spawn, pipes and waitpid",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",