@farm.js/islx 0.0.0-stage → 0.0.2

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 (39) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE +21 -0
  3. package/README.md +198 -2
  4. package/SPEC.md +220 -0
  5. package/dist/_chunks/actions-2sTj0A2T.mjs +306 -0
  6. package/dist/_chunks/actions-B8dJ8f7L.mjs +615 -0
  7. package/dist/_chunks/actions-BCtIGvrP.d.mts +58 -0
  8. package/dist/_chunks/actions-Eutk6UzT.mjs +326 -0
  9. package/dist/_chunks/build-COIcs7wm.mjs +9 -0
  10. package/dist/_chunks/codec-CK8kZp4u.d.mts +64 -0
  11. package/dist/_chunks/codec-DqfVYKsx.mjs +279 -0
  12. package/dist/_chunks/handle-C0N9ZyNV.d.mts +35 -0
  13. package/dist/_chunks/hooks-3VZV8f_d.d.mts +30 -0
  14. package/dist/_chunks/hooks-De0gNpOn.d.mts +64 -0
  15. package/dist/_chunks/prehydration-B1GLTLdk.mjs +36 -0
  16. package/dist/_chunks/schedule-BFoGKRrU.mjs +216 -0
  17. package/dist/_chunks/schedule-Dw14dW0H.d.mts +17 -0
  18. package/dist/_chunks/tree-BDB5VEao.mjs +777 -0
  19. package/dist/_chunks/tree-fID6sz25.d.mts +102 -0
  20. package/dist/_chunks/value-store-Dm-7xJ6W.d.mts +19 -0
  21. package/dist/_chunks/wire-C3Rpbwl6.d.mts +98 -0
  22. package/dist/_chunks/wire-CXOodE06.mjs +121 -0
  23. package/dist/client.d.mts +58 -0
  24. package/dist/client.mjs +197 -0
  25. package/dist/compiler.d.mts +2 -0
  26. package/dist/compiler.mjs +1 -0
  27. package/dist/index.d.mts +3 -0
  28. package/dist/index.mjs +3 -0
  29. package/dist/react/client.d.mts +49 -0
  30. package/dist/react/client.mjs +456 -0
  31. package/dist/react/server.d.mts +51 -0
  32. package/dist/react/server.mjs +84 -0
  33. package/dist/react.d.mts +3 -0
  34. package/dist/react.mjs +1 -0
  35. package/dist/server.d.mts +200 -0
  36. package/dist/server.mjs +378 -0
  37. package/dist/vite.d.mts +57 -0
  38. package/dist/vite.mjs +497 -0
  39. package/package.json +121 -4
@@ -0,0 +1,326 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { walk } from "estree-walker";
4
+ import isReference from "is-reference";
5
+ import MagicString from "magic-string";
6
+ import { analyze } from "periscopic";
7
+ // src/compiler/actions.ts
8
+ /**
9
+ * The server-action compiler: a pure function from a module's source to its source for the
10
+ * server or the browser. Bundler plugins call it; it parses with the parser they pass.
11
+ *
12
+ * - A `"use server"` module: on the server every export registers as an action; in the browser
13
+ * the module becomes references that call those ids, so its code never reaches a bundle.
14
+ * - An inline `"use server"` function in server code: hoisted to module scope, taking the
15
+ * variables it read from the component around it as one array. Where it was written, it
16
+ * becomes a reference that carries those variables, sealed.
17
+ */
18
+ /** A mistake in the source, with where it is. Bundlers show `loc` with a code frame. */
19
+ var ActionCompileError = class extends Error {
20
+ loc;
21
+ id;
22
+ constructor(message, file, code, offset) {
23
+ const before = code.slice(0, offset).split("\n");
24
+ const line = before.length;
25
+ const column = before.at(-1).length;
26
+ super(`${message} (${file}:${line}:${column + 1})`);
27
+ this.name = "ActionCompileError";
28
+ this.id = file;
29
+ this.loc = {
30
+ file,
31
+ line,
32
+ column
33
+ };
34
+ }
35
+ };
36
+ /**
37
+ * Whether the module's directive prologue (the strings before any other statement, after
38
+ * comments) holds `directive`. A linear scan, for a cheap check before parsing.
39
+ */
40
+ function hasModuleDirective(code, directive) {
41
+ let index = 0;
42
+ while (index < code.length) {
43
+ const char = code.charCodeAt(index);
44
+ if (char === 32 || char >= 9 && char <= 13 || char === 65279 || char === 160) index++;
45
+ else if (code.startsWith("//", index)) {
46
+ const end = code.indexOf("\n", index);
47
+ if (end === -1) return false;
48
+ index = end + 1;
49
+ } else if (code.startsWith("/*", index)) {
50
+ const end = code.indexOf("*/", index + 2);
51
+ if (end === -1) return false;
52
+ index = end + 2;
53
+ } else if (char === 34 || char === 39) {
54
+ const quote = code[index];
55
+ const end = code.indexOf(quote, index + 1);
56
+ if (end === -1) return false;
57
+ const value = code.slice(index + 1, end);
58
+ index = end + 1;
59
+ while (index < code.length && /[ \t]/.test(code[index])) index++;
60
+ if (code[index] === ";") index++;
61
+ else if (index < code.length && code[index] !== "\n" && code[index] !== "\r") return false;
62
+ if (value === directive) return true;
63
+ } else return false;
64
+ }
65
+ return false;
66
+ }
67
+ /** The id an action has on both sides: a hash of its module path from the root and its name. */
68
+ function getActionId(root, file, name, salt = "") {
69
+ const relative = path.relative(root, file.split("?", 1)[0]).split(path.sep).join("/");
70
+ return createHash("sha256").update(`${salt}${relative}#${name}`).digest("base64url").slice(0, 22);
71
+ }
72
+ function hasDirective(statements, directive) {
73
+ for (const statement of statements ?? []) {
74
+ if (statement.type !== "ExpressionStatement") return false;
75
+ const expression = statement.expression;
76
+ const value = statement.directive ?? (expression?.type === "Literal" ? expression.value : void 0);
77
+ if (typeof value !== "string") return false;
78
+ if (value === directive) return true;
79
+ }
80
+ return false;
81
+ }
82
+ const FUNCTION_TYPES = /* @__PURE__ */ new Set([
83
+ "FunctionDeclaration",
84
+ "FunctionExpression",
85
+ "ArrowFunctionExpression"
86
+ ]);
87
+ function isInlineAction(node, directive) {
88
+ return FUNCTION_TYPES.has(node.type) && node.body?.type === "BlockStatement" && hasDirective(node.body.body, directive);
89
+ }
90
+ function actionName(node, parent) {
91
+ if (node.id?.name) return node.id.name;
92
+ if (parent?.type === "VariableDeclarator" && parent.id?.type === "Identifier") return parent.id.name;
93
+ if (parent?.type === "Property" && parent.key?.type === "Identifier") return parent.key.name;
94
+ if (parent?.type === "JSXExpressionContainer") return null;
95
+ return null;
96
+ }
97
+ /** Exports of a `"use server"` module, refusing what cannot be an action. */
98
+ function collectExports(program, fail, directive) {
99
+ const exports = [];
100
+ const onlyAsync = `A "${directive}" module may export only async functions`;
101
+ const checkFunction = (node, name) => {
102
+ if (FUNCTION_TYPES.has(node.type)) {
103
+ if (!node.async) fail(`${onlyAsync}: make ${name} async`, node);
104
+ if (node.generator) fail(`${onlyAsync}: ${name} is a generator`, node);
105
+ return;
106
+ }
107
+ if (node.type === "Literal" || node.type === "ObjectExpression" || node.type === "ArrayExpression" || node.type === "TemplateLiteral" || node.type === "ClassExpression") fail(`${onlyAsync}; ${name} is not a function`, node);
108
+ };
109
+ for (const node of program.body ?? []) {
110
+ if (node.type === "ExportAllDeclaration") fail(`"${directive}" modules cannot re-export with \`export *\`: export each action by name`, node);
111
+ if (node.type === "ExportNamedDeclaration") {
112
+ if (node.exportKind === "type") continue;
113
+ if (node.source) fail(`"${directive}" modules cannot re-export from another module: import the actions, then export them`, node);
114
+ const declaration = node.declaration;
115
+ if (declaration?.type === "FunctionDeclaration") {
116
+ checkFunction(declaration, declaration.id.name);
117
+ exports.push({
118
+ exported: declaration.id.name,
119
+ local: declaration.id.name,
120
+ node: declaration
121
+ });
122
+ } else if (declaration?.type === "VariableDeclaration") for (const declarator of declaration.declarations ?? []) {
123
+ if (declarator.id?.type !== "Identifier") fail(`${onlyAsync}; destructured exports are not supported`, declarator);
124
+ if (declarator.init) checkFunction(declarator.init, declarator.id.name);
125
+ exports.push({
126
+ exported: declarator.id.name,
127
+ local: declarator.id.name,
128
+ node: declarator
129
+ });
130
+ }
131
+ else if (declaration?.type === "ClassDeclaration") fail(`${onlyAsync}; ${declaration.id?.name ?? "a class"} is a class`, declaration);
132
+ else if (declaration && !String(declaration.type).startsWith("TS")) fail(onlyAsync, declaration);
133
+ for (const specifier of node.specifiers ?? []) {
134
+ if (specifier.exportKind === "type") continue;
135
+ const exported = specifier.exported?.name ?? specifier.exported?.value;
136
+ exports.push({
137
+ exported,
138
+ local: specifier.local?.name ?? null,
139
+ node: specifier
140
+ });
141
+ }
142
+ } else if (node.type === "ExportDefaultDeclaration") {
143
+ const declaration = node.declaration;
144
+ checkFunction(declaration, "the default export");
145
+ const local = FUNCTION_TYPES.has(declaration.type) && declaration.id?.name || declaration.type === "Identifier" && declaration.name || null;
146
+ exports.push({
147
+ exported: "default",
148
+ local,
149
+ node
150
+ });
151
+ }
152
+ }
153
+ return exports;
154
+ }
155
+ /**
156
+ * Compiles a module's server actions, or returns null when it has none. Throws an
157
+ * `ActionCompileError` for a mistake, such as an inline action in browser code.
158
+ */
159
+ function transformActions(options) {
160
+ const { code, id, root, environment } = options;
161
+ const salt = options.salt ?? "";
162
+ const serverRuntime = options.runtime?.server ?? "@farm.js/islx/server";
163
+ const clientRuntime = options.runtime?.client ?? "@farm.js/islx/client";
164
+ const directive = options.directive ?? "use server";
165
+ const moduleDirective = hasModuleDirective(code, directive);
166
+ if (!moduleDirective && !code.includes(directive)) return null;
167
+ const program = options.parse(code);
168
+ const file = id.split("?", 1)[0];
169
+ const relative = path.relative(root, file).split(path.sep).join("/");
170
+ const fail = (message, node) => {
171
+ throw new ActionCompileError(message, file, code, node.start);
172
+ };
173
+ const actionId = (name) => getActionId(root, file, name, salt);
174
+ const label = (name) => `${name} (${relative})`;
175
+ if (moduleDirective && environment === "client") {
176
+ const exports = collectExports(program, fail, directive);
177
+ const lines = [`import { createServerReference as $$islx_ref } from ${JSON.stringify(clientRuntime)};`];
178
+ for (const entry of exports) {
179
+ const reference = `$$islx_ref(${JSON.stringify(actionId(entry.exported))}, undefined, ${JSON.stringify(entry.exported === "default" ? "default" : entry.exported)})`;
180
+ lines.push(entry.exported === "default" ? `export default ${reference};` : `export const ${entry.exported} = ${reference};`);
181
+ }
182
+ const output = new MagicString(code);
183
+ output.overwrite(0, code.length, `${lines.join("\n")}\n`);
184
+ return {
185
+ code: output.toString(),
186
+ map: output.generateMap({
187
+ hires: true,
188
+ source: file
189
+ })
190
+ };
191
+ }
192
+ const { map, scope: moduleScope } = analyze(program);
193
+ const scopes = [moduleScope];
194
+ const scopeNodes = [program];
195
+ const inline = [];
196
+ const active = [];
197
+ let functionDepth = 0;
198
+ const ownFunctionDepth = [];
199
+ walk(program, {
200
+ enter(rawNode, rawParent) {
201
+ const node = rawNode;
202
+ const parent = rawParent;
203
+ if (typeof node.type === "string" && node.type.startsWith("TS")) {
204
+ this.skip();
205
+ return;
206
+ }
207
+ const scope = node === program ? void 0 : map.get(node);
208
+ if (scope) {
209
+ scopes.push(scope);
210
+ scopeNodes.push(node);
211
+ }
212
+ if (FUNCTION_TYPES.has(node.type)) {
213
+ if (isInlineAction(node, directive) && !(moduleDirective && functionDepth === 0)) {
214
+ if (environment === "client") fail(`An inline "${directive}" function can only be written in server code. Move it to a "${directive}" module and import it here`, node);
215
+ if (!node.async) fail("A server action must be an async function", node);
216
+ if (node.generator) fail("A server action cannot be a generator", node);
217
+ const action = {
218
+ node,
219
+ parent,
220
+ nested: functionDepth > 0,
221
+ name: actionName(node, parent),
222
+ captured: [],
223
+ scopeIndex: scopes.length - 1
224
+ };
225
+ inline.push(action);
226
+ active.push(action);
227
+ ownFunctionDepth.push(0);
228
+ } else if (node.type !== "ArrowFunctionExpression") for (let index = 0; index < ownFunctionDepth.length; index++) ownFunctionDepth[index]++;
229
+ functionDepth++;
230
+ return;
231
+ }
232
+ if (active.length === 0) return;
233
+ if (node.type === "ThisExpression" || node.type === "Identifier" && node.name === "arguments" && parent && isReference(node, parent)) {
234
+ for (let index = 0; index < active.length; index++) if (ownFunctionDepth[index] === 0) fail(`A server action cannot use \`${node.type === "ThisExpression" ? "this" : "arguments"}\`: pass what it needs as an argument`, node);
235
+ return;
236
+ }
237
+ if (node.type !== "Identifier" || !parent || !isReference(node, parent)) return;
238
+ if (parent.id === node && FUNCTION_TYPES.has(parent.type)) return;
239
+ const owner = scopes.at(-1).find_owner(node.name);
240
+ if (!owner || owner === moduleScope) return;
241
+ const ownerIndex = scopes.lastIndexOf(owner);
242
+ for (const action of active) if (ownerIndex > 0 && ownerIndex < action.scopeIndex && !action.captured.includes(node.name)) {
243
+ if (action.node.type === "FunctionDeclaration" && action.name === node.name) fail(`The server action ${node.name} refers to itself. To call it again, move it to a "${directive}" module`, node);
244
+ action.captured.push(node.name);
245
+ }
246
+ },
247
+ leave(rawNode) {
248
+ const node = rawNode;
249
+ if (scopeNodes.at(-1) === node && node !== program) {
250
+ scopes.pop();
251
+ scopeNodes.pop();
252
+ }
253
+ if (FUNCTION_TYPES.has(node.type)) {
254
+ functionDepth--;
255
+ if (active.at(-1)?.node === node) {
256
+ active.pop();
257
+ ownFunctionDepth.pop();
258
+ } else if (node.type !== "ArrowFunctionExpression") for (let index = 0; index < ownFunctionDepth.length; index++) ownFunctionDepth[index]--;
259
+ }
260
+ }
261
+ });
262
+ if (inline.length === 0 && !moduleDirective) return null;
263
+ const output = new MagicString(code);
264
+ const footer = [];
265
+ let usesBind = false;
266
+ let usesInline = false;
267
+ let usesReference = false;
268
+ const numbered = [...inline].sort((a, b) => a.node.start - b.node.start);
269
+ const ids = new Map(numbered.map((action, index) => [action, index]));
270
+ for (const action of [...inline].sort((a, b) => b.node.start - a.node.start)) {
271
+ const index = ids.get(action);
272
+ const name = action.name ?? `action ${index + 1}`;
273
+ const idValue = actionId(`inline:${index}`);
274
+ const { node } = action;
275
+ if (!action.nested) {
276
+ usesReference = true;
277
+ if (node.type === "FunctionDeclaration") footer.push(`$$islx_ref(${node.id.name}, ${JSON.stringify(idValue)}, ${JSON.stringify(label(name))});`);
278
+ else {
279
+ output.prependLeft(node.start, "$$islx_ref(");
280
+ output.appendRight(node.end, `, ${JSON.stringify(idValue)}, ${JSON.stringify(label(name))})`);
281
+ }
282
+ continue;
283
+ }
284
+ usesBind = true;
285
+ usesInline = true;
286
+ const hoisted = `$$islx_action_${index}`;
287
+ const params = node.params.map((param) => output.slice(param.start, param.end));
288
+ const captured = action.captured.length > 0 ? `[${action.captured.join(", ")}]` : "$$islx_captured";
289
+ const body = output.slice(node.body.start + 1, node.body.end - 1);
290
+ footer.push(`async function ${hoisted}(${[captured, ...params].join(", ")}) {${body}}`, `$$islx_inline(${hoisted}, ${JSON.stringify(idValue)}, ${action.captured.length}, ${JSON.stringify(label(name))});`);
291
+ const reference = `$$islx_bind(${JSON.stringify(idValue)}, () => [${action.captured.join(", ")}], ${JSON.stringify(action.captured)})`;
292
+ if (node.type === "FunctionDeclaration") {
293
+ const block = action.parent;
294
+ if (!block || block.type !== "BlockStatement" && block.type !== "StaticBlock") fail("Declare this server action directly inside a function body", node);
295
+ output.remove(node.start, node.end);
296
+ output.appendRight(block.start + 1, `const ${node.id.name} = ${reference};`);
297
+ } else output.overwrite(node.start, node.end, reference);
298
+ }
299
+ if (moduleDirective) {
300
+ usesReference = true;
301
+ for (const entry of collectExports(program, fail, directive)) {
302
+ if (!entry.local) {
303
+ const declaration = entry.node.declaration;
304
+ output.overwrite(entry.node.start, declaration.start, "const $$islx_default = ");
305
+ footer.push("export { $$islx_default as default };");
306
+ entry.local = "$$islx_default";
307
+ }
308
+ footer.push(`$$islx_ref(${entry.local}, ${JSON.stringify(actionId(entry.exported))}, ${JSON.stringify(label(entry.exported))});`);
309
+ }
310
+ }
311
+ const imports = [
312
+ usesBind && "bindInlineAction as $$islx_bind",
313
+ usesInline && "registerInlineAction as $$islx_inline",
314
+ usesReference && "registerServerReference as $$islx_ref"
315
+ ].filter(Boolean);
316
+ output.prepend(`import { ${imports.join(", ")} } from ${JSON.stringify(serverRuntime)};\n`);
317
+ output.append(`\n${footer.join("\n")}\n`);
318
+ return {
319
+ code: output.toString(),
320
+ map: output.generateMap({
321
+ hires: true,
322
+ source: file
323
+ })
324
+ };
325
+ }
326
+ export { transformActions as i, getActionId as n, hasModuleDirective as r, ActionCompileError as t };
@@ -0,0 +1,9 @@
1
+ // src/server/build.ts
2
+ const BUILD_KEY = Symbol.for("islx.build");
3
+ function setBuildDefaults(defaults) {
4
+ globalThis[BUILD_KEY] = defaults;
5
+ }
6
+ function getBuildDefaults() {
7
+ return globalThis[BUILD_KEY] ?? {};
8
+ }
9
+ export { setBuildDefaults as n, getBuildDefaults as t };
@@ -0,0 +1,64 @@
1
+ /** Standard base64 of bytes. */
2
+ declare function toBase64(bytes: Uint8Array): string;
3
+ /** Bytes of standard base64. Throws on anything else. */
4
+ declare function fromBase64(base64: string): Uint8Array;
5
+ /** A tagged object. */
6
+ declare function tagged(tag: string, value?: unknown): Record<string, unknown>;
7
+ /** Whether a plain object must travel wrapped as `o`, because its own keys include `$`. */
8
+ declare function needsObjectTag(object: object): boolean;
9
+ /**
10
+ * The tag for a value JSON cannot hold and that has no children (everything above but `M`, `S`
11
+ * and `o`), or undefined when the value needs none.
12
+ */
13
+ declare function encodeLeaf(value: unknown): Record<string, unknown> | undefined;
14
+ /** Returned by `decodeTagged` for the runtime's reference tags (`r`, `s`, `a`). */
15
+ declare const NOT_A_VALUE: unique symbol;
16
+ /** A plain object from entries; `defineProperty`, so a "__proto__" key stays an ordinary key. */
17
+ declare function plainObject(entries: Iterable<[string, unknown]>): Record<string, unknown>;
18
+ /**
19
+ * The value a tagged object stands for, decoding children (a `Map`'s or `Set`'s entries, an `o`
20
+ * object's properties) with `decodeChild`. Returns `NOT_A_VALUE` for the runtime's reference tags,
21
+ * and throws for an unknown or malformed tag.
22
+ */
23
+ declare function decodeTagged(object: Record<string, unknown>, decodeChild: (value: unknown) => unknown): unknown;
24
+ /** Thrown by `encode` for a value it cannot carry; names where the value sits. */
25
+ declare class EncodeError extends TypeError {
26
+ /** Where the value sits, from the root: `["user", "roles", 2]`. */
27
+ readonly path: readonly (string | number)[];
28
+ /** What is wrong with it: "functions are not serializable". */
29
+ readonly reason: string;
30
+ constructor(reason: string, path: readonly (string | number)[]);
31
+ /** The path as written in code: `user.roles[2]`. */
32
+ get location(): string;
33
+ }
34
+ interface EncodeOptions {
35
+ /**
36
+ * Encodes a function, such as a server action, or returns undefined to refuse it. Without it,
37
+ * functions throw.
38
+ */
39
+ encodeFunction?: (value: Function) => unknown;
40
+ /**
41
+ * Encodes an object the codec cannot carry itself (`FormData`, a `Blob`), or returns
42
+ * undefined to refuse it.
43
+ */
44
+ encodeObject?: (value: object) => unknown;
45
+ /** Names the root of the value in error messages. */
46
+ path?: readonly (string | number)[];
47
+ }
48
+ /**
49
+ * Encodes a value for JSON with the tags above: plain objects, arrays and the types listed.
50
+ * Symbols, class instances, circular values and (without `encodeFunction`) functions throw an
51
+ * `EncodeError` that names where the value sits.
52
+ */
53
+ declare function encode(value: unknown, options?: EncodeOptions): unknown;
54
+ interface DecodeOptions {
55
+ /**
56
+ * Decodes one of the runtime's reference tags (`r`, `s`, `a`), or returns undefined to refuse
57
+ * it. Without it, every reference throws: input from a browser must not name the runtime's own
58
+ * references unless the caller checks them.
59
+ */
60
+ decodeReference?: (object: Record<string, unknown>) => unknown;
61
+ }
62
+ /** Decodes what `encode` produced, after `JSON.parse`. Reference tags are rejected by default. */
63
+ declare function decode(value: unknown, options?: DecodeOptions): unknown;
64
+ export { decode as a, encodeLeaf as c, plainObject as d, tagged as f, NOT_A_VALUE as i, fromBase64 as l, EncodeError as n, decodeTagged as o, toBase64 as p, EncodeOptions as r, encode as s, DecodeOptions as t, needsObjectTag as u };
@@ -0,0 +1,279 @@
1
+ import { N as getTag } from "./wire-CXOodE06.mjs";
2
+ // src/protocol/codec.ts
3
+ /**
4
+ * Values JSON cannot hold. Each travels as a tagged object: the reserved key `$` holds a short tag
5
+ * and `v` holds the data.
6
+ *
7
+ * undefined {"$":"u"}
8
+ * NaN, ±Infinity, -0 {"$":"n","v":"NaN"}
9
+ * 12345678901234567890n {"$":"B","v":"12345678901234567890"}
10
+ * new Date(…) {"$":"D","v":"2026-10-28T09:00:00.000Z"} (null for an invalid date)
11
+ * new Map([[k, v]]) {"$":"M","v":[[k, v]]} (keys and values tagged)
12
+ * new Set([v]) {"$":"S","v":[v]}
13
+ * new URL(…) {"$":"U","v":"https://example.com/"}
14
+ * /ab+c/gi {"$":"R","v":["ab+c","gi"]}
15
+ * Uint8Array, ArrayBuffer… {"$":"Y","v":["Uint8Array","base64…"]}
16
+ * app data with a "$" key {"$":"o","v":{…}} (never read as a tag)
17
+ *
18
+ * Decoding accepts input from the browser (server action arguments): it builds plain objects
19
+ * without assigning `__proto__`, and rejects unknown or malformed tags.
20
+ */
21
+ const TYPED_ARRAYS = {
22
+ Int8Array,
23
+ Uint8Array,
24
+ Uint8ClampedArray,
25
+ Int16Array,
26
+ Uint16Array,
27
+ Int32Array,
28
+ Uint32Array,
29
+ Float32Array,
30
+ Float64Array,
31
+ BigInt64Array,
32
+ BigUint64Array
33
+ };
34
+ /** Standard base64 of bytes. */
35
+ function toBase64(bytes) {
36
+ let binary = "";
37
+ for (let index = 0; index < bytes.length; index += 32768) binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
38
+ return btoa(binary);
39
+ }
40
+ /** Bytes of standard base64. Throws on anything else. */
41
+ function fromBase64(base64) {
42
+ const binary = atob(base64);
43
+ const bytes = new Uint8Array(binary.length);
44
+ for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
45
+ return bytes;
46
+ }
47
+ /** A tagged object. */
48
+ function tagged(tag, value) {
49
+ return value === void 0 ? { ["$"]: tag } : {
50
+ ["$"]: tag,
51
+ ["v"]: value
52
+ };
53
+ }
54
+ /** Whether a plain object must travel wrapped as `o`, because its own keys include `$`. */
55
+ function needsObjectTag(object) {
56
+ return Object.prototype.hasOwnProperty.call(object, "$");
57
+ }
58
+ /**
59
+ * The tag for a value JSON cannot hold and that has no children (everything above but `M`, `S`
60
+ * and `o`), or undefined when the value needs none.
61
+ */
62
+ function encodeLeaf(value) {
63
+ switch (typeof value) {
64
+ case "undefined": return tagged("u");
65
+ case "bigint": return tagged("B", value.toString());
66
+ case "number":
67
+ if (Number.isNaN(value)) return tagged("n", "NaN");
68
+ if (value === Infinity) return tagged("n", "Infinity");
69
+ if (value === -Infinity) return tagged("n", "-Infinity");
70
+ if (Object.is(value, -0)) return tagged("n", "-0");
71
+ return;
72
+ case "object": break;
73
+ default: return;
74
+ }
75
+ if (value === null) return void 0;
76
+ if (value instanceof Date) return tagged("D", Number.isNaN(value.getTime()) ? null : value.toISOString());
77
+ if (typeof URL === "function" && value instanceof URL) return tagged("U", value.href);
78
+ if (value instanceof RegExp) return tagged("R", [value.source, value.flags]);
79
+ if (value instanceof ArrayBuffer) return tagged("Y", ["ArrayBuffer", toBase64(new Uint8Array(value))]);
80
+ if (ArrayBuffer.isView(value)) {
81
+ const name = value instanceof DataView ? "DataView" : value.constructor.name;
82
+ if (name !== "DataView" && !(name in TYPED_ARRAYS)) return void 0;
83
+ return tagged("Y", [name, toBase64(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))]);
84
+ }
85
+ }
86
+ /** Returned by `decodeTagged` for the runtime's reference tags (`r`, `s`, `a`). */
87
+ const NOT_A_VALUE = Symbol.for("islx.codec.not-a-value");
88
+ /** A plain object from entries; `defineProperty`, so a "__proto__" key stays an ordinary key. */
89
+ function plainObject(entries) {
90
+ const object = {};
91
+ for (const [key, value] of entries) setOwn(object, key, value);
92
+ return object;
93
+ }
94
+ /**
95
+ * Sets an own property. Plain assignment keeps the object fast; only a `"__proto__"` key needs
96
+ * `defineProperty`, which assignment would turn into a prototype change.
97
+ */
98
+ function setOwn(object, key, value) {
99
+ if (key === "__proto__") Object.defineProperty(object, key, {
100
+ value,
101
+ enumerable: true,
102
+ writable: true,
103
+ configurable: true
104
+ });
105
+ else object[key] = value;
106
+ }
107
+ /**
108
+ * The value a tagged object stands for, decoding children (a `Map`'s or `Set`'s entries, an `o`
109
+ * object's properties) with `decodeChild`. Returns `NOT_A_VALUE` for the runtime's reference tags,
110
+ * and throws for an unknown or malformed tag.
111
+ */
112
+ function decodeTagged(object, decodeChild) {
113
+ const tag = object["$"];
114
+ const data = object["v"];
115
+ switch (tag) {
116
+ case "r":
117
+ case "s":
118
+ case "a":
119
+ case "F":
120
+ case "K": return NOT_A_VALUE;
121
+ case "o":
122
+ if (data && typeof data === "object" && !Array.isArray(data)) return plainObject(Object.entries(data).map(([key, value]) => [key, decodeChild(value)]));
123
+ break;
124
+ case "u": return;
125
+ case "n":
126
+ if (data === "NaN") return NaN;
127
+ if (data === "Infinity") return Infinity;
128
+ if (data === "-Infinity") return -Infinity;
129
+ if (data === "-0") return -0;
130
+ break;
131
+ case "B":
132
+ if (typeof data === "string" && /^-?\d+$/.test(data)) return BigInt(data);
133
+ break;
134
+ case "D":
135
+ if (data === null) return /* @__PURE__ */ new Date(NaN);
136
+ if (typeof data === "string") return new Date(data);
137
+ break;
138
+ case "U":
139
+ if (typeof data === "string") return new URL(data);
140
+ break;
141
+ case "R":
142
+ if (Array.isArray(data) && typeof data[0] === "string" && typeof data[1] === "string") return new RegExp(data[0], data[1]);
143
+ break;
144
+ case "Y": {
145
+ if (!Array.isArray(data) || typeof data[0] !== "string" || typeof data[1] !== "string") break;
146
+ const bytes = fromBase64(data[1]);
147
+ if (data[0] === "ArrayBuffer") return bytes.buffer;
148
+ if (data[0] === "DataView") return new DataView(bytes.buffer);
149
+ const TypedArray = Object.prototype.hasOwnProperty.call(TYPED_ARRAYS, data[0]) ? TYPED_ARRAYS[data[0]] : void 0;
150
+ if (!TypedArray || bytes.byteLength % TypedArray.BYTES_PER_ELEMENT !== 0) break;
151
+ return new TypedArray(bytes.buffer, 0, bytes.byteLength / TypedArray.BYTES_PER_ELEMENT);
152
+ }
153
+ case "M":
154
+ if (Array.isArray(data) && data.every((entry) => Array.isArray(entry) && entry.length === 2)) return new Map(data.map(([key, value]) => [decodeChild(key), decodeChild(value)]));
155
+ break;
156
+ case "S":
157
+ if (Array.isArray(data)) return new Set(data.map(decodeChild));
158
+ break;
159
+ default: throw new TypeError(`unknown ${JSON.stringify(tag)} tag`);
160
+ }
161
+ throw new TypeError(`malformed ${JSON.stringify(tag)} tag`);
162
+ }
163
+ /** Describes where in a value encoding failed, for error messages: `user.roles[2]`. */
164
+ function describePath(path) {
165
+ return path.map((part, index) => typeof part === "number" ? `[${part}]` : /^[A-Za-z_$][\w$]*$/.test(part) ? `${index === 0 ? "" : "."}${part}` : `[${JSON.stringify(part)}]`).join("");
166
+ }
167
+ /** Thrown by `encode` for a value it cannot carry; names where the value sits. */
168
+ var EncodeError = class extends TypeError {
169
+ /** Where the value sits, from the root: `["user", "roles", 2]`. */
170
+ path;
171
+ /** What is wrong with it: "functions are not serializable". */
172
+ reason;
173
+ constructor(reason, path) {
174
+ super(path.length > 0 ? `${describePath(path)}: ${reason}` : reason);
175
+ this.name = "EncodeError";
176
+ this.path = [...path];
177
+ this.reason = reason;
178
+ }
179
+ /** The path as written in code: `user.roles[2]`. */
180
+ get location() {
181
+ return describePath(this.path);
182
+ }
183
+ };
184
+ /**
185
+ * Encodes a value for JSON with the tags above: plain objects, arrays and the types listed.
186
+ * Symbols, class instances, circular values and (without `encodeFunction`) functions throw an
187
+ * `EncodeError` that names where the value sits.
188
+ */
189
+ function encode(value, options = {}) {
190
+ const seen = /* @__PURE__ */ new Set();
191
+ const path = [...options.path ?? []];
192
+ const fail = (reason) => {
193
+ throw new EncodeError(reason, path);
194
+ };
195
+ const visit = (value) => {
196
+ const leaf = encodeLeaf(value);
197
+ if (leaf) return leaf;
198
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
199
+ if (typeof value === "number") return value;
200
+ if (typeof value === "function") {
201
+ const encoded = options.encodeFunction?.(value);
202
+ return encoded === void 0 ? fail("functions are not serializable") : encoded;
203
+ }
204
+ if (typeof value !== "object") return fail(`${typeof value} values are not serializable`);
205
+ if (seen.has(value)) return fail("circular values are not serializable");
206
+ seen.add(value);
207
+ try {
208
+ if (Array.isArray(value)) return value.map((entry, index) => {
209
+ path.push(index);
210
+ try {
211
+ return visit(entry);
212
+ } finally {
213
+ path.pop();
214
+ }
215
+ });
216
+ if (value instanceof Map) {
217
+ let index = 0;
218
+ return tagged("M", Array.from(value, ([key, entry]) => {
219
+ path.push(index++);
220
+ try {
221
+ return [visit(key), visit(entry)];
222
+ } finally {
223
+ path.pop();
224
+ }
225
+ }));
226
+ }
227
+ if (value instanceof Set) {
228
+ let index = 0;
229
+ return tagged("S", Array.from(value, (entry) => {
230
+ path.push(index++);
231
+ try {
232
+ return visit(entry);
233
+ } finally {
234
+ path.pop();
235
+ }
236
+ }));
237
+ }
238
+ if (typeof value.$$typeof === "symbol") return fail("elements (JSX) are not serializable");
239
+ const prototype = Object.getPrototypeOf(value);
240
+ if (prototype !== Object.prototype && prototype !== null) {
241
+ const special = options.encodeObject?.(value);
242
+ if (special !== void 0) return special;
243
+ const name = value.constructor?.name;
244
+ return fail(`class instances${name ? ` (${name})` : ""} are not serializable`);
245
+ }
246
+ const encoded = plainObject(Object.entries(value).map(([key, entry]) => {
247
+ path.push(key);
248
+ try {
249
+ return [key, visit(entry)];
250
+ } finally {
251
+ path.pop();
252
+ }
253
+ }));
254
+ return needsObjectTag(value) ? tagged("o", encoded) : encoded;
255
+ } finally {
256
+ seen.delete(value);
257
+ }
258
+ };
259
+ return visit(value);
260
+ }
261
+ /** Decodes what `encode` produced, after `JSON.parse`. Reference tags are rejected by default. */
262
+ function decode(value, options = {}) {
263
+ const visit = (value) => {
264
+ if (Array.isArray(value)) return value.map(visit);
265
+ if (!value || typeof value !== "object") return value;
266
+ if (getTag(value) !== void 0) {
267
+ const object = value;
268
+ const decoded = decodeTagged(object, visit);
269
+ if (decoded !== NOT_A_VALUE) return decoded;
270
+ const reference = options.decodeReference?.(object);
271
+ if (reference !== void 0) return reference;
272
+ throw new TypeError(`a ${JSON.stringify(object["$"])} reference is not allowed here`);
273
+ }
274
+ if (needsObjectTag(value)) throw new TypeError("malformed \"$\" tag");
275
+ return plainObject(Object.entries(value).map(([key, entry]) => [key, visit(entry)]));
276
+ };
277
+ return visit(value);
278
+ }
279
+ export { encode as a, needsObjectTag as c, tagged as d, toBase64 as f, decodeTagged as i, plainObject as l, NOT_A_VALUE as n, encodeLeaf as o, decode as r, fromBase64 as s, EncodeError as t, setOwn as u };