zero-com 1.6.4 → 1.6.5

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/lib/common.d.ts CHANGED
@@ -1,4 +1,10 @@
1
+ import type { SourceMap } from 'magic-string';
1
2
  import { CallExpression, Project, SourceFile } from 'ts-morph';
3
+ export type Replacement = {
4
+ start: number;
5
+ end: number;
6
+ content: string;
7
+ };
2
8
  export type Options = {
3
9
  development?: boolean;
4
10
  };
@@ -28,15 +34,23 @@ export declare const resolveFilePath: (basePath: string) => string;
28
34
  export declare const createProject: () => Project;
29
35
  export declare const buildRegistry: (contextDir: string, registry: ServerFuncRegistry) => void;
30
36
  export declare const getImportedServerFunctions: (sourceFile: SourceFile, registry: ServerFuncRegistry) => Map<string, ServerFuncInfo>;
31
- export declare const transformCallSites: (sourceFile: SourceFile, importedFuncs: Map<string, ServerFuncInfo>) => boolean;
32
- export declare const transformHandleCalls: (sourceFile: SourceFile) => boolean;
33
- export declare const transformSendCalls: (sourceFile: SourceFile) => boolean;
34
- export declare const transformFuncCalls: (sourceFile: SourceFile) => boolean;
37
+ export declare const collectCallSiteReplacements: (sourceFile: SourceFile, importedFuncs: Map<string, ServerFuncInfo>) => Replacement[];
38
+ export declare const collectHandleCallReplacements: (sourceFile: SourceFile) => Replacement[];
39
+ export declare const collectSendCallReplacements: (sourceFile: SourceFile) => Replacement[];
40
+ export declare const collectFuncCallReplacements: (sourceFile: SourceFile) => Replacement[];
41
+ export declare const applyReplacementsWithMap: (source: string, replacements: Replacement[], filePath: string) => {
42
+ code: string;
43
+ map: SourceMap;
44
+ };
35
45
  export declare const appendRegistryCode: (sourceFile: SourceFile, fileRegistry: Map<string, ServerFuncInfo>) => string;
36
46
  export type TransformResult = {
37
47
  content: string;
38
48
  transformed: boolean;
49
+ map?: SourceMap;
50
+ };
51
+ export type TransformOptions = {
52
+ development?: boolean;
39
53
  };
40
- export declare const transformSourceFile: (filePath: string, content: string, registry: ServerFuncRegistry) => TransformResult;
54
+ export declare const transformSourceFile: (filePath: string, content: string, registry: ServerFuncRegistry, options?: TransformOptions) => TransformResult;
41
55
  export declare const emitToJs: (filePath: string, content: string) => string;
42
56
  export declare const applyReplacements: (code: string, compilationId: string) => string;
package/lib/common.js CHANGED
@@ -3,9 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.applyReplacements = exports.emitToJs = exports.transformSourceFile = exports.appendRegistryCode = exports.transformFuncCalls = exports.transformSendCalls = exports.transformHandleCalls = exports.transformCallSites = exports.getImportedServerFunctions = exports.buildRegistry = exports.createProject = exports.resolveFilePath = exports.isFromLibrary = exports.getReplacements = exports.generateCompilationId = exports.formatFuncIdName = exports.FILE_EXTENSIONS = exports.LIBRARY_NAME = exports.CONTEXT_TYPE_NAME = exports.CALL_NAME = exports.HANDLE_NAME = exports.SERVER_FUNCTION_WRAPPER_NAME = exports.ZERO_COM_SERVER_REGISTRY = exports.ZERO_COM_CLIENT_CALL = void 0;
6
+ exports.applyReplacements = exports.emitToJs = exports.transformSourceFile = exports.appendRegistryCode = exports.applyReplacementsWithMap = exports.collectFuncCallReplacements = exports.collectSendCallReplacements = exports.collectHandleCallReplacements = exports.collectCallSiteReplacements = exports.getImportedServerFunctions = exports.buildRegistry = exports.createProject = exports.resolveFilePath = exports.isFromLibrary = exports.getReplacements = exports.generateCompilationId = exports.formatFuncIdName = exports.FILE_EXTENSIONS = exports.LIBRARY_NAME = exports.CONTEXT_TYPE_NAME = exports.CALL_NAME = exports.HANDLE_NAME = exports.SERVER_FUNCTION_WRAPPER_NAME = exports.ZERO_COM_SERVER_REGISTRY = exports.ZERO_COM_CLIENT_CALL = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const magic_string_1 = __importDefault(require("magic-string"));
9
10
  const ts_morph_1 = require("ts-morph");
10
11
  // Constants
11
12
  exports.ZERO_COM_CLIENT_CALL = 'ZERO_COM_CLIENT_CALL';
@@ -153,11 +154,11 @@ const getImportedServerFunctions = (sourceFile, registry) => {
153
154
  return importedFuncs;
154
155
  };
155
156
  exports.getImportedServerFunctions = getImportedServerFunctions;
156
- // Transformations
157
- const transformCallSites = (sourceFile, importedFuncs) => {
157
+ // Transformations - collect replacements instead of modifying AST
158
+ const collectCallSiteReplacements = (sourceFile, importedFuncs) => {
158
159
  if (importedFuncs.size === 0)
159
- return false;
160
- let modified = false;
160
+ return [];
161
+ const replacements = [];
161
162
  sourceFile.forEachDescendant((node) => {
162
163
  if (node.getKind() !== ts_morph_1.SyntaxKind.CallExpression)
163
164
  return;
@@ -167,14 +168,17 @@ const transformCallSites = (sourceFile, importedFuncs) => {
167
168
  if (!funcInfo)
168
169
  return;
169
170
  const args = callExpr.getArguments().map(a => a.getText()).join(', ');
170
- callExpr.replaceWithText(`globalThis.${exports.ZERO_COM_CLIENT_CALL}('${funcInfo.funcId}', [${args}])`);
171
- modified = true;
171
+ replacements.push({
172
+ start: callExpr.getStart(),
173
+ end: callExpr.getEnd(),
174
+ content: `globalThis.${exports.ZERO_COM_CLIENT_CALL}('${funcInfo.funcId}', [${args}])`
175
+ });
172
176
  });
173
- return modified;
177
+ return replacements;
174
178
  };
175
- exports.transformCallSites = transformCallSites;
176
- const transformHandleCalls = (sourceFile) => {
177
- let modified = false;
179
+ exports.collectCallSiteReplacements = collectCallSiteReplacements;
180
+ const collectHandleCallReplacements = (sourceFile) => {
181
+ const replacements = [];
178
182
  sourceFile.forEachDescendant((node) => {
179
183
  if (node.getKind() !== ts_morph_1.SyntaxKind.CallExpression)
180
184
  return;
@@ -184,18 +188,20 @@ const transformHandleCalls = (sourceFile) => {
184
188
  const args = callExpr.getArguments();
185
189
  if (args.length < 3)
186
190
  return;
187
- // Inline the logic directly using a named function for better stack traces.
188
191
  const funcId = args[0].getText();
189
192
  const ctx = args[1].getText();
190
193
  const argsArray = args[2].getText();
191
- callExpr.replaceWithText(`(function handle(__fn, __ctx, __args) { return __fn.requireContext ? __fn(__ctx, ...__args) : __fn(...__args); })(globalThis.${exports.ZERO_COM_SERVER_REGISTRY}[${funcId}], ${ctx}, ${argsArray})`);
192
- modified = true;
194
+ replacements.push({
195
+ start: callExpr.getStart(),
196
+ end: callExpr.getEnd(),
197
+ content: `((__fn) => __fn.requireContext ? __fn(${ctx}, ...${argsArray}) : __fn(...${argsArray}))(globalThis.${exports.ZERO_COM_SERVER_REGISTRY}[${funcId}])`
198
+ });
193
199
  });
194
- return modified;
200
+ return replacements;
195
201
  };
196
- exports.transformHandleCalls = transformHandleCalls;
197
- const transformSendCalls = (sourceFile) => {
198
- let modified = false;
202
+ exports.collectHandleCallReplacements = collectHandleCallReplacements;
203
+ const collectSendCallReplacements = (sourceFile) => {
204
+ const replacements = [];
199
205
  sourceFile.forEachDescendant((node) => {
200
206
  if (node.getKind() !== ts_morph_1.SyntaxKind.CallExpression)
201
207
  return;
@@ -205,18 +211,21 @@ const transformSendCalls = (sourceFile) => {
205
211
  const args = callExpr.getArguments();
206
212
  if (args.length < 1)
207
213
  return;
208
- callExpr.replaceWithText(`globalThis.${exports.ZERO_COM_CLIENT_CALL} = ${args[0].getText()}`);
209
- modified = true;
214
+ replacements.push({
215
+ start: callExpr.getStart(),
216
+ end: callExpr.getEnd(),
217
+ content: `globalThis.${exports.ZERO_COM_CLIENT_CALL} = ${args[0].getText()}`
218
+ });
210
219
  });
211
- return modified;
220
+ return replacements;
212
221
  };
213
- exports.transformSendCalls = transformSendCalls;
214
- // Transform func(fn) calls to just fn.
222
+ exports.collectSendCallReplacements = collectSendCallReplacements;
223
+ // Collect func(fn) replacements to just fn.
215
224
  // The func() wrapper is only needed for type-level transformation (RemoveContextParam).
216
225
  // At runtime, we just need the raw function. The registration code (appendRegistryCode)
217
226
  // will set requireContext on the function based on compile-time analysis.
218
- const transformFuncCalls = (sourceFile) => {
219
- let modified = false;
227
+ const collectFuncCallReplacements = (sourceFile) => {
228
+ const replacements = [];
220
229
  sourceFile.forEachDescendant((node) => {
221
230
  if (node.getKind() !== ts_morph_1.SyntaxKind.CallExpression)
222
231
  return;
@@ -227,17 +236,34 @@ const transformFuncCalls = (sourceFile) => {
227
236
  if (args.length !== 1)
228
237
  return;
229
238
  // Replace func(fn) with just fn
230
- callExpr.replaceWithText(args[0].getText());
231
- modified = true;
239
+ replacements.push({
240
+ start: callExpr.getStart(),
241
+ end: callExpr.getEnd(),
242
+ content: args[0].getText()
243
+ });
232
244
  });
233
- return modified;
245
+ return replacements;
234
246
  };
235
- exports.transformFuncCalls = transformFuncCalls;
247
+ exports.collectFuncCallReplacements = collectFuncCallReplacements;
248
+ // Apply collected replacements using MagicString for sourcemap support
249
+ const applyReplacementsWithMap = (source, replacements, filePath) => {
250
+ const s = new magic_string_1.default(source);
251
+ // Sort replacements by start position descending to preserve positions
252
+ const sorted = [...replacements].sort((a, b) => b.start - a.start);
253
+ for (const { start, end, content } of sorted) {
254
+ s.overwrite(start, end, content);
255
+ }
256
+ return {
257
+ code: s.toString(),
258
+ map: s.generateMap({ source: filePath, includeContent: true, hires: true })
259
+ };
260
+ };
261
+ exports.applyReplacementsWithMap = applyReplacementsWithMap;
236
262
  const appendRegistryCode = (sourceFile, fileRegistry) => {
237
263
  // Generate registration code for each server function.
238
264
  // We set requireContext based on compile-time analysis of the function's first parameter type.
239
265
  // If the first param is context<T>, requireContext = true, otherwise false.
240
- // This allows execFunc() at runtime to know whether to inject the context as the first argument.
266
+ // This allows handle() at runtime to know whether to inject the context as the first argument.
241
267
  const registrations = Array.from(fileRegistry.values())
242
268
  .map(info => `globalThis.${exports.ZERO_COM_SERVER_REGISTRY}['${info.funcId}'] = ${info.exportName};\n` +
243
269
  `${info.exportName}.requireContext = ${info.requireContext};`)
@@ -247,7 +273,8 @@ if (!globalThis.${exports.ZERO_COM_SERVER_REGISTRY}) globalThis.${exports.ZERO_C
247
273
  ${registrations}`;
248
274
  };
249
275
  exports.appendRegistryCode = appendRegistryCode;
250
- const transformSourceFile = (filePath, content, registry) => {
276
+ const transformSourceFile = (filePath, content, registry, options = {}) => {
277
+ const { development = true } = options;
251
278
  const project = (0, exports.createProject)();
252
279
  const sourceFile = project.createSourceFile(filePath, content, { overwrite: true });
253
280
  const fileRegistry = registry.get(filePath);
@@ -257,17 +284,34 @@ const transformSourceFile = (filePath, content, registry) => {
257
284
  if (isServerFunctionFile) {
258
285
  fileRegistry.forEach((_, name) => importedFuncs.delete(name));
259
286
  }
260
- const callsTransformed = (0, exports.transformCallSites)(sourceFile, importedFuncs);
261
- const handleTransformed = (0, exports.transformHandleCalls)(sourceFile);
262
- const sendTransformed = (0, exports.transformSendCalls)(sourceFile);
263
- // Transform func() calls in files that define server functions.
264
- // This strips the func() wrapper since it's only needed for types.
287
+ // Collect all replacements
288
+ const replacements = [];
289
+ // Always collect client call site replacements (needed in both dev and prod)
290
+ replacements.push(...(0, exports.collectCallSiteReplacements)(sourceFile, importedFuncs));
291
+ // In production mode, collect handle(), call(), and func() replacements.
292
+ // In development mode, these work at runtime via ZERO_COM_DEV_MODE flag.
293
+ if (!development) {
294
+ replacements.push(...(0, exports.collectHandleCallReplacements)(sourceFile));
295
+ replacements.push(...(0, exports.collectSendCallReplacements)(sourceFile));
296
+ }
297
+ // Handle server function files
265
298
  if (isServerFunctionFile) {
266
- (0, exports.transformFuncCalls)(sourceFile);
299
+ // In production mode, strip func() wrappers
300
+ if (!development) {
301
+ replacements.push(...(0, exports.collectFuncCallReplacements)(sourceFile));
302
+ }
303
+ // Apply replacements and append registry code
304
+ if (replacements.length > 0) {
305
+ const { code, map } = (0, exports.applyReplacementsWithMap)(content, replacements, filePath);
306
+ // Create a new source file from the transformed code to append registry
307
+ const transformedSourceFile = project.createSourceFile(filePath + '.transformed', code, { overwrite: true });
308
+ return { content: (0, exports.appendRegistryCode)(transformedSourceFile, fileRegistry), transformed: true, map };
309
+ }
267
310
  return { content: (0, exports.appendRegistryCode)(sourceFile, fileRegistry), transformed: true };
268
311
  }
269
- if (callsTransformed || handleTransformed || sendTransformed) {
270
- return { content: sourceFile.getFullText(), transformed: true };
312
+ if (replacements.length > 0) {
313
+ const { code, map } = (0, exports.applyReplacementsWithMap)(content, replacements, filePath);
314
+ return { content: code, transformed: true, map };
271
315
  }
272
316
  return { content, transformed: false };
273
317
  };
package/lib/rollup.js CHANGED
@@ -42,15 +42,16 @@ function zeroComRollupPlugin(options = {}) {
42
42
  });
43
43
  },
44
44
  load(id) {
45
- var _a, _b;
45
+ var _a, _b, _c;
46
46
  if (!((_b = (_a = this.getModuleInfo(id)) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.needsTransform) || !fs_1.default.existsSync(id))
47
47
  return null;
48
48
  const content = fs_1.default.readFileSync(id, 'utf8');
49
- const result = (0, common_1.transformSourceFile)(id, content, registry);
49
+ const result = (0, common_1.transformSourceFile)(id, content, registry, { development });
50
50
  if (!result.transformed)
51
51
  return null;
52
52
  console.log(`[ZeroComRollupPlugin] Transformed: ${path_1.default.relative(process.cwd(), id)}`);
53
- return (0, common_1.emitToJs)(id, result.content);
53
+ const code = (0, common_1.emitToJs)(id, result.content);
54
+ return { code, map: (_c = result.map) !== null && _c !== void 0 ? _c : null };
54
55
  },
55
56
  renderChunk(code) {
56
57
  if (development)
package/lib/runtime.d.ts CHANGED
@@ -10,6 +10,6 @@ export type context<T = unknown> = T & {
10
10
  };
11
11
  type RemoveContextParam<F> = F extends (ctx: infer C, ...args: infer A) => infer R ? C extends context<unknown> ? (...args: A) => R : F : F;
12
12
  export declare function func<F extends (...args: any[]) => any>(fn: F): RemoveContextParam<F>;
13
- export declare const handle: (_funcId: string, _ctx: any, _args: any[]) => any;
14
- export declare const call: (_fn: (funcId: string, args: any[]) => Promise<any>) => void;
13
+ export declare const handle: (funcId: string, ctx: any, args: any[]) => any;
14
+ export declare const call: (fn: (funcId: string, args: any[]) => Promise<any>) => void;
15
15
  export {};
package/lib/runtime.js CHANGED
@@ -3,19 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.call = exports.handle = void 0;
4
4
  exports.func = func;
5
5
  // Implementation
6
- // User-facing function - transformed by plugin to just the inner function.
7
- // The plugin also appends code to register the function and set requireContext based on
8
- // compile-time analysis of whether the first parameter is context<T>.
9
- function func(_fn) {
10
- throw new Error('func() was not transformed. Ensure the zero-com plugin is configured.');
6
+ // In development mode: works at runtime, returns the function as-is.
7
+ // In production mode: transformed by plugin to just the inner function.
8
+ // The plugin appends code to register the function and set requireContext.
9
+ function func(fn) {
10
+ return fn;
11
11
  }
12
- // User-facing function - transformed by plugin to inline code that checks requireContext and calls the function
13
- const handle = (_funcId, _ctx, _args) => {
14
- throw new Error('handle() was not transformed. Ensure the zero-com plugin is configured.');
12
+ // In development mode: works at runtime, looks up function and calls it.
13
+ // In production mode: transformed by plugin to inline code.
14
+ const handle = (funcId, ctx, args) => {
15
+ const fn = globalThis.ZERO_COM_SERVER_REGISTRY[funcId];
16
+ if (!fn) {
17
+ throw new Error(`Function not found in registry: ${funcId}`);
18
+ }
19
+ if (fn.requireContext) {
20
+ return fn(ctx, ...args);
21
+ }
22
+ else {
23
+ return fn(...args);
24
+ }
15
25
  };
16
26
  exports.handle = handle;
17
- // User-facing function - transformed by plugin to globalThis.ZERO_COM_CLIENT_CALL = fn
18
- const call = (_fn) => {
19
- throw new Error('call() was not transformed. Ensure the zero-com plugin is configured.');
27
+ // In development mode: works at runtime, sets the client call handler.
28
+ // In production mode: transformed by plugin to assignment.
29
+ const call = (fn) => {
30
+ globalThis.ZERO_COM_CLIENT_CALL = fn;
20
31
  };
21
32
  exports.call = call;
@@ -2,5 +2,6 @@ import type { LoaderContext } from 'webpack';
2
2
  import { ServerFuncRegistry } from './common';
3
3
  export interface ZeroComLoaderOptions {
4
4
  registry: ServerFuncRegistry;
5
+ development: boolean;
5
6
  }
6
- export default function zeroComLoader(this: LoaderContext<ZeroComLoaderOptions>, source: string): string;
7
+ export default function zeroComLoader(this: LoaderContext<ZeroComLoaderOptions>, source: string): void;
@@ -9,10 +9,13 @@ const common_1 = require("./common");
9
9
  function zeroComLoader(source) {
10
10
  const options = this.getOptions();
11
11
  const filePath = this.resourcePath;
12
- const result = (0, common_1.transformSourceFile)(filePath, source, options.registry);
12
+ const result = (0, common_1.transformSourceFile)(filePath, source, options.registry, {
13
+ development: options.development
14
+ });
13
15
  if (!result.transformed) {
14
- return source;
16
+ this.callback(null, source);
17
+ return;
15
18
  }
16
19
  console.log(`[ZeroComWebpackPlugin] Transformed: ${path_1.default.basename(filePath)}`);
17
- return result.content;
20
+ this.callback(null, result.content, result.map);
18
21
  }
package/lib/webpack.js CHANGED
@@ -24,7 +24,7 @@ class ZeroComWebpackPlugin {
24
24
  enforce: 'pre',
25
25
  use: [{
26
26
  loader: loaderPath,
27
- options: { registry: this.registry }
27
+ options: { registry: this.registry, development: this.options.development }
28
28
  }]
29
29
  };
30
30
  compiler.options.module.rules.unshift(loaderRule);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-com",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "main": "index.js",
5
5
  "repository": "https://github.com/yosbelms/zero-com",
6
6
  "keywords": [
@@ -22,6 +22,7 @@
22
22
  "license": "MIT",
23
23
  "description": "",
24
24
  "dependencies": {
25
+ "magic-string": "^0.30.21",
25
26
  "minimatch": "^10.0.1",
26
27
  "ts-morph": "^26.0.0"
27
28
  },