@farm.js/plugin 0.1.0-beta.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.
Files changed (51) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +11 -0
  3. package/dist/api/index.d.ts +42 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +567 -0
  6. package/dist/context/index.d.ts +61 -0
  7. package/dist/context/index.d.ts.map +1 -0
  8. package/dist/context/index.js +75 -0
  9. package/dist/index.d.ts +43 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +49 -0
  12. package/dist/middleware/index.d.ts +97 -0
  13. package/dist/middleware/index.d.ts.map +1 -0
  14. package/dist/middleware/index.js +469 -0
  15. package/dist/observability/index.d.ts +190 -0
  16. package/dist/observability/index.d.ts.map +1 -0
  17. package/dist/observability/index.js +399 -0
  18. package/dist/rsc/build-paths.d.ts +3 -0
  19. package/dist/rsc/build-paths.d.ts.map +1 -0
  20. package/dist/rsc/build-paths.js +8 -0
  21. package/dist/rsc/entries/client.d.ts +14 -0
  22. package/dist/rsc/entries/client.d.ts.map +1 -0
  23. package/dist/rsc/entries/client.js +283 -0
  24. package/dist/rsc/entries/rsc.d.ts +13 -0
  25. package/dist/rsc/entries/rsc.d.ts.map +1 -0
  26. package/dist/rsc/entries/rsc.js +932 -0
  27. package/dist/rsc/entries/ssr.d.ts +13 -0
  28. package/dist/rsc/entries/ssr.d.ts.map +1 -0
  29. package/dist/rsc/entries/ssr.js +245 -0
  30. package/dist/rsc/index.d.ts +78 -0
  31. package/dist/rsc/index.d.ts.map +1 -0
  32. package/dist/rsc/index.js +1368 -0
  33. package/dist/rsc/nitro-build.d.ts +36 -0
  34. package/dist/rsc/nitro-build.d.ts.map +1 -0
  35. package/dist/rsc/nitro-build.js +396 -0
  36. package/dist/rsc/optimized-boundary.d.ts +20 -0
  37. package/dist/rsc/optimized-boundary.d.ts.map +1 -0
  38. package/dist/rsc/optimized-boundary.js +15 -0
  39. package/dist/rsc/server-fn-transform.d.ts +6 -0
  40. package/dist/rsc/server-fn-transform.d.ts.map +1 -0
  41. package/dist/rsc/server-fn-transform.js +152 -0
  42. package/dist/rsc/types.d.ts +123 -0
  43. package/dist/rsc/types.d.ts.map +1 -0
  44. package/dist/rsc/types.js +1 -0
  45. package/dist/rsc/vite-plugin-nitro.d.ts +33 -0
  46. package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -0
  47. package/dist/rsc/vite-plugin-nitro.js +163 -0
  48. package/package.json +94 -0
  49. package/scripts/build.js +7 -0
  50. package/scripts/clean.js +6 -0
  51. package/scripts/run-nitro.mjs +18 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Nitro-based production build for RSC (React Server Components).
3
+ * Wraps the built RSC entry (fetch handler) in a Nitro server for Vercel etc.
4
+ * Based on: https://github.com/hi-ogawa/vite-plugins (packages/nitro), vite-plugin-rsc-deploy-example.
5
+ */
6
+ /**
7
+ * Register a server runtime owned by the Farm plugin so production output is
8
+ * self-contained even when the application does not install it directly.
9
+ */
10
+ export declare function registerRscNitroRuntimePackage(root: string, packageName: string, packageRoot: string): void;
11
+ /**
12
+ * Wait for the RSC plugin to write the assets manifest to dist/rsc (it may run after closeBundle(ssr)).
13
+ * Polls until the file exists and size > MIN_MANIFEST_BYTES or timeout.
14
+ */
15
+ export declare function waitForRscManifest(root: string, outDir: string, options?: {
16
+ intervalMs?: number;
17
+ timeoutMs?: number;
18
+ }): Promise<void>;
19
+ /**
20
+ * Wait until all RSC build outputs exist (rsc index, ssr index, manifest). Use in buildEnd(ssr) after a short delay.
21
+ */
22
+ export declare function waitForRscOutputs(root: string, outDir: string, options?: {
23
+ intervalMs?: number;
24
+ timeoutMs?: number;
25
+ }): Promise<void>;
26
+ export type BuildRscNitroOptions = {
27
+ rendererPath: string;
28
+ publicDir: string;
29
+ ssrPath?: string;
30
+ assetsDir?: string;
31
+ root: string;
32
+ outputDir?: string;
33
+ preset?: string;
34
+ };
35
+ export declare function buildRscNitro(options: BuildRscNitroOptions): Promise<void>;
36
+ //# sourceMappingURL=nitro-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nitro-build.d.ts","sourceRoot":"","sources":["../../src/rsc/nitro-build.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,IAAI,CAKN;AAwFD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,IAAI,CAAC,CAgCf;AA+DD,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqMhF"}
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Nitro-based production build for RSC (React Server Components).
3
+ * Wraps the built RSC entry (fetch handler) in a Nitro server for Vercel etc.
4
+ * Based on: https://github.com/hi-ogawa/vite-plugins (packages/nitro), vite-plugin-rsc-deploy-example.
5
+ */
6
+ import path from "path";
7
+ import { createRequire } from "node:module";
8
+ import { writeFileSync, readFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, statSync, } from "fs";
9
+ import { resolveRscBuildOutputPath } from "./build-paths.js";
10
+ const MANIFEST_FILENAME = "__vite_rsc_assets_manifest.js";
11
+ const MIN_MANIFEST_BYTES = 100; // real manifest is ~500+, stub is 64
12
+ const POLL_INTERVAL_MS = 50;
13
+ const POLL_TIMEOUT_MS = 15_000;
14
+ const BUNDLED_RSC_RUNTIMES = ["@farming-labs/strata"];
15
+ const runtimeRequire = createRequire(import.meta.url);
16
+ const runtimePackagesByRoot = new Map();
17
+ /**
18
+ * Register a server runtime owned by the Farm plugin so production output is
19
+ * self-contained even when the application does not install it directly.
20
+ */
21
+ export function registerRscNitroRuntimePackage(root, packageName, packageRoot) {
22
+ const resolvedRoot = path.resolve(root);
23
+ const packages = runtimePackagesByRoot.get(resolvedRoot) ?? new Map();
24
+ packages.set(packageName, path.resolve(packageRoot));
25
+ runtimePackagesByRoot.set(resolvedRoot, packages);
26
+ }
27
+ function copyDir(src, dest, ignoredNames = new Set()) {
28
+ if (!existsSync(src))
29
+ return;
30
+ mkdirSync(dest, { recursive: true });
31
+ for (const name of readdirSync(src)) {
32
+ if (ignoredNames.has(name))
33
+ continue;
34
+ const srcPath = path.join(src, name);
35
+ const destPath = path.join(dest, name);
36
+ if (statSync(srcPath).isDirectory())
37
+ copyDir(srcPath, destPath, ignoredNames);
38
+ else
39
+ copyFileSync(srcPath, destPath);
40
+ }
41
+ }
42
+ function detectBundledRscRuntimes(rendererPath) {
43
+ const packages = new Map();
44
+ const rendererDir = path.dirname(path.resolve(rendererPath));
45
+ const files = [];
46
+ const collectJavaScript = (dir) => {
47
+ if (!existsSync(dir))
48
+ return;
49
+ for (const name of readdirSync(dir)) {
50
+ const candidate = path.join(dir, name);
51
+ if (statSync(candidate).isDirectory())
52
+ collectJavaScript(candidate);
53
+ else if (/\.(?:c|m)?js$/.test(name))
54
+ files.push(candidate);
55
+ }
56
+ };
57
+ collectJavaScript(rendererDir);
58
+ const source = files.map((file) => readFileSync(file, "utf-8")).join("\n");
59
+ for (const packageName of BUNDLED_RSC_RUNTIMES) {
60
+ if (!source.includes(packageName))
61
+ continue;
62
+ try {
63
+ const manifestPath = runtimeRequire.resolve(`${packageName}/package.json`);
64
+ packages.set(packageName, path.dirname(manifestPath));
65
+ }
66
+ catch {
67
+ throw new Error(`[Farm.js] The RSC bundle uses ${packageName}, but Farm could not locate its bundled runtime.`);
68
+ }
69
+ }
70
+ return packages;
71
+ }
72
+ function copyRuntimePackages(serverDir, packages) {
73
+ if (packages.size === 0)
74
+ return;
75
+ const outputPackagePath = path.join(serverDir, "package.json");
76
+ const outputPackage = JSON.parse(readFileSync(outputPackagePath, "utf-8"));
77
+ outputPackage.dependencies ??= {};
78
+ const copiedPackages = new Set();
79
+ const copyPackage = (packageName, packageRoot) => {
80
+ if (copiedPackages.has(packageName))
81
+ return;
82
+ const manifestPath = path.join(packageRoot, "package.json");
83
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
84
+ copyDir(packageRoot, path.join(serverDir, "node_modules", ...packageName.split("/")), new Set(["node_modules"]));
85
+ outputPackage.dependencies[packageName] = manifest.version ?? "0.0.0";
86
+ copiedPackages.add(packageName);
87
+ const packageRequire = createRequire(manifestPath);
88
+ for (const optionalDependency of Object.keys(manifest.optionalDependencies ?? {})) {
89
+ try {
90
+ const optionalManifestPath = packageRequire.resolve(`${optionalDependency}/package.json`);
91
+ copyPackage(optionalDependency, path.dirname(optionalManifestPath));
92
+ }
93
+ catch {
94
+ // Package managers only install optional dependencies for the current
95
+ // build platform. Missing platform packages are expected.
96
+ }
97
+ }
98
+ };
99
+ for (const [packageName, packageRoot] of packages) {
100
+ copyPackage(packageName, packageRoot);
101
+ }
102
+ writeFileSync(outputPackagePath, JSON.stringify(outputPackage, null, 2), "utf-8");
103
+ }
104
+ /**
105
+ * Wait for the RSC plugin to write the assets manifest to dist/rsc (it may run after closeBundle(ssr)).
106
+ * Polls until the file exists and size > MIN_MANIFEST_BYTES or timeout.
107
+ */
108
+ export function waitForRscManifest(root, outDir, options = {}) {
109
+ const intervalMs = options.intervalMs ?? POLL_INTERVAL_MS;
110
+ const timeoutMs = options.timeoutMs ?? POLL_TIMEOUT_MS;
111
+ const manifestPath = resolveRscBuildOutputPath(root, outDir, "rsc", MANIFEST_FILENAME);
112
+ const deadline = Date.now() + timeoutMs;
113
+ return new Promise((resolve, reject) => {
114
+ const check = () => {
115
+ try {
116
+ if (existsSync(manifestPath)) {
117
+ const size = statSync(manifestPath).size;
118
+ if (size >= MIN_MANIFEST_BYTES) {
119
+ resolve();
120
+ return;
121
+ }
122
+ }
123
+ }
124
+ catch {
125
+ // ignore
126
+ }
127
+ if (Date.now() >= deadline) {
128
+ reject(new Error(`Timeout waiting for RSC manifest at ${manifestPath}`));
129
+ return;
130
+ }
131
+ setTimeout(check, intervalMs);
132
+ };
133
+ check();
134
+ });
135
+ }
136
+ /**
137
+ * Wait until all RSC build outputs exist (rsc index, ssr index, manifest). Use in buildEnd(ssr) after a short delay.
138
+ */
139
+ export function waitForRscOutputs(root, outDir, options = {}) {
140
+ const intervalMs = options.intervalMs ?? POLL_INTERVAL_MS;
141
+ const timeoutMs = options.timeoutMs ?? 20_000;
142
+ const rendererPath = resolveRscBuildOutputPath(root, outDir, "rsc", "index.js");
143
+ const ssrPath = resolveRscBuildOutputPath(root, outDir, "ssr", "index.js");
144
+ const manifestInRsc = resolveRscBuildOutputPath(root, outDir, "rsc", MANIFEST_FILENAME);
145
+ const manifestInSsr = resolveRscBuildOutputPath(root, outDir, "ssr", MANIFEST_FILENAME);
146
+ const deadline = Date.now() + timeoutMs;
147
+ return new Promise((resolve, reject) => {
148
+ const check = () => {
149
+ try {
150
+ const manifestPath = existsSync(manifestInRsc) ? manifestInRsc : manifestInSsr;
151
+ if (existsSync(rendererPath) &&
152
+ existsSync(ssrPath) &&
153
+ existsSync(manifestPath) &&
154
+ statSync(manifestPath).size >= MIN_MANIFEST_BYTES) {
155
+ resolve();
156
+ return;
157
+ }
158
+ }
159
+ catch {
160
+ // ignore
161
+ }
162
+ if (Date.now() >= deadline) {
163
+ reject(new Error(`Timeout waiting for RSC outputs under ${path.join(root, outDir)}`));
164
+ return;
165
+ }
166
+ setTimeout(check, intervalMs);
167
+ };
168
+ check();
169
+ });
170
+ }
171
+ /** Write a physical entry file. Use relative imports so we can externalize and copy dist into output. */
172
+ function writeRscEntryFile(buildDir, rendererPath, ssrPath) {
173
+ const resolvedRenderer = path.resolve(rendererPath);
174
+ const resolvedSsr = ssrPath ? path.resolve(ssrPath) : null;
175
+ // Relative to buildDir (.nitro) -> e.g. ../dist/rsc/index.js
176
+ const relRenderer = path.relative(buildDir, resolvedRenderer).replace(/\\/g, "/");
177
+ const relSsr = resolvedSsr ? path.relative(buildDir, resolvedSsr).replace(/\\/g, "/") : null;
178
+ const setSsLoader = relSsr
179
+ ? `globalThis.__VITE_RSC_LOAD_SSR__ = () => import("./${relSsr}");`
180
+ : "";
181
+ const code = `
182
+ ${setSsLoader}
183
+ import * as entryExports from "./${relRenderer}";
184
+ import { defineEventHandler } from "h3";
185
+
186
+ function getFetchHandler(exports) {
187
+ const def = exports?.default;
188
+ if (def && typeof def === "object" && "fetch" in def && typeof def.fetch === "function") return def.fetch;
189
+ if (typeof def === "function") return def;
190
+ throw new Error("Invalid RSC server handler: expected default.fetch or default function");
191
+ }
192
+
193
+ const handler = getFetchHandler(entryExports);
194
+
195
+ function createRscRequest(event) {
196
+ const node = event.node;
197
+ if (!node?.req || !node?.res) return event.req;
198
+
199
+ const controller = new AbortController();
200
+ const cleanup = () => {
201
+ node.req.off("aborted", abort);
202
+ node.res.off("close", close);
203
+ node.res.off("finish", cleanup);
204
+ };
205
+ const abort = () => {
206
+ if (!controller.signal.aborted) controller.abort();
207
+ cleanup();
208
+ };
209
+ const close = () => {
210
+ if (!node.res.writableEnded) abort();
211
+ else cleanup();
212
+ };
213
+
214
+ node.req.once("aborted", abort);
215
+ node.res.once("close", close);
216
+ node.res.once("finish", cleanup);
217
+ if (node.req.aborted) abort();
218
+
219
+ return new Request(event.req, { signal: controller.signal });
220
+ }
221
+
222
+ export default defineEventHandler((event) => handler(createRscRequest(event), {
223
+ waitUntil: (promise) => event.waitUntil(promise),
224
+ }));
225
+ `.trim();
226
+ const entryPath = path.join(buildDir, "rsc-entry.mjs");
227
+ mkdirSync(buildDir, { recursive: true });
228
+ writeFileSync(entryPath, code, "utf-8");
229
+ return entryPath;
230
+ }
231
+ export async function buildRscNitro(options) {
232
+ const { rendererPath, publicDir, ssrPath, assetsDir, root, outputDir = path.join(root, ".output"), preset = "vercel", } = options;
233
+ const { build, copyPublicAssets, createNitro, prepare } = await import("nitro");
234
+ console.log(`[FARM] Building RSC server with Nitro (preset: ${preset})...`);
235
+ const buildDir = path.join(root, ".nitro");
236
+ const serverDir = path.join(outputDir, "server");
237
+ const publicOutDir = path.join(outputDir, "public");
238
+ const entryPath = writeRscEntryFile(buildDir, rendererPath, ssrPath);
239
+ const registeredRuntimePackages = new Map(runtimePackagesByRoot.get(path.resolve(root)) ?? []);
240
+ for (const [packageName, packageRoot] of detectBundledRscRuntimes(rendererPath)) {
241
+ registeredRuntimePackages.set(packageName, packageRoot);
242
+ }
243
+ const publicAssets = [
244
+ { dir: publicDir, baseURL: "/", maxAge: 31536000 },
245
+ ];
246
+ if (assetsDir) {
247
+ publicAssets.push({
248
+ dir: path.join(publicDir, assetsDir),
249
+ baseURL: `/${assetsDir}`,
250
+ maxAge: 31536000,
251
+ });
252
+ }
253
+ const config = {
254
+ preset,
255
+ rootDir: root,
256
+ srcDir: root,
257
+ buildDir,
258
+ dev: false,
259
+ output: {
260
+ dir: outputDir,
261
+ serverDir,
262
+ publicDir: publicOutDir,
263
+ },
264
+ publicAssets,
265
+ renderer: { entry: entryPath },
266
+ // The prebuilt RSC/SSR bundles are copied after Nitro finishes, so Nitro's
267
+ // module graph cannot otherwise see their bare package imports. Trace both
268
+ // entry points explicitly to keep node-server output self-contained.
269
+ externals: {
270
+ traceInclude: [rendererPath, ...(ssrPath ? [ssrPath] : [])],
271
+ },
272
+ // Externalize rsc/ssr so we don't bundle (they reference Vite-generated manifest). We copy dist into server output after build.
273
+ rollupConfig: {
274
+ external: (id) => id.includes("../dist/") || id.includes("dist/rsc") || id.includes("dist/ssr"),
275
+ },
276
+ compatibilityDate: "2024-12-01",
277
+ minify: true,
278
+ };
279
+ const nitro = await createNitro(config);
280
+ await prepare(nitro);
281
+ await copyPublicAssets(nitro);
282
+ await build(nitro);
283
+ await nitro.close();
284
+ copyRuntimePackages(serverDir, registeredRuntimePackages);
285
+ // Copy dist into server so runtime relative imports resolve. Handle missing dir (e.g. buildApp order).
286
+ const serverDistDir = path.join(serverDir, "dist");
287
+ const rscSrc = path.dirname(path.resolve(rendererPath));
288
+ const ssrSrc = ssrPath ? path.dirname(path.resolve(ssrPath)) : null;
289
+ mkdirSync(serverDistDir, { recursive: true });
290
+ copyDir(rscSrc, path.join(serverDistDir, "rsc"));
291
+ if (ssrSrc)
292
+ copyDir(ssrSrc, path.join(serverDistDir, "ssr"));
293
+ // Stub manifest if missing (RSC plugin may write it after our hook runs)
294
+ const rscDestDir = path.join(serverDistDir, "rsc");
295
+ const manifestDest = path.join(rscDestDir, "__vite_rsc_assets_manifest.js");
296
+ if (!existsSync(manifestDest)) {
297
+ mkdirSync(rscDestDir, { recursive: true });
298
+ writeFileSync(manifestDest, "export default { serverResources: {}, clientReferenceDeps: {} };", "utf-8");
299
+ }
300
+ // Nitro may group the entry under chunks/build or chunks/_ depending on the
301
+ // selected builder. Find it by name and make copied-dist imports relative to
302
+ // the emitted chunk so the output survives being moved away from the project.
303
+ const entryChunkPaths = [];
304
+ const findEntryChunks = (dir) => {
305
+ if (!existsSync(dir))
306
+ return;
307
+ for (const name of readdirSync(dir)) {
308
+ const candidate = path.join(dir, name);
309
+ if (statSync(candidate).isDirectory())
310
+ findEntryChunks(candidate);
311
+ else if (name === "rsc-entry.mjs")
312
+ entryChunkPaths.push(candidate);
313
+ }
314
+ };
315
+ findEntryChunks(path.join(serverDir, "chunks"));
316
+ for (const entryChunkPath of entryChunkPaths) {
317
+ let code = readFileSync(entryChunkPath, "utf-8");
318
+ const relativeImport = (target) => {
319
+ const relative = path.relative(path.dirname(entryChunkPath), target).replace(/\\/g, "/");
320
+ return relative.startsWith(".") ? relative : `./${relative}`;
321
+ };
322
+ // Match any path that ends with dist/rsc/index.js or dist/ssr/index.js (minified, no spaces)
323
+ code = code.replace(/(["'`])[^"'`]*?dist\/rsc\/index\.js\1/g, JSON.stringify(relativeImport(path.join(serverDistDir, "rsc", "index.js"))));
324
+ code = code.replace(/(["'`])[^"'`]*?dist\/ssr\/index\.js\1/g, JSON.stringify(relativeImport(path.join(serverDistDir, "ssr", "index.js"))));
325
+ writeFileSync(entryChunkPath, code, "utf-8");
326
+ }
327
+ // Patch SSR bundle: inject production client CSS href and bootstrap script so HTML has styles and hydration works
328
+ const ssrIndexPath = path.join(serverDistDir, "ssr", "index.js");
329
+ const manifestPath = path.join(serverDistDir, "rsc", MANIFEST_FILENAME);
330
+ let bootstrapScriptContent = "";
331
+ let clientEntryHref = "";
332
+ const RSC_CALL_SERVER_PLACEHOLDER = "(function(){if(typeof globalThis.__viteRscCallServer!=='function'){globalThis.__viteRscCallServer=function(){return Promise.reject(new Error(\"Farm.js: server actions not ready\"));}}})();\n";
333
+ if (existsSync(manifestPath)) {
334
+ try {
335
+ const manifestContent = readFileSync(manifestPath, "utf-8");
336
+ const start = manifestContent.indexOf("{");
337
+ const end = manifestContent.lastIndexOf("}") + 1;
338
+ if (start >= 0 && end > start) {
339
+ const manifest = JSON.parse(manifestContent.slice(start, end));
340
+ bootstrapScriptContent = manifest.bootstrapScriptContent || "";
341
+ const importMatch = bootstrapScriptContent.match(/^import\s*\(\s*["']([^"']+)["']\s*\)\s*$/);
342
+ if (importMatch)
343
+ clientEntryHref = importMatch[1];
344
+ }
345
+ }
346
+ catch {
347
+ // ignore
348
+ }
349
+ }
350
+ const fullBootstrap = bootstrapScriptContent
351
+ ? RSC_CALL_SERVER_PLACEHOLDER + bootstrapScriptContent
352
+ : "";
353
+ // Patch manifest so production SSR gets bootstrap with __viteRscCallServer placeholder (SSR reads assetsManifest.bootstrapScriptContent)
354
+ if (fullBootstrap && existsSync(manifestPath)) {
355
+ try {
356
+ let manifestCode = readFileSync(manifestPath, "utf-8");
357
+ const escaped = JSON.stringify(bootstrapScriptContent);
358
+ const escapedFull = JSON.stringify(fullBootstrap);
359
+ if (manifestCode.includes(escaped) && escaped !== escapedFull) {
360
+ manifestCode = manifestCode.replace(escaped, escapedFull);
361
+ writeFileSync(manifestPath, manifestCode, "utf-8");
362
+ }
363
+ }
364
+ catch {
365
+ // ignore
366
+ }
367
+ }
368
+ if (existsSync(ssrIndexPath)) {
369
+ const assetsDir = path.join(publicDir, "assets");
370
+ let clientCssHref = "";
371
+ if (existsSync(assetsDir)) {
372
+ const files = readdirSync(assetsDir);
373
+ const cssFile = files.find((f) => f.endsWith(".css"));
374
+ if (cssFile)
375
+ clientCssHref = "/assets/" + cssFile;
376
+ }
377
+ try {
378
+ let ssrCode = readFileSync(ssrIndexPath, "utf-8");
379
+ if (ssrCode.includes("__FARM_CLIENT_CSS_HREF__")) {
380
+ ssrCode = ssrCode.replace("__FARM_CLIENT_CSS_HREF__", clientCssHref);
381
+ }
382
+ if (clientEntryHref && ssrCode.includes("__FARM_CLIENT_ENTRY_HREF__")) {
383
+ ssrCode = ssrCode.replace(/(CLIENT_ENTRY_HREF\s*=\s*)"__FARM_CLIENT_ENTRY_HREF__"/, "$1" + JSON.stringify(clientEntryHref));
384
+ }
385
+ if (fullBootstrap && ssrCode.includes("__FARM_BOOTSTRAP_SCRIPT__")) {
386
+ ssrCode = ssrCode.replace(/=\s*["']__FARM_BOOTSTRAP_SCRIPT__["']/, "= " + JSON.stringify(fullBootstrap));
387
+ }
388
+ writeFileSync(ssrIndexPath, ssrCode, "utf-8");
389
+ }
390
+ catch {
391
+ // ignore
392
+ }
393
+ }
394
+ console.log("[FARM] RSC Nitro build completed");
395
+ console.log(`[FARM] Output: ${outputDir}`);
396
+ }
@@ -0,0 +1,20 @@
1
+ import { type RenderOptions, type StrataDocument } from "@farming-labs/strata";
2
+ import { type StaticFragmentBoundary, type StaticFragmentProps } from "@farming-labs/strata/react-server";
3
+ export type { RenderOptions, StrataDocument, StrataElement, StrataNode, StrataTag, StrataText, } from "@farming-labs/strata";
4
+ export interface OptimizedBoundaryProps extends Omit<StaticFragmentProps, "content"> {
5
+ /**
6
+ * Typed host-only content. Client Components, refs, effects, and event
7
+ * handlers must remain outside the boundary.
8
+ */
9
+ document: StrataDocument;
10
+ renderOptions?: RenderOptions;
11
+ }
12
+ /**
13
+ * Render a typed host-only document with the optimized native runtime and
14
+ * place it inside an opaque React-owned boundary.
15
+ *
16
+ * Enable `experimental.optimizedBoundary` before importing this component.
17
+ */
18
+ export declare function OptimizedBoundary({ document, renderOptions, ...props }: OptimizedBoundaryProps): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
19
+ export type OptimizedBoundaryTag = StaticFragmentBoundary;
20
+ //# sourceMappingURL=optimized-boundary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/optimized-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACvF,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACzB,MAAM,mCAAmC,CAAC;AAE3C,YAAY,EACV,aAAa,EACb,cAAc,EACd,aAAa,EACb,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC;IAClF;;;OAGG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,KAAK,EAAE,EAAE,sBAAsB,0FAM9F;AAED,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { render } from "@farming-labs/strata";
2
+ import { StaticFragment, } from "@farming-labs/strata/react-server";
3
+ /**
4
+ * Render a typed host-only document with the optimized native runtime and
5
+ * place it inside an opaque React-owned boundary.
6
+ *
7
+ * Enable `experimental.optimizedBoundary` before importing this component.
8
+ */
9
+ export function OptimizedBoundary({ document, renderOptions, ...props }) {
10
+ const content = render(document, renderOptions);
11
+ return StaticFragment({
12
+ ...props,
13
+ content,
14
+ });
15
+ }
@@ -0,0 +1,6 @@
1
+ export type FarmServerFnTransformResult = {
2
+ code: string;
3
+ exports: string[];
4
+ };
5
+ export declare function transformFarmServerFns(code: string, id: string): FarmServerFnTransformResult | null;
6
+ //# sourceMappingURL=server-fn-transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-fn-transform.d.ts","sourceRoot":"","sources":["../../src/rsc/server-fn-transform.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,2BAA2B,GAAG,IAAI,CAiCpC"}
@@ -0,0 +1,152 @@
1
+ const SERVER_FUNCTION_FACTORY_IMPORT_RE = /\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?(\{[^}]*\})\s+from\s*["']@farm.js\/core(?:\/(?:server-fn|server-query))?["']\s*;?/g;
2
+ const EXPORT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*\(/g;
3
+ const MODULE_EXT_RE = /\.[cm]?[jt]sx?$/;
4
+ const DECLARATION_RE = /(^|\n)\s*["']([^"']+)["']\s*;?/g;
5
+ export function transformFarmServerFns(code, id) {
6
+ if (!isTransformableModule(id) ||
7
+ (!code.includes("createServerFn") && !code.includes("createServerQuery"))) {
8
+ return null;
9
+ }
10
+ const factoryNames = findServerFunctionFactoryImportNames(code);
11
+ if (factoryNames.size === 0)
12
+ return null;
13
+ const replacements = findServerFnExportReplacements(code, factoryNames);
14
+ if (replacements.length === 0)
15
+ return null;
16
+ if (hasModuleDirective(code, "use client")) {
17
+ throw new Error("createServerFn and createServerQuery exports must live in a server module. Move them out of the 'use client' file.");
18
+ }
19
+ let output = code;
20
+ for (const replacement of [...replacements].sort((a, b) => b.start - a.start)) {
21
+ output = output.slice(0, replacement.start) + replacement.code + output.slice(replacement.end);
22
+ }
23
+ if (!hasModuleDirective(output, "use server")) {
24
+ output = `"use server";\n${output}`;
25
+ }
26
+ return {
27
+ code: output,
28
+ exports: replacements.map((replacement) => replacement.exportName),
29
+ };
30
+ }
31
+ function isTransformableModule(id) {
32
+ const cleanId = id.split("?", 1)[0];
33
+ return MODULE_EXT_RE.test(cleanId) && !/\.d\.[cm]?ts$/.test(cleanId);
34
+ }
35
+ function findServerFunctionFactoryImportNames(code) {
36
+ const names = new Map();
37
+ for (const match of code.matchAll(SERVER_FUNCTION_FACTORY_IMPORT_RE)) {
38
+ const importClause = match[1] ?? "";
39
+ const namedStart = importClause.indexOf("{");
40
+ const namedEnd = importClause.lastIndexOf("}");
41
+ if (namedStart === -1 || namedEnd === -1 || namedEnd <= namedStart)
42
+ continue;
43
+ const namedImports = importClause.slice(namedStart + 1, namedEnd).split(",");
44
+ for (const specifier of namedImports) {
45
+ const normalized = specifier.trim().replace(/^type\s+/, "");
46
+ const specifierMatch = normalized.match(/^(createServerFn|createServerQuery)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
47
+ if (specifierMatch) {
48
+ const factory = specifierMatch[1];
49
+ names.set(specifierMatch[2] ?? factory, factory === "createServerFn" ? "fn" : "query");
50
+ }
51
+ }
52
+ }
53
+ return names;
54
+ }
55
+ function findServerFnExportReplacements(code, factoryNames) {
56
+ const replacements = [];
57
+ for (const match of code.matchAll(EXPORT_SERVER_FUNCTION_FACTORY_RE)) {
58
+ const exportName = match[1];
59
+ const calleeName = match[2];
60
+ const factory = calleeName ? factoryNames.get(calleeName) : undefined;
61
+ if (!exportName || !calleeName || !factory)
62
+ continue;
63
+ const openParenIndex = (match.index ?? 0) + match[0].length - 1;
64
+ const callEnd = findMatchingParen(code, openParenIndex);
65
+ if (callEnd === -1)
66
+ continue;
67
+ let declarationEnd = callEnd + 1;
68
+ while (/\s/.test(code[declarationEnd] ?? ""))
69
+ declarationEnd++;
70
+ if (code[declarationEnd] === ";")
71
+ declarationEnd++;
72
+ const calleeIndex = code.indexOf(calleeName, match.index);
73
+ const callExpression = code.slice(calleeIndex, callEnd + 1);
74
+ const privateName = `$$farm_server_${factory}_${exportName}`;
75
+ replacements.push({
76
+ start: match.index ?? 0,
77
+ end: declarationEnd,
78
+ exportName,
79
+ code: `const ${privateName} = ${callExpression};\nexport async function ${exportName}(input) {\n return ${privateName}(input);\n}`,
80
+ });
81
+ }
82
+ return replacements;
83
+ }
84
+ function hasModuleDirective(code, directive) {
85
+ for (const match of code.matchAll(DECLARATION_RE)) {
86
+ const value = match[2];
87
+ if (value === directive)
88
+ return true;
89
+ if (value !== "use strict" && value !== "use client" && value !== "use server")
90
+ return false;
91
+ }
92
+ return false;
93
+ }
94
+ function findMatchingParen(code, openParenIndex) {
95
+ let depth = 0;
96
+ let quote = null;
97
+ let lineComment = false;
98
+ let blockComment = false;
99
+ let escaped = false;
100
+ for (let index = openParenIndex; index < code.length; index++) {
101
+ const char = code[index];
102
+ const next = code[index + 1];
103
+ if (lineComment) {
104
+ if (char === "\n" || char === "\r")
105
+ lineComment = false;
106
+ continue;
107
+ }
108
+ if (blockComment) {
109
+ if (char === "*" && next === "/") {
110
+ blockComment = false;
111
+ index++;
112
+ }
113
+ continue;
114
+ }
115
+ if (quote) {
116
+ if (escaped) {
117
+ escaped = false;
118
+ continue;
119
+ }
120
+ if (char === "\\") {
121
+ escaped = true;
122
+ continue;
123
+ }
124
+ if (char === quote) {
125
+ quote = null;
126
+ }
127
+ continue;
128
+ }
129
+ if (char === "/" && next === "/") {
130
+ lineComment = true;
131
+ index++;
132
+ continue;
133
+ }
134
+ if (char === "/" && next === "*") {
135
+ blockComment = true;
136
+ index++;
137
+ continue;
138
+ }
139
+ if (char === "'" || char === '"' || char === "`") {
140
+ quote = char;
141
+ continue;
142
+ }
143
+ if (char === "(")
144
+ depth++;
145
+ if (char === ")") {
146
+ depth--;
147
+ if (depth === 0)
148
+ return index;
149
+ }
150
+ }
151
+ return -1;
152
+ }