@cordy/electro-generator 1.2.12 → 1.2.13

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 (2) hide show
  1. package/dist/index.mjs +28 -18
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -71,8 +71,9 @@ contextBridge.exposeInMainWorld("electro", ${featureEntries.length > 0 ? `{\n${f
71
71
  * Generate bridge type declarations for a specific view.
72
72
  * Makes `window.electro` type-safe in the renderer.
73
73
  */
74
- function generateBridgeTypes(viewName, features, policy) {
75
- const allowedFeatures = features.filter((f) => policy.canAccess(viewName, f.id));
74
+ function generateBridgeTypes(view, features, policy) {
75
+ const allowedFeatures = features.filter((f) => policy.canAccess(view.name, f.id));
76
+ const bridgeModulePath = join(dirname(view.__source), "bridge.gen.ts");
76
77
  const featureTypes = [];
77
78
  for (const feature of allowedFeatures) {
78
79
  const exposedServices = feature.services.filter((s) => s.scope === "exposed");
@@ -81,33 +82,42 @@ function generateBridgeTypes(viewName, features, policy) {
81
82
  continue;
82
83
  }
83
84
  const serviceTypes = [];
84
- for (const service of exposedServices) {
85
- if (service.methods.length === 0) {
86
- serviceTypes.push(` ${q(service.id)}: Record<string, never>;`);
87
- continue;
88
- }
89
- const methodTypes = service.methods.map((m) => ` ${q(m)}(...args: unknown[]): Promise<unknown>;`).join("\n");
90
- serviceTypes.push(` ${q(service.id)}: {\n${methodTypes}\n };`);
91
- }
85
+ for (const service of exposedServices) if (service.exported) {
86
+ const importPath = toImportPathFrom(bridgeModulePath, service.filePath);
87
+ serviceTypes.push(` ${q(service.id)}: _SvcApi<typeof import("${importPath}").${service.varName}>;`);
88
+ } else serviceTypes.push(` ${q(service.id)}: unknown;`);
92
89
  featureTypes.push(` ${q(feature.id)}: {\n${serviceTypes.join("\n")}\n };`);
93
90
  }
94
- const eventKeys = [];
95
- for (const feature of allowedFeatures) for (const evt of feature.events ?? []) eventKeys.push(`"${feature.id}:${evt.id}"`);
96
- if (allowedFeatures.length > 0) {
97
- const channelType = eventKeys.length > 0 ? eventKeys.join(" | ") : "string";
98
- featureTypes.push(` events: {\n on(channel: ${channelType} | (string & {}), handler: (payload: unknown) => void): () => void;\n };`);
91
+ const eventTypeEntries = [];
92
+ for (const feature of allowedFeatures) for (const evt of feature.events ?? []) {
93
+ const eventKey = `${feature.id}:${evt.id}`;
94
+ if (evt.exported) {
95
+ const importPath = toImportPathFrom(bridgeModulePath, evt.filePath);
96
+ eventTypeEntries.push(` ${JSON.stringify(eventKey)}: _EventPayload<typeof import("${importPath}").${evt.varName}>;`);
97
+ } else eventTypeEntries.push(` ${JSON.stringify(eventKey)}: unknown;`);
99
98
  }
99
+ const eventMapTypes = eventTypeEntries.length > 0 ? `\ntype ElectroEventMap = {\n${eventTypeEntries.join("\n")}\n};` : "";
100
+ if (allowedFeatures.length > 0) if (eventTypeEntries.length > 0) featureTypes.push(` events: {\n on<K extends keyof ElectroEventMap>(channel: K, handler: (payload: ElectroEventMap[K]) => void): () => void;\n on(channel: string & {}, handler: (payload: unknown) => void): () => void;\n };`);
101
+ else featureTypes.push(` events: {\n on(channel: string & {}, handler: (payload: unknown) => void): () => void;\n };`);
100
102
  const content = `${HEADER}
103
+ type _SvcApi<T> = T extends { api(): infer R } ? NonNullable<R> : never;
104
+ type _EventPayload<T> = T extends { payload(): infer P } ? P : unknown;${eventMapTypes}
105
+
101
106
  export interface ElectroBridge ${featureTypes.length > 0 ? `{\n${featureTypes.join("\n")}\n }` : "{}"}
102
107
  `;
103
108
  return {
104
- path: `generated/views/${viewName}.bridge.d.ts`,
109
+ path: `generated/views/${view.name}.bridge.d.ts`,
105
110
  content
106
111
  };
107
112
  }
108
113
  /** Calculate the import path from the generated env types file to a source file. */
114
+ function toImportPathFrom(outputFilePath, sourceFilePath) {
115
+ const importPath = relative(dirname(outputFilePath), sourceFilePath).replace(/\.(?:[cm]?tsx?|[cm]?jsx?)$/, "");
116
+ return importPath.startsWith(".") ? importPath : `./${importPath}`;
117
+ }
118
+ /** Calculate the import path from the generated env types file to a source file. */
109
119
  function toImportPath(envTypesDir, sourceFilePath) {
110
- const importPath = relative(dirname(join(envTypesDir, "electro-env.d.ts")), sourceFilePath).replace(/\.ts$/, "");
120
+ const importPath = relative(dirname(join(envTypesDir, "electro-env.d.ts")), sourceFilePath).replace(/\.(?:[cm]?tsx?|[cm]?jsx?)$/, "");
111
121
  return importPath.startsWith(".") ? importPath : `./${importPath}`;
112
122
  }
113
123
  const FEATURE_TYPES_HEADER = `// Auto-generated by Electro codegen. Do not edit.
@@ -232,7 +242,7 @@ function generate(input) {
232
242
  const knownIds = new Set(scanResult.features.map((f) => f.id));
233
243
  for (const fId of view.features ?? []) if (!knownIds.has(fId)) console.warn(`[generator] View "${view.name}" references unknown feature "${fId}"`);
234
244
  files.push(generatePreload(view.name, scanResult.features, policy, view.preload));
235
- files.push(generateBridgeTypes(view.name, scanResult.features, policy));
245
+ files.push(generateBridgeTypes(view, scanResult.features, policy));
236
246
  }
237
247
  return {
238
248
  files,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cordy/electro-generator",
3
- "version": "1.2.12",
3
+ "version": "1.2.13",
4
4
  "description": "Code generator for @cordy/electro — scans features and emits preload scripts and bridge types",
5
5
  "license": "MIT",
6
6
  "type": "module",