@farm.js/plugin 0.1.0-beta.103 → 0.1.0-beta.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -39,5 +39,5 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export declare const version = "0.1.0-beta.102";
42
+ export declare const version = "0.1.0-beta.104";
43
43
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export const version = "0.1.0-beta.102";
42
+ export const version = "0.1.0-beta.104";
43
43
  // Note: Import plugins from their respective subpaths:
44
44
  // - @farm.js/plugin/rsc
45
45
  // - @farm.js/plugin/middleware
@@ -1 +1 @@
1
- {"version":3,"file":"server-fn-transform.d.ts","sourceRoot":"","sources":["../../src/rsc/server-fn-transform.ts"],"names":[],"mappings":"AASA,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"}
1
+ {"version":3,"file":"server-fn-transform.d.ts","sourceRoot":"","sources":["../../src/rsc/server-fn-transform.ts"],"names":[],"mappings":"AAkBA,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,CAoDpC"}
@@ -1,6 +1,13 @@
1
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
2
  const EXPORT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?\s*\(/g;
3
3
  const EXPORT_DEFAULT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?\s*\(/g;
4
+ // Any assignment counts as a declaration, not only `const x = factory(...)`:
5
+ // splitting `let save; save = createServerFn(...)` must not hide the binding
6
+ // from the fail-closed export checks below.
7
+ const SERVER_FUNCTION_DECLARATION_RE = /(?<![.\w$])([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?\s*\(/g;
8
+ const LOCAL_EXPORT_CLAUSE_RE = /\bexport\s*\{([^}]*)\}/g;
9
+ const EXPORT_DEFAULT_ALIAS_RE = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*(?:;|(?=[\r\n])|$)/g;
10
+ const EXPORT_BINDING_ALIAS_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*(?:;|(?=[,\r\n])|$)/g;
4
11
  const MODULE_EXT_RE = /\.[cm]?[jt]sx?$/;
5
12
  const DECLARATION_RE = /(^|\n)\s*["']([^"']+)["']\s*;?/g;
6
13
  export function transformFarmServerFns(code, id) {
@@ -11,6 +18,20 @@ export function transformFarmServerFns(code, id) {
11
18
  const factoryNames = findServerFunctionFactoryImportNames(code);
12
19
  if (factoryNames.size === 0)
13
20
  return null;
21
+ const reExport = findServerFnReExport(code, factoryNames);
22
+ if (reExport) {
23
+ const factoryName = reExport.factory === "fn" ? "createServerFn" : "createServerQuery";
24
+ const escape = reExport.form === "clause"
25
+ ? "with an export clause"
26
+ : reExport.form === "default"
27
+ ? "as the default export"
28
+ : "through another exported binding";
29
+ const inlineForm = reExport.form === "default"
30
+ ? `export default ${factoryName}(...)`
31
+ : `export const ${reExport.exportName} = ${factoryName}(...)`;
32
+ throw new Error(`Server function "${reExport.localName}" cannot be re-exported ${escape}. ` +
33
+ `Export it inline instead: ${inlineForm}.`);
34
+ }
14
35
  const replacements = findServerFnExportReplacements(code, factoryNames);
15
36
  if (replacements.length === 0)
16
37
  return null;
@@ -53,6 +74,135 @@ function findServerFunctionFactoryImportNames(code) {
53
74
  }
54
75
  return names;
55
76
  }
77
+ function findServerFnReExport(code, factoryNames) {
78
+ // Comments and string contents must not hide or fake an export: a comment
79
+ // inside `export { /* … */ save }` previously made the specifier unparsable
80
+ // and the module shipped untransformed.
81
+ const scannable = stripCommentsAndStrings(code);
82
+ const declarations = new Map();
83
+ for (const match of scannable.matchAll(SERVER_FUNCTION_DECLARATION_RE)) {
84
+ const localName = match[1];
85
+ const factoryName = match[2];
86
+ const factory = factoryName ? factoryNames.get(factoryName) : undefined;
87
+ if (localName && factory)
88
+ declarations.set(localName, factory);
89
+ }
90
+ if (declarations.size === 0)
91
+ return null;
92
+ for (const match of scannable.matchAll(LOCAL_EXPORT_CLAUSE_RE)) {
93
+ const clause = match[1] ?? "";
94
+ const clauseEnd = (match.index ?? 0) + match[0].length;
95
+ if (scannable.slice(clauseEnd).trimStart().startsWith("from"))
96
+ continue;
97
+ for (const specifier of clause.split(",")) {
98
+ const parts = specifier.trim().split(/\s+as\s+/);
99
+ const localName = parts[0]?.trim();
100
+ if (!localName || !declarations.has(localName))
101
+ continue;
102
+ return {
103
+ localName,
104
+ exportName: parts[1]?.trim() || localName,
105
+ factory: declarations.get(localName),
106
+ form: "clause",
107
+ };
108
+ }
109
+ }
110
+ // `export default save;` — the export-default replacement only recognizes a
111
+ // direct factory call, so a bare identifier slipped through untransformed.
112
+ for (const match of scannable.matchAll(EXPORT_DEFAULT_ALIAS_RE)) {
113
+ const localName = match[1];
114
+ if (!localName || !declarations.has(localName))
115
+ continue;
116
+ return {
117
+ localName,
118
+ exportName: "default",
119
+ factory: declarations.get(localName),
120
+ form: "default",
121
+ };
122
+ }
123
+ // `export const persist = save;` — same escape through a renamed binding.
124
+ for (const match of scannable.matchAll(EXPORT_BINDING_ALIAS_RE)) {
125
+ const exportName = match[1];
126
+ const localName = match[2];
127
+ if (!exportName || !localName || !declarations.has(localName))
128
+ continue;
129
+ return {
130
+ localName,
131
+ exportName,
132
+ factory: declarations.get(localName),
133
+ form: "binding",
134
+ };
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Blank out comments and string/template contents, preserving length, so the
140
+ * export scans above see only real syntax. Quotes themselves are kept; the
141
+ * characters between them become spaces.
142
+ */
143
+ function stripCommentsAndStrings(code) {
144
+ const output = code.split("");
145
+ let quote = null;
146
+ let lineComment = false;
147
+ let blockComment = false;
148
+ let escaped = false;
149
+ for (let index = 0; index < code.length; index++) {
150
+ const char = code[index];
151
+ const next = code[index + 1];
152
+ if (lineComment) {
153
+ if (char === "\n" || char === "\r")
154
+ lineComment = false;
155
+ else
156
+ output[index] = " ";
157
+ continue;
158
+ }
159
+ if (blockComment) {
160
+ if (char === "*" && next === "/") {
161
+ blockComment = false;
162
+ output[index] = " ";
163
+ output[index + 1] = " ";
164
+ index++;
165
+ }
166
+ else if (char !== "\n") {
167
+ output[index] = " ";
168
+ }
169
+ continue;
170
+ }
171
+ if (quote) {
172
+ if (escaped) {
173
+ escaped = false;
174
+ output[index] = " ";
175
+ continue;
176
+ }
177
+ if (char === "\\") {
178
+ escaped = true;
179
+ output[index] = " ";
180
+ continue;
181
+ }
182
+ if (char === quote) {
183
+ quote = null;
184
+ continue;
185
+ }
186
+ if (char !== "\n")
187
+ output[index] = " ";
188
+ continue;
189
+ }
190
+ if (char === "/" && next === "/") {
191
+ lineComment = true;
192
+ output[index] = " ";
193
+ continue;
194
+ }
195
+ if (char === "/" && next === "*") {
196
+ blockComment = true;
197
+ output[index] = " ";
198
+ continue;
199
+ }
200
+ if (char === "'" || char === '"' || char === "`") {
201
+ quote = char;
202
+ }
203
+ }
204
+ return output.join("");
205
+ }
56
206
  function findServerFnExportReplacements(code, factoryNames) {
57
207
  const replacements = [];
58
208
  for (const match of code.matchAll(EXPORT_SERVER_FUNCTION_FACTORY_RE)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm.js/plugin",
3
- "version": "0.1.0-beta.103",
3
+ "version": "0.1.0-beta.105",
4
4
  "description": "Official plugins for Farm.js framework",
5
5
  "license": "MIT",
6
6
  "author": "Farm.js Team",
@@ -61,7 +61,7 @@
61
61
  "nitro": "3.0.1-alpha.0",
62
62
  "picocolors": "^1.0.0",
63
63
  "rsc-html-stream": "^0.0.4",
64
- "@farm.js/core": "0.1.0-beta.103"
64
+ "@farm.js/core": "0.1.0-beta.105"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "^20.19.21",
@@ -82,7 +82,7 @@
82
82
  "react-dom": "19.2.8",
83
83
  "react-server-dom-webpack": "19.2.8",
84
84
  "vite": ">=6.0.0",
85
- "@farm.js/core": "0.1.0-beta.103"
85
+ "@farm.js/core": "0.1.0-beta.105"
86
86
  },
87
87
  "peerDependenciesMeta": {
88
88
  "@vitejs/plugin-rsc": {