@farm.js/islx 0.0.0-stage → 0.0.1

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 +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +198 -2
  4. package/SPEC.md +220 -0
  5. package/dist/_chunks/actions-BCtIGvrP.d.mts +58 -0
  6. package/dist/_chunks/actions-C4dZ1-XD.mjs +615 -0
  7. package/dist/_chunks/actions-CA_K-In7.mjs +310 -0
  8. package/dist/_chunks/actions-Eutk6UzT.mjs +326 -0
  9. package/dist/_chunks/build-COIcs7wm.mjs +9 -0
  10. package/dist/_chunks/codec-BTDkzzPP.mjs +271 -0
  11. package/dist/_chunks/codec-CK8kZp4u.d.mts +64 -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-BEV73f_8.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 +403 -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,310 @@
1
+ import { A as VALUE_SCRIPT_TYPE, N as getTag, O as VALUES_HEADER, j as attachmentField, k as VALUE_KEY_PATTERN, o as BOUND_FIELD, s as CALL_FIELD, t as ACTION_HEADER } from "./wire-CXOodE06.mjs";
2
+ import { a as encode, r as decode } from "./codec-BTDkzzPP.mjs";
3
+ // src/client/value-store.ts
4
+ /**
5
+ * The browser's store of values sent once. It keeps every value it has read, parses each once,
6
+ * and gives islands that share a value the same object. A navigation names the keys it holds
7
+ * (`valueHeaders()`), so the server leaves those values out.
8
+ */
9
+ /** Past this many values the store starts over at the next navigation. */
10
+ const MAX_STORED_VALUES = 1024;
11
+ const STORE_KEY = Symbol.for("islx.values");
12
+ function createValueStore() {
13
+ const entries = /* @__PURE__ */ new Map();
14
+ const read = /* @__PURE__ */ new WeakSet();
15
+ const get = (key) => {
16
+ const entry = entries.get(key);
17
+ if (!entry) throw new Error(`islx value ${key} was not found`);
18
+ entries.delete(key);
19
+ entries.set(key, entry);
20
+ if (entry.state === "resolved") return entry.value;
21
+ if (entry.state === "resolving") throw new Error(`islx value ${key} refers to itself`);
22
+ entry.state = "resolving";
23
+ try {
24
+ entry.value = resolve(JSON.parse(entry.json));
25
+ } catch (error) {
26
+ entry.state = "raw";
27
+ throw error;
28
+ }
29
+ entry.state = "resolved";
30
+ entry.json = "";
31
+ return entry.value;
32
+ };
33
+ const resolve = (value) => {
34
+ if (Array.isArray(value)) return value.map(resolve);
35
+ if (!value || typeof value !== "object") return value;
36
+ const object = value;
37
+ const tag = getTag(object);
38
+ if (tag === "r" && typeof object["v"] === "string") return get(object["v"]);
39
+ if (tag === "o" && object["v"] && typeof object["v"] === "object") return {
40
+ ["$"]: "o",
41
+ ["v"]: resolveEntries(object["v"])
42
+ };
43
+ return resolveEntries(object);
44
+ };
45
+ const resolveEntries = (object) => {
46
+ const resolved = {};
47
+ for (const key of Object.keys(object)) Object.defineProperty(resolved, key, {
48
+ value: resolve(object[key]),
49
+ enumerable: true,
50
+ writable: true,
51
+ configurable: true
52
+ });
53
+ return resolved;
54
+ };
55
+ return {
56
+ ingest(root) {
57
+ for (const script of root.querySelectorAll(`script[type="${VALUE_SCRIPT_TYPE}"]`)) {
58
+ if (read.has(script)) continue;
59
+ read.add(script);
60
+ const key = script.getAttribute("name") ?? "";
61
+ if (VALUE_KEY_PATTERN.test(key) && !entries.has(key)) entries.set(key, {
62
+ json: script.textContent || "null",
63
+ state: "raw"
64
+ });
65
+ }
66
+ },
67
+ resolve,
68
+ heldHeader() {
69
+ if (entries.size > MAX_STORED_VALUES) entries.clear();
70
+ if (entries.size === 0) return void 0;
71
+ return [...entries.keys()].slice(-128).join(",");
72
+ },
73
+ clear() {
74
+ entries.clear();
75
+ }
76
+ };
77
+ }
78
+ /** The page's value store, shared by every copy of islx on the page. */
79
+ function getValueStore() {
80
+ const scope = globalThis;
81
+ return scope[STORE_KEY] ??= createValueStore();
82
+ }
83
+ /** Headers a navigation sends so the server can leave out values the browser holds. */
84
+ function valueHeaders() {
85
+ const held = globalThis[STORE_KEY]?.heldHeader();
86
+ return held ? { [VALUES_HEADER]: held } : {};
87
+ }
88
+ // src/client/hooks.ts
89
+ const HOOKS_KEY = Symbol.for("islx.client.hooks");
90
+ const CANCELABLE = /* @__PURE__ */ new Set(["navigate"]);
91
+ /** Types an app's hooks module. */
92
+ function defineClientHooks(hooks) {
93
+ return hooks;
94
+ }
95
+ /** Installs the app's hooks; the plugin's browser entry calls it. */
96
+ function setClientHooks(hooks) {
97
+ globalThis[HOOKS_KEY] = hooks;
98
+ }
99
+ /** The DOM event name of a hook: `navigateError` → `f:navigate-error`. */
100
+ function hookEventName(name) {
101
+ return `f:${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
102
+ }
103
+ /**
104
+ * Runs a hook and dispatches its DOM event. Returns false when it was cancelled. A hook that
105
+ * throws is reported, never allowed to break navigation or hydration.
106
+ */
107
+ function emit(name, detail) {
108
+ const cancelable = CANCELABLE.has(name);
109
+ let prevented = false;
110
+ const event = {
111
+ ...detail,
112
+ preventDefault() {
113
+ if (cancelable) prevented = true;
114
+ },
115
+ get defaultPrevented() {
116
+ return prevented;
117
+ }
118
+ };
119
+ const hook = globalThis[HOOKS_KEY]?.[name];
120
+ if (hook) try {
121
+ hook(event);
122
+ } catch (error) {
123
+ console.error(`[islx] The ${name} hook failed:`, error);
124
+ }
125
+ if (typeof document !== "undefined" && typeof CustomEvent === "function") {
126
+ const dom = new CustomEvent(hookEventName(name), {
127
+ detail,
128
+ cancelable
129
+ });
130
+ if (!document.dispatchEvent(dom)) prevented = cancelable;
131
+ }
132
+ return !prevented;
133
+ }
134
+ // src/client/actions.ts
135
+ /**
136
+ * The browser side of server actions. A client module's import of a `"use server"` module
137
+ * becomes references made here, and an action passed as a prop arrives as one.
138
+ *
139
+ * Calls run one at a time, in order, as React expects of actions. After each, the page refreshes
140
+ * (through the framework's router) so it shows what the action changed; a redirect from the
141
+ * action navigates instead. An action this server no longer knows, as after a deploy, reloads the
142
+ * page.
143
+ */
144
+ const STATE_KEY = Symbol.for("islx.actions.client");
145
+ function state() {
146
+ const scope = globalThis;
147
+ return scope[STATE_KEY] ??= {
148
+ navigator: {},
149
+ queue: Promise.resolve(),
150
+ references: /* @__PURE__ */ new Map(),
151
+ info: /* @__PURE__ */ new WeakMap()
152
+ };
153
+ }
154
+ /** Connects actions to the framework's router. */
155
+ function setActionNavigator(navigator) {
156
+ state().navigator = navigator;
157
+ }
158
+ /** Encodes an action passed back to the server as an argument. */
159
+ function encodeFunction(value) {
160
+ const reference = state().info.get(value);
161
+ if (!reference) return void 0;
162
+ if (reference.bound.length > 0) throw new TypeError("an action bound in the browser cannot be sent to the server");
163
+ return reference.token === void 0 ? {
164
+ ["$"]: "a",
165
+ ["v"]: reference.id
166
+ } : {
167
+ ["$"]: "a",
168
+ ["v"]: reference.id,
169
+ ["b"]: reference.token
170
+ };
171
+ }
172
+ /** Decodes an action in a result or props: `{"$":"a","v":id,"b"?:sealed}`. */
173
+ function decodeActionReference(object) {
174
+ if (object["$"] !== "a" || typeof object["v"] !== "string") return void 0;
175
+ const token = object["b"];
176
+ if (token !== void 0 && typeof token !== "string") return void 0;
177
+ return createServerReference(object["v"], token);
178
+ }
179
+ /** Returned by a call whose action this deploy no longer knows: the page reloads. */
180
+ const STALE = Symbol("islx.action.stale");
181
+ async function call(reference, args) {
182
+ const all = [...reference.bound, ...args];
183
+ const single = all.length === 1 && all[0] instanceof FormData ? all[0] : null;
184
+ let body;
185
+ const headers = { [ACTION_HEADER]: reference.id };
186
+ if (single) {
187
+ const form = new FormData();
188
+ for (const [key, value] of single) form.append(key, value);
189
+ if (reference.token !== void 0) form.append(BOUND_FIELD, reference.token);
190
+ body = form;
191
+ } else {
192
+ const attachments = [];
193
+ const encodeObject = (value) => {
194
+ if (value instanceof FormData || value instanceof Blob) {
195
+ attachments.push(value);
196
+ return {
197
+ ["$"]: value instanceof FormData ? "F" : "K",
198
+ ["v"]: attachments.length - 1
199
+ };
200
+ }
201
+ };
202
+ const json = JSON.stringify({
203
+ a: encode(all, {
204
+ encodeFunction,
205
+ encodeObject
206
+ }),
207
+ b: reference.token
208
+ });
209
+ if (attachments.length === 0) {
210
+ headers["content-type"] = "text/plain;charset=UTF-8";
211
+ body = json;
212
+ } else {
213
+ const form = new FormData();
214
+ form.append(CALL_FIELD, json);
215
+ attachments.forEach((attachment, index) => {
216
+ if (attachment instanceof FormData) for (const [key, value] of attachment) form.append(attachmentField(index, key), value);
217
+ else form.append(attachmentField(index), attachment);
218
+ });
219
+ body = form;
220
+ }
221
+ }
222
+ const response = await fetch(location.href, {
223
+ method: "POST",
224
+ headers,
225
+ body,
226
+ credentials: "same-origin"
227
+ });
228
+ if (response.headers.get("F-Action-Stale")) {
229
+ location.reload();
230
+ return STALE;
231
+ }
232
+ if (!response.ok) throw new Error(`Server action failed: ${response.status}`);
233
+ const payload = await response.json();
234
+ const navigator = state().navigator;
235
+ if (typeof payload.redirect === "string") {
236
+ reference.redirect = payload.redirect;
237
+ if (navigator.navigate) await navigator.navigate(payload.redirect);
238
+ else location.assign(payload.redirect);
239
+ return;
240
+ }
241
+ const result = decode(payload.result, { decodeReference: decodeActionReference });
242
+ if (navigator.refresh) await navigator.refresh();
243
+ return result;
244
+ }
245
+ function createReference(reference) {
246
+ const client = state();
247
+ const fn = (...args) => {
248
+ const { id, name } = reference;
249
+ let startedAt = 0;
250
+ const run = async () => {
251
+ emit("action", {
252
+ id,
253
+ name
254
+ });
255
+ startedAt = performance.now();
256
+ reference.redirect = void 0;
257
+ return call(reference, args);
258
+ };
259
+ const result = client.queue.then(run).then((value) => {
260
+ if (value !== STALE) {
261
+ const { redirect } = reference;
262
+ emit("actionEnd", {
263
+ id,
264
+ name,
265
+ ok: true,
266
+ duration: performance.now() - startedAt,
267
+ redirect
268
+ });
269
+ }
270
+ return value;
271
+ }, (error) => {
272
+ emit("actionEnd", {
273
+ id,
274
+ name,
275
+ ok: false,
276
+ duration: performance.now() - startedAt,
277
+ error
278
+ });
279
+ throw error;
280
+ });
281
+ client.queue = result.then(() => {}, () => {});
282
+ return result.then((value) => value === STALE ? new Promise(() => {}) : value);
283
+ };
284
+ client.info.set(fn, reference);
285
+ Object.defineProperty(fn, "bind", {
286
+ configurable: true,
287
+ value: (_thisArg, ...args) => createReference({
288
+ ...reference,
289
+ bound: [...reference.bound, ...args]
290
+ })
291
+ });
292
+ return fn;
293
+ }
294
+ /** The function that calls a server action; one per id and sealed values, so props keep identity. */
295
+ function createServerReference(id, token, name) {
296
+ const key = token === void 0 ? id : `${id}\0${token}`;
297
+ const references = state().references;
298
+ let reference = references.get(key);
299
+ if (!reference) {
300
+ reference = createReference({
301
+ id,
302
+ token,
303
+ bound: [],
304
+ name
305
+ });
306
+ references.set(key, reference);
307
+ }
308
+ return reference;
309
+ }
310
+ export { emit as a, createValueStore as c, defineClientHooks as i, getValueStore as l, decodeActionReference as n, hookEventName as o, setActionNavigator as r, setClientHooks as s, createServerReference as t, valueHeaders as u };
@@ -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 };