@noego/wood 0.4.6 → 0.4.8

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 (243) hide show
  1. package/bin/wood.js +6 -1
  2. package/dist/client/app_env.cjs +39 -0
  3. package/dist/client/app_env.cjs.map +1 -0
  4. package/dist/client/app_env.d.cts +7 -0
  5. package/dist/client/app_env.d.ts +7 -0
  6. package/dist/client/app_env.js +14 -0
  7. package/dist/client/app_env.js.map +1 -0
  8. package/dist/client/index.cjs +33 -9
  9. package/dist/client/index.cjs.map +1 -1
  10. package/dist/client/index.d.cts +5 -1
  11. package/dist/client/index.d.ts +5 -1
  12. package/dist/client/index.js +32 -9
  13. package/dist/client/index.js.map +1 -1
  14. package/dist/codegen/channel_map_generator.cjs +102 -0
  15. package/dist/codegen/channel_map_generator.cjs.map +1 -0
  16. package/dist/codegen/channel_map_generator.d.cts +6 -0
  17. package/dist/codegen/channel_map_generator.d.ts +6 -0
  18. package/dist/codegen/channel_map_generator.js +78 -0
  19. package/dist/codegen/channel_map_generator.js.map +1 -0
  20. package/dist/codegen/controller_registration_generator.cjs +6 -1
  21. package/dist/codegen/controller_registration_generator.cjs.map +1 -1
  22. package/dist/codegen/controller_registration_generator.js +6 -1
  23. package/dist/codegen/controller_registration_generator.js.map +1 -1
  24. package/dist/codegen/generated_file_notice.cjs +40 -0
  25. package/dist/codegen/generated_file_notice.cjs.map +1 -0
  26. package/dist/codegen/generated_file_notice.d.cts +3 -0
  27. package/dist/codegen/generated_file_notice.d.ts +3 -0
  28. package/dist/codegen/generated_file_notice.js +16 -0
  29. package/dist/codegen/generated_file_notice.js.map +1 -0
  30. package/dist/codegen/index.cjs +3 -0
  31. package/dist/codegen/index.cjs.map +1 -1
  32. package/dist/codegen/index.d.cts +1 -0
  33. package/dist/codegen/index.d.ts +1 -0
  34. package/dist/codegen/index.js +2 -0
  35. package/dist/codegen/index.js.map +1 -1
  36. package/dist/codegen/preload_generator.cjs +97 -1
  37. package/dist/codegen/preload_generator.cjs.map +1 -1
  38. package/dist/codegen/preload_generator.js +97 -1
  39. package/dist/codegen/preload_generator.js.map +1 -1
  40. package/dist/codegen/renderer_app_generator.cjs +6 -1
  41. package/dist/codegen/renderer_app_generator.cjs.map +1 -1
  42. package/dist/codegen/renderer_app_generator.js +6 -1
  43. package/dist/codegen/renderer_app_generator.js.map +1 -1
  44. package/dist/codegen/renderer_controller_registration_generator.cjs +6 -1
  45. package/dist/codegen/renderer_controller_registration_generator.cjs.map +1 -1
  46. package/dist/codegen/renderer_controller_registration_generator.js +6 -1
  47. package/dist/codegen/renderer_controller_registration_generator.js.map +1 -1
  48. package/dist/codegen/renderer_view_manifest_generator.cjs +6 -1
  49. package/dist/codegen/renderer_view_manifest_generator.cjs.map +1 -1
  50. package/dist/codegen/renderer_view_manifest_generator.js +6 -1
  51. package/dist/codegen/renderer_view_manifest_generator.js.map +1 -1
  52. package/dist/codegen/runtime_generator.cjs +6 -1
  53. package/dist/codegen/runtime_generator.cjs.map +1 -1
  54. package/dist/codegen/runtime_generator.js +6 -1
  55. package/dist/codegen/runtime_generator.js.map +1 -1
  56. package/dist/codegen/test_mock_generator.cjs +5 -1
  57. package/dist/codegen/test_mock_generator.cjs.map +1 -1
  58. package/dist/codegen/test_mock_generator.js +5 -1
  59. package/dist/codegen/test_mock_generator.js.map +1 -1
  60. package/dist/codegen/type_generator.cjs +6 -1
  61. package/dist/codegen/type_generator.cjs.map +1 -1
  62. package/dist/codegen/type_generator.js +6 -1
  63. package/dist/codegen/type_generator.js.map +1 -1
  64. package/dist/index.cjs +19 -6
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.d.cts +7 -1
  67. package/dist/index.d.ts +7 -1
  68. package/dist/index.js +13 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/main/index.cjs +133 -55
  71. package/dist/main/index.cjs.map +1 -1
  72. package/dist/main/index.d.cts +30 -2
  73. package/dist/main/index.d.ts +30 -2
  74. package/dist/main/index.js +134 -55
  75. package/dist/main/index.js.map +1 -1
  76. package/dist/main/renderer_trace_drain_coordinator.cjs +127 -0
  77. package/dist/main/renderer_trace_drain_coordinator.cjs.map +1 -0
  78. package/dist/main/renderer_trace_drain_coordinator.d.cts +46 -0
  79. package/dist/main/renderer_trace_drain_coordinator.d.ts +46 -0
  80. package/dist/main/renderer_trace_drain_coordinator.js +101 -0
  81. package/dist/main/renderer_trace_drain_coordinator.js.map +1 -0
  82. package/dist/router/wood_router.cjs +9 -1
  83. package/dist/router/wood_router.cjs.map +1 -1
  84. package/dist/router/wood_router.d.cts +1 -1
  85. package/dist/router/wood_router.d.ts +1 -1
  86. package/dist/router/wood_router.js +9 -1
  87. package/dist/router/wood_router.js.map +1 -1
  88. package/dist/runtime/app_env.cjs +52 -0
  89. package/dist/runtime/app_env.cjs.map +1 -0
  90. package/dist/runtime/app_env.d.cts +10 -0
  91. package/dist/runtime/app_env.d.ts +10 -0
  92. package/dist/runtime/app_env.js +18 -0
  93. package/dist/runtime/app_env.js.map +1 -0
  94. package/dist/runtime/index.cjs +9 -2
  95. package/dist/runtime/index.cjs.map +1 -1
  96. package/dist/runtime/index.d.cts +5 -1
  97. package/dist/runtime/index.d.ts +5 -1
  98. package/dist/runtime/index.js +9 -1
  99. package/dist/runtime/index.js.map +1 -1
  100. package/dist/runtime/renderer_log_dispatcher.cjs.map +1 -1
  101. package/dist/runtime/renderer_log_dispatcher.d.cts +1 -1
  102. package/dist/runtime/renderer_log_dispatcher.d.ts +1 -1
  103. package/dist/runtime/renderer_log_dispatcher.js.map +1 -1
  104. package/dist/runtime/renderer_trace_batch_receiver_registry.cjs +50 -0
  105. package/dist/runtime/renderer_trace_batch_receiver_registry.cjs.map +1 -0
  106. package/dist/runtime/renderer_trace_batch_receiver_registry.d.cts +10 -0
  107. package/dist/runtime/renderer_trace_batch_receiver_registry.d.ts +10 -0
  108. package/dist/runtime/renderer_trace_batch_receiver_registry.js +24 -0
  109. package/dist/runtime/renderer_trace_batch_receiver_registry.js.map +1 -0
  110. package/dist/runtime/runtime.cjs +75 -57
  111. package/dist/runtime/runtime.cjs.map +1 -1
  112. package/dist/runtime/runtime.d.cts +8 -1
  113. package/dist/runtime/runtime.d.ts +8 -1
  114. package/dist/runtime/runtime.js +80 -58
  115. package/dist/runtime/runtime.js.map +1 -1
  116. package/dist/testing/browser.cjs +540 -11
  117. package/dist/testing/browser.cjs.map +1 -1
  118. package/dist/testing/browser.d.cts +219 -3
  119. package/dist/testing/browser.d.ts +219 -3
  120. package/dist/testing/browser.js +540 -11
  121. package/dist/testing/browser.js.map +1 -1
  122. package/dist/testing/create_controller_harness.d.cts +1 -1
  123. package/dist/testing/create_controller_harness.d.ts +1 -1
  124. package/dist/testing/index.cjs +13 -0
  125. package/dist/testing/index.cjs.map +1 -1
  126. package/dist/testing/index.d.cts +7 -2
  127. package/dist/testing/index.d.ts +7 -2
  128. package/dist/testing/index.js +14 -0
  129. package/dist/testing/index.js.map +1 -1
  130. package/dist/trace-testing/index.d.cts +1 -1
  131. package/dist/trace-testing/index.d.ts +1 -1
  132. package/dist/trace-testing/trace_recorder.d.cts +1 -1
  133. package/dist/trace-testing/trace_recorder.d.ts +1 -1
  134. package/dist/trace-transport/index.cjs +32 -0
  135. package/dist/trace-transport/index.cjs.map +1 -0
  136. package/dist/trace-transport/index.d.cts +6 -0
  137. package/dist/trace-transport/index.d.ts +6 -0
  138. package/dist/trace-transport/index.js +7 -0
  139. package/dist/trace-transport/index.js.map +1 -0
  140. package/dist/trace_hub-Cob54qRx.d.cts +56 -0
  141. package/dist/trace_hub-Cob54qRx.d.ts +56 -0
  142. package/dist/tracing/index.cjs +14 -0
  143. package/dist/tracing/index.cjs.map +1 -1
  144. package/dist/tracing/index.d.cts +7 -1
  145. package/dist/tracing/index.d.ts +7 -1
  146. package/dist/tracing/index.js +12 -0
  147. package/dist/tracing/index.js.map +1 -1
  148. package/dist/tracing/renderer_trace_batch_host.cjs +80 -0
  149. package/dist/tracing/renderer_trace_batch_host.cjs.map +1 -0
  150. package/dist/tracing/renderer_trace_batch_host.d.cts +16 -0
  151. package/dist/tracing/renderer_trace_batch_host.d.ts +16 -0
  152. package/dist/tracing/renderer_trace_batch_host.js +56 -0
  153. package/dist/tracing/renderer_trace_batch_host.js.map +1 -0
  154. package/dist/tracing/renderer_trace_batch_transport.cjs +313 -0
  155. package/dist/tracing/renderer_trace_batch_transport.cjs.map +1 -0
  156. package/dist/tracing/renderer_trace_batch_transport.d.cts +53 -0
  157. package/dist/tracing/renderer_trace_batch_transport.d.ts +53 -0
  158. package/dist/tracing/renderer_trace_batch_transport.js +292 -0
  159. package/dist/tracing/renderer_trace_batch_transport.js.map +1 -0
  160. package/dist/tracing/trace_commit_writer.cjs +159 -0
  161. package/dist/tracing/trace_commit_writer.cjs.map +1 -0
  162. package/dist/tracing/trace_commit_writer.d.cts +97 -0
  163. package/dist/tracing/trace_commit_writer.d.ts +97 -0
  164. package/dist/tracing/trace_commit_writer.js +135 -0
  165. package/dist/tracing/trace_commit_writer.js.map +1 -0
  166. package/dist/tracing/trace_hub.cjs +58 -3
  167. package/dist/tracing/trace_hub.cjs.map +1 -1
  168. package/dist/tracing/trace_hub.d.cts +2 -46
  169. package/dist/tracing/trace_hub.d.ts +2 -46
  170. package/dist/tracing/trace_hub.js +56 -3
  171. package/dist/tracing/trace_hub.js.map +1 -1
  172. package/dist/tracing/trace_provider.cjs +42 -12
  173. package/dist/tracing/trace_provider.cjs.map +1 -1
  174. package/dist/tracing/trace_provider.d.cts +15 -1
  175. package/dist/tracing/trace_provider.d.ts +15 -1
  176. package/dist/tracing/trace_provider.js +43 -12
  177. package/dist/tracing/trace_provider.js.map +1 -1
  178. package/dist/tracing/types/wood_trace_batch.types.cjs +17 -0
  179. package/dist/tracing/types/wood_trace_batch.types.cjs.map +1 -0
  180. package/dist/tracing/types/wood_trace_batch.types.d.cts +124 -0
  181. package/dist/tracing/types/wood_trace_batch.types.d.ts +124 -0
  182. package/dist/tracing/types/wood_trace_batch.types.js +1 -0
  183. package/dist/tracing/types/wood_trace_batch.types.js.map +1 -0
  184. package/dist/tracing/types/wood_trace_sink.types.cjs +17 -0
  185. package/dist/tracing/types/wood_trace_sink.types.cjs.map +1 -0
  186. package/dist/tracing/types/wood_trace_sink.types.d.cts +2 -0
  187. package/dist/tracing/types/wood_trace_sink.types.d.ts +2 -0
  188. package/dist/tracing/types/wood_trace_sink.types.js +1 -0
  189. package/dist/tracing/types/wood_trace_sink.types.js.map +1 -0
  190. package/dist/tracing/wood_trace_batch_contract.cjs +229 -0
  191. package/dist/tracing/wood_trace_batch_contract.cjs.map +1 -0
  192. package/dist/tracing/wood_trace_batch_contract.d.cts +23 -0
  193. package/dist/tracing/wood_trace_batch_contract.d.ts +23 -0
  194. package/dist/tracing/wood_trace_batch_contract.js +205 -0
  195. package/dist/tracing/wood_trace_batch_contract.js.map +1 -0
  196. package/dist/transport/build_transport_bridge.cjs +62 -0
  197. package/dist/transport/build_transport_bridge.cjs.map +1 -0
  198. package/dist/transport/build_transport_bridge.d.cts +29 -0
  199. package/dist/transport/build_transport_bridge.d.ts +29 -0
  200. package/dist/transport/build_transport_bridge.js +38 -0
  201. package/dist/transport/build_transport_bridge.js.map +1 -0
  202. package/dist/transport/in_process_router_transport.cjs +41 -0
  203. package/dist/transport/in_process_router_transport.cjs.map +1 -0
  204. package/dist/transport/in_process_router_transport.d.cts +31 -0
  205. package/dist/transport/in_process_router_transport.d.ts +31 -0
  206. package/dist/transport/in_process_router_transport.js +17 -0
  207. package/dist/transport/in_process_router_transport.js.map +1 -0
  208. package/dist/transport/index.cjs +42 -0
  209. package/dist/transport/index.cjs.map +1 -0
  210. package/dist/transport/index.d.cts +8 -0
  211. package/dist/transport/index.d.ts +8 -0
  212. package/dist/transport/index.js +19 -0
  213. package/dist/transport/index.js.map +1 -0
  214. package/dist/transport/loopback_remote_transport.cjs +270 -0
  215. package/dist/transport/loopback_remote_transport.cjs.map +1 -0
  216. package/dist/transport/loopback_remote_transport.d.cts +49 -0
  217. package/dist/transport/loopback_remote_transport.d.ts +49 -0
  218. package/dist/transport/loopback_remote_transport.js +237 -0
  219. package/dist/transport/loopback_remote_transport.js.map +1 -0
  220. package/dist/transport/loopback_serialization.cjs +116 -0
  221. package/dist/transport/loopback_serialization.cjs.map +1 -0
  222. package/dist/transport/loopback_serialization.d.cts +28 -0
  223. package/dist/transport/loopback_serialization.d.ts +28 -0
  224. package/dist/transport/loopback_serialization.js +91 -0
  225. package/dist/transport/loopback_serialization.js.map +1 -0
  226. package/dist/transport/transport_contract_suite.cjs +110 -0
  227. package/dist/transport/transport_contract_suite.cjs.map +1 -0
  228. package/dist/transport/transport_contract_suite.d.cts +23 -0
  229. package/dist/transport/transport_contract_suite.d.ts +23 -0
  230. package/dist/transport/transport_contract_suite.js +85 -0
  231. package/dist/transport/transport_contract_suite.js.map +1 -0
  232. package/dist/transport/wood_rpc_transport.cjs +17 -0
  233. package/dist/transport/wood_rpc_transport.cjs.map +1 -0
  234. package/dist/transport/wood_rpc_transport.d.cts +44 -0
  235. package/dist/transport/wood_rpc_transport.d.ts +44 -0
  236. package/dist/transport/wood_rpc_transport.js +1 -0
  237. package/dist/transport/wood_rpc_transport.js.map +1 -0
  238. package/docs/browser-testing.md +139 -1
  239. package/package.json +25 -4
  240. package/src/client/app_env.ts +21 -0
  241. package/src/components/NavigationShell.browser.test.ts +236 -0
  242. package/src/components/NavigationShell.svelte +162 -49
  243. package/src/runtime/app_env.ts +27 -0
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var channel_map_generator_exports = {};
20
+ __export(channel_map_generator_exports, {
21
+ generateChannelMap: () => generateChannelMap
22
+ });
23
+ module.exports = __toCommonJS(channel_map_generator_exports);
24
+ var import_generated_file_notice = require("./generated_file_notice.cjs");
25
+ const CHANNEL_MAP_SOURCE_INSTRUCTIONS = [
26
+ "To change IPC channels or their input/output types, edit the operations YAML configured by",
27
+ "wood.config.yml main.openapi and regenerate with `wood dev` or `wood build`."
28
+ ];
29
+ function operationHasInput(op) {
30
+ return Boolean(
31
+ op.inputSchema && op.inputSchema.type === "object" && Object.keys(
32
+ op.inputSchema.properties ?? {}
33
+ ).length > 0
34
+ );
35
+ }
36
+ function jsonSchemaToTS(schema, indent) {
37
+ if (!schema) return "unknown";
38
+ const type = schema.type;
39
+ if (type === "string") {
40
+ if (schema.enum) {
41
+ return schema.enum.map((v) => `'${v}'`).join(" | ");
42
+ }
43
+ return "string";
44
+ }
45
+ if (type === "number" || type === "integer") return "number";
46
+ if (type === "boolean") return "boolean";
47
+ if (type === "array") {
48
+ return `Array<${jsonSchemaToTS(schema.items, indent)}>`;
49
+ }
50
+ if (type === "object") {
51
+ const props = schema.properties ?? {};
52
+ const required = new Set(schema.required ?? []);
53
+ const entries = Object.entries(props);
54
+ if (entries.length === 0) return "Record<string, unknown>";
55
+ const lines = entries.map(([key, val]) => {
56
+ const optional = required.has(key) ? "" : "?";
57
+ const valType = jsonSchemaToTS(val, indent + " ");
58
+ const safeKey = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
59
+ return `${indent} ${safeKey}${optional}: ${valType};`;
60
+ });
61
+ return `{
62
+ ${lines.join("\n")}
63
+ ${indent} }`;
64
+ }
65
+ return "unknown";
66
+ }
67
+ function generateChannelMap(operations) {
68
+ const channels = Array.from(operations.values()).sort(
69
+ (a, b) => a.channel.localeCompare(b.channel)
70
+ );
71
+ const entries = channels.map((op) => {
72
+ const inputType = operationHasInput(op) ? jsonSchemaToTS(op.inputSchema, " ") : "void";
73
+ const outputType = jsonSchemaToTS(op.outputSchema, " ");
74
+ return ` ${JSON.stringify(op.channel)}: { input: ${inputType}; output: ${outputType} };`;
75
+ });
76
+ return `${(0, import_generated_file_notice.generatedFileNotice)(CHANNEL_MAP_SOURCE_INSTRUCTIONS)}
77
+ import type { TypedCall } from '@noego/wood/transport';
78
+
79
+ /**
80
+ * Channel -> { input, output } lookup, generated from the operations YAML.
81
+ * Inputless operations have \`input: void\`.
82
+ *
83
+ * Emitted as a \`type\` (not \`interface\`) so it satisfies the
84
+ * \`WoodChannelMapLike\` (= \`Record<string, WoodChannelContract>\`) constraint on
85
+ * \`TypedCall\`: an interface has no implicit index signature, a type alias does.
86
+ */
87
+ export type WoodChannelMap = {
88
+ ${entries.join("\n")}
89
+ };
90
+
91
+ /** All channel names as a string-literal union. */
92
+ export type WoodChannelName = keyof WoodChannelMap;
93
+
94
+ /** The typed \`call\` signature for the test harness, bound to this app's channels. */
95
+ export type WoodTypedCall = TypedCall<WoodChannelMap>;
96
+ `;
97
+ }
98
+ // Annotate the CommonJS export names for ESM import in node:
99
+ 0 && (module.exports = {
100
+ generateChannelMap
101
+ });
102
+ //# sourceMappingURL=channel_map_generator.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/codegen/channel_map_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.cjs';\nimport { generatedFileNotice } from './generated_file_notice.cjs';\n\nconst CHANNEL_MAP_SOURCE_INSTRUCTIONS = [\n 'To change IPC channels or their input/output types, edit the operations YAML configured by',\n 'wood.config.yml main.openapi and regenerate with `wood dev` or `wood build`.',\n] as const;\n\n/**\n * A boring, generated lookup interface mapping channel name -> { input, output }.\n *\n * This is the source of truth for the harness `call(channel, payload)` typing —\n * the same operations map that drives `ui/generated/wood.d.ts`. No conditional\n * type machinery: one flat interface literal keyed by channel string, consumed\n * by `WoodChannelInput`/`WoodChannelOutput`/`TypedCall` from `@noego/wood`.\n */\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys(\n (op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {},\n ).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n return `Array<${jsonSchemaToTS(schema.items as Record<string, unknown>, indent)}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n const safeKey = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n return `${indent} ${safeKey}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent} }`;\n }\n\n return 'unknown';\n}\n\n/** Generate the `WoodChannelMap` lookup interface plus its typed `call` alias. */\nexport function generateChannelMap(operations: Map<string, OperationDef>): string {\n const channels = Array.from(operations.values()).sort((a, b) =>\n a.channel.localeCompare(b.channel),\n );\n\n const entries = channels.map((op) => {\n const inputType = operationHasInput(op)\n ? jsonSchemaToTS(op.inputSchema as Record<string, unknown>, ' ')\n : 'void';\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, ' ');\n return ` ${JSON.stringify(op.channel)}: { input: ${inputType}; output: ${outputType} };`;\n });\n\n return `${generatedFileNotice(CHANNEL_MAP_SOURCE_INSTRUCTIONS)}\nimport type { TypedCall } from '@noego/wood/transport';\n\n/**\n * Channel -> { input, output } lookup, generated from the operations YAML.\n * Inputless operations have \\`input: void\\`.\n *\n * Emitted as a \\`type\\` (not \\`interface\\`) so it satisfies the\n * \\`WoodChannelMapLike\\` (= \\`Record<string, WoodChannelContract>\\`) constraint on\n * \\`TypedCall\\`: an interface has no implicit index signature, a type alias does.\n */\nexport type WoodChannelMap = {\n${entries.join('\\n')}\n};\n\n/** All channel names as a string-literal union. */\nexport type WoodChannelName = keyof WoodChannelMap;\n\n/** The typed \\`call\\` signature for the test harness, bound to this app's channels. */\nexport type WoodTypedCall = TypedCall<WoodChannelMap>;\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mCAAoC;AAEpC,MAAM,kCAAkC;AAAA,EACtC;AAAA,EACA;AACF;AAWA,SAAS,kBAAkB,IAA2B;AACpD,SAAO;AAAA,IACL,GAAG,eACA,GAAG,YAAwC,SAAS,YACrD,OAAO;AAAA,MACJ,GAAG,YAAwD,cAAc,CAAC;AAAA,IAC7E,EAAE,SAAS;AAAA,EACf;AACF;AAEA,SAAS,eAAe,QAA6C,QAAwB;AAC3F,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,OAAO,OAAO;AAEpB,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,MAAM;AACf,aAAQ,OAAO,KAAkB,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAAS,YAAY,SAAS,UAAW,QAAO;AACpD,MAAI,SAAS,UAAW,QAAO;AAC/B,MAAI,SAAS,SAAS;AACpB,WAAO,SAAS,eAAe,OAAO,OAAkC,MAAM,CAAC;AAAA,EACjF;AACA,MAAI,SAAS,UAAU;AACrB,UAAM,QAAS,OAAO,cAAc,CAAC;AACrC,UAAM,WAAW,IAAI,IAAK,OAAO,YAAyB,CAAC,CAAC;AAC5D,UAAM,UAAU,OAAO,QAAQ,KAAK;AACpC,QAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,UAAM,QAAQ,QAAQ,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AACxC,YAAM,WAAW,SAAS,IAAI,GAAG,IAAI,KAAK;AAC1C,YAAM,UAAU,eAAe,KAAK,SAAS,IAAI;AACjD,YAAM,UAAU,6BAA6B,KAAK,GAAG,IAAI,MAAM,KAAK,UAAU,GAAG;AACjF,aAAO,GAAG,MAAM,OAAO,OAAO,GAAG,QAAQ,KAAK,OAAO;AAAA,IACvD,CAAC;AACD,WAAO;AAAA,EAAM,MAAM,KAAK,IAAI,CAAC;AAAA,EAAK,MAAM;AAAA,EAC1C;AAEA,SAAO;AACT;AAGO,SAAS,mBAAmB,YAA+C;AAChF,QAAM,WAAW,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE;AAAA,IAAK,CAAC,GAAG,MACxD,EAAE,QAAQ,cAAc,EAAE,OAAO;AAAA,EACnC;AAEA,QAAM,UAAU,SAAS,IAAI,CAAC,OAAO;AACnC,UAAM,YAAY,kBAAkB,EAAE,IAClC,eAAe,GAAG,aAAwC,IAAI,IAC9D;AACJ,UAAM,aAAa,eAAe,GAAG,cAAyC,IAAI;AAClF,WAAO,KAAK,KAAK,UAAU,GAAG,OAAO,CAAC,cAAc,SAAS,aAAa,UAAU;AAAA,EACtF,CAAC;AAED,SAAO,OAAG,kDAAoB,+BAA+B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY9D,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASpB;","names":[]}
@@ -0,0 +1,6 @@
1
+ import { OperationDef } from '../types/operations.cjs';
2
+
3
+ /** Generate the `WoodChannelMap` lookup interface plus its typed `call` alias. */
4
+ declare function generateChannelMap(operations: Map<string, OperationDef>): string;
5
+
6
+ export { generateChannelMap };
@@ -0,0 +1,6 @@
1
+ import { OperationDef } from '../types/operations.js';
2
+
3
+ /** Generate the `WoodChannelMap` lookup interface plus its typed `call` alias. */
4
+ declare function generateChannelMap(operations: Map<string, OperationDef>): string;
5
+
6
+ export { generateChannelMap };
@@ -0,0 +1,78 @@
1
+ import { generatedFileNotice } from "./generated_file_notice.js";
2
+ const CHANNEL_MAP_SOURCE_INSTRUCTIONS = [
3
+ "To change IPC channels or their input/output types, edit the operations YAML configured by",
4
+ "wood.config.yml main.openapi and regenerate with `wood dev` or `wood build`."
5
+ ];
6
+ function operationHasInput(op) {
7
+ return Boolean(
8
+ op.inputSchema && op.inputSchema.type === "object" && Object.keys(
9
+ op.inputSchema.properties ?? {}
10
+ ).length > 0
11
+ );
12
+ }
13
+ function jsonSchemaToTS(schema, indent) {
14
+ if (!schema) return "unknown";
15
+ const type = schema.type;
16
+ if (type === "string") {
17
+ if (schema.enum) {
18
+ return schema.enum.map((v) => `'${v}'`).join(" | ");
19
+ }
20
+ return "string";
21
+ }
22
+ if (type === "number" || type === "integer") return "number";
23
+ if (type === "boolean") return "boolean";
24
+ if (type === "array") {
25
+ return `Array<${jsonSchemaToTS(schema.items, indent)}>`;
26
+ }
27
+ if (type === "object") {
28
+ const props = schema.properties ?? {};
29
+ const required = new Set(schema.required ?? []);
30
+ const entries = Object.entries(props);
31
+ if (entries.length === 0) return "Record<string, unknown>";
32
+ const lines = entries.map(([key, val]) => {
33
+ const optional = required.has(key) ? "" : "?";
34
+ const valType = jsonSchemaToTS(val, indent + " ");
35
+ const safeKey = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
36
+ return `${indent} ${safeKey}${optional}: ${valType};`;
37
+ });
38
+ return `{
39
+ ${lines.join("\n")}
40
+ ${indent} }`;
41
+ }
42
+ return "unknown";
43
+ }
44
+ function generateChannelMap(operations) {
45
+ const channels = Array.from(operations.values()).sort(
46
+ (a, b) => a.channel.localeCompare(b.channel)
47
+ );
48
+ const entries = channels.map((op) => {
49
+ const inputType = operationHasInput(op) ? jsonSchemaToTS(op.inputSchema, " ") : "void";
50
+ const outputType = jsonSchemaToTS(op.outputSchema, " ");
51
+ return ` ${JSON.stringify(op.channel)}: { input: ${inputType}; output: ${outputType} };`;
52
+ });
53
+ return `${generatedFileNotice(CHANNEL_MAP_SOURCE_INSTRUCTIONS)}
54
+ import type { TypedCall } from '@noego/wood/transport';
55
+
56
+ /**
57
+ * Channel -> { input, output } lookup, generated from the operations YAML.
58
+ * Inputless operations have \`input: void\`.
59
+ *
60
+ * Emitted as a \`type\` (not \`interface\`) so it satisfies the
61
+ * \`WoodChannelMapLike\` (= \`Record<string, WoodChannelContract>\`) constraint on
62
+ * \`TypedCall\`: an interface has no implicit index signature, a type alias does.
63
+ */
64
+ export type WoodChannelMap = {
65
+ ${entries.join("\n")}
66
+ };
67
+
68
+ /** All channel names as a string-literal union. */
69
+ export type WoodChannelName = keyof WoodChannelMap;
70
+
71
+ /** The typed \`call\` signature for the test harness, bound to this app's channels. */
72
+ export type WoodTypedCall = TypedCall<WoodChannelMap>;
73
+ `;
74
+ }
75
+ export {
76
+ generateChannelMap
77
+ };
78
+ //# sourceMappingURL=channel_map_generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/codegen/channel_map_generator.ts"],"sourcesContent":["import type { OperationDef } from '../types/index.js';\nimport { generatedFileNotice } from './generated_file_notice.js';\n\nconst CHANNEL_MAP_SOURCE_INSTRUCTIONS = [\n 'To change IPC channels or their input/output types, edit the operations YAML configured by',\n 'wood.config.yml main.openapi and regenerate with `wood dev` or `wood build`.',\n] as const;\n\n/**\n * A boring, generated lookup interface mapping channel name -> { input, output }.\n *\n * This is the source of truth for the harness `call(channel, payload)` typing —\n * the same operations map that drives `ui/generated/wood.d.ts`. No conditional\n * type machinery: one flat interface literal keyed by channel string, consumed\n * by `WoodChannelInput`/`WoodChannelOutput`/`TypedCall` from `@noego/wood`.\n */\n\nfunction operationHasInput(op: OperationDef): boolean {\n return Boolean(\n op.inputSchema &&\n (op.inputSchema as Record<string, unknown>).type === 'object' &&\n Object.keys(\n (op.inputSchema as Record<string, Record<string, unknown>>).properties ?? {},\n ).length > 0,\n );\n}\n\nfunction jsonSchemaToTS(schema: Record<string, unknown> | undefined, indent: string): string {\n if (!schema) return 'unknown';\n\n const type = schema.type as string | undefined;\n\n if (type === 'string') {\n if (schema.enum) {\n return (schema.enum as string[]).map((v) => `'${v}'`).join(' | ');\n }\n return 'string';\n }\n if (type === 'number' || type === 'integer') return 'number';\n if (type === 'boolean') return 'boolean';\n if (type === 'array') {\n return `Array<${jsonSchemaToTS(schema.items as Record<string, unknown>, indent)}>`;\n }\n if (type === 'object') {\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required as string[]) ?? []);\n const entries = Object.entries(props);\n if (entries.length === 0) return 'Record<string, unknown>';\n\n const lines = entries.map(([key, val]) => {\n const optional = required.has(key) ? '' : '?';\n const valType = jsonSchemaToTS(val, indent + ' ');\n const safeKey = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n return `${indent} ${safeKey}${optional}: ${valType};`;\n });\n return `{\\n${lines.join('\\n')}\\n${indent} }`;\n }\n\n return 'unknown';\n}\n\n/** Generate the `WoodChannelMap` lookup interface plus its typed `call` alias. */\nexport function generateChannelMap(operations: Map<string, OperationDef>): string {\n const channels = Array.from(operations.values()).sort((a, b) =>\n a.channel.localeCompare(b.channel),\n );\n\n const entries = channels.map((op) => {\n const inputType = operationHasInput(op)\n ? jsonSchemaToTS(op.inputSchema as Record<string, unknown>, ' ')\n : 'void';\n const outputType = jsonSchemaToTS(op.outputSchema as Record<string, unknown>, ' ');\n return ` ${JSON.stringify(op.channel)}: { input: ${inputType}; output: ${outputType} };`;\n });\n\n return `${generatedFileNotice(CHANNEL_MAP_SOURCE_INSTRUCTIONS)}\nimport type { TypedCall } from '@noego/wood/transport';\n\n/**\n * Channel -> { input, output } lookup, generated from the operations YAML.\n * Inputless operations have \\`input: void\\`.\n *\n * Emitted as a \\`type\\` (not \\`interface\\`) so it satisfies the\n * \\`WoodChannelMapLike\\` (= \\`Record<string, WoodChannelContract>\\`) constraint on\n * \\`TypedCall\\`: an interface has no implicit index signature, a type alias does.\n */\nexport type WoodChannelMap = {\n${entries.join('\\n')}\n};\n\n/** All channel names as a string-literal union. */\nexport type WoodChannelName = keyof WoodChannelMap;\n\n/** The typed \\`call\\` signature for the test harness, bound to this app's channels. */\nexport type WoodTypedCall = TypedCall<WoodChannelMap>;\n`;\n}\n"],"mappings":"AACA,SAAS,2BAA2B;AAEpC,MAAM,kCAAkC;AAAA,EACtC;AAAA,EACA;AACF;AAWA,SAAS,kBAAkB,IAA2B;AACpD,SAAO;AAAA,IACL,GAAG,eACA,GAAG,YAAwC,SAAS,YACrD,OAAO;AAAA,MACJ,GAAG,YAAwD,cAAc,CAAC;AAAA,IAC7E,EAAE,SAAS;AAAA,EACf;AACF;AAEA,SAAS,eAAe,QAA6C,QAAwB;AAC3F,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,OAAO,OAAO;AAEpB,MAAI,SAAS,UAAU;AACrB,QAAI,OAAO,MAAM;AACf,aAAQ,OAAO,KAAkB,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK;AAAA,IAClE;AACA,WAAO;AAAA,EACT;AACA,MAAI,SAAS,YAAY,SAAS,UAAW,QAAO;AACpD,MAAI,SAAS,UAAW,QAAO;AAC/B,MAAI,SAAS,SAAS;AACpB,WAAO,SAAS,eAAe,OAAO,OAAkC,MAAM,CAAC;AAAA,EACjF;AACA,MAAI,SAAS,UAAU;AACrB,UAAM,QAAS,OAAO,cAAc,CAAC;AACrC,UAAM,WAAW,IAAI,IAAK,OAAO,YAAyB,CAAC,CAAC;AAC5D,UAAM,UAAU,OAAO,QAAQ,KAAK;AACpC,QAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,UAAM,QAAQ,QAAQ,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM;AACxC,YAAM,WAAW,SAAS,IAAI,GAAG,IAAI,KAAK;AAC1C,YAAM,UAAU,eAAe,KAAK,SAAS,IAAI;AACjD,YAAM,UAAU,6BAA6B,KAAK,GAAG,IAAI,MAAM,KAAK,UAAU,GAAG;AACjF,aAAO,GAAG,MAAM,OAAO,OAAO,GAAG,QAAQ,KAAK,OAAO;AAAA,IACvD,CAAC;AACD,WAAO;AAAA,EAAM,MAAM,KAAK,IAAI,CAAC;AAAA,EAAK,MAAM;AAAA,EAC1C;AAEA,SAAO;AACT;AAGO,SAAS,mBAAmB,YAA+C;AAChF,QAAM,WAAW,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE;AAAA,IAAK,CAAC,GAAG,MACxD,EAAE,QAAQ,cAAc,EAAE,OAAO;AAAA,EACnC;AAEA,QAAM,UAAU,SAAS,IAAI,CAAC,OAAO;AACnC,UAAM,YAAY,kBAAkB,EAAE,IAClC,eAAe,GAAG,aAAwC,IAAI,IAC9D;AACJ,UAAM,aAAa,eAAe,GAAG,cAAyC,IAAI;AAClF,WAAO,KAAK,KAAK,UAAU,GAAG,OAAO,CAAC,cAAc,SAAS,aAAa,UAAU;AAAA,EACtF,CAAC;AAED,SAAO,GAAG,oBAAoB,+BAA+B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY9D,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASpB;","names":[]}
@@ -33,6 +33,11 @@ __export(controller_registration_generator_exports, {
33
33
  module.exports = __toCommonJS(controller_registration_generator_exports);
34
34
  var import_fs = require("fs");
35
35
  var import_path = __toESM(require("path"), 1);
36
+ var import_generated_file_notice = require("./generated_file_notice.cjs");
37
+ const CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS = [
38
+ "To change controller registration, edit the operations YAML configured by",
39
+ "wood.config.yml main.openapi and the matching controller files."
40
+ ];
36
41
  const CANDIDATE_SUFFIXES = [
37
42
  ".ts",
38
43
  ".controller.ts",
@@ -102,7 +107,7 @@ Ensure each controller file exists with one of these suffixes: .ts, .controller.
102
107
  );
103
108
  }
104
109
  const lines = [
105
- "// AUTO-GENERATED by @noego/wood -- do not edit",
110
+ (0, import_generated_file_notice.generatedFileNotice)(CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS),
106
111
  "//",
107
112
  "// Static imports ensure all controllers are bundled and available at startup.",
108
113
  "// registerController() puts each class directly in the router's cache."
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/codegen/controller_registration_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { OperationDef } from '../types/index.cjs';\n\n/**\n * Candidate file extensions to search when resolving a controller name to a file.\n * Matches the order used by ControllerResolver.resolveClass().\n */\nconst CANDIDATE_SUFFIXES = [\n '.ts',\n '.controller.ts',\n '.js',\n '.controller.js',\n] as const;\n\nconst INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\n/**\n * Resolve a controller name to a file path on disk.\n * Returns the first candidate that exists, or undefined if none match.\n */\nfunction resolveControllerFile(controllerName: string, controllersDir: string): string | undefined {\n const basePath = path.resolve(controllersDir, controllerName);\n\n for (const suffix of CANDIDATE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) return candidate;\n }\n\n for (const indexFile of INDEX_CANDIDATES) {\n const candidate = path.resolve(controllersDir, controllerName, indexFile);\n if (existsSync(candidate)) return candidate;\n }\n\n return undefined;\n}\n\n/**\n * Compute a relative import path from the generated file's directory to the\n * resolved controller file. Strips `.ts` extensions since TypeScript imports\n * should not include them.\n */\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n // Ensure the path starts with ./ or ../\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n // Strip .ts extension (TypeScript convention)\n if (rel.endsWith('.ts')) {\n rel = rel.slice(0, -3);\n }\n\n // Normalize path separators to forward slashes (Windows compat)\n return rel.replace(/\\\\/g, '/');\n}\n\n/**\n * Convert a controller name to a valid PascalCase identifier for the import.\n * e.g. \"chat\" -> \"ChatController\", \"multi_agent\" -> \"MultiAgentController\"\n */\nfunction toControllerClassName(controllerName: string): string {\n const pascal = controllerName\n .split(/[_\\-.]/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return pascal + 'Controller';\n}\n\n/**\n * Extract all unique controller names from a set of parsed operations.\n */\nfunction extractControllerNames(operations: Map<string, OperationDef>): string[] {\n const names = new Set<string>();\n\n operations.forEach((op) => {\n names.add(op.controller);\n });\n\n return Array.from(names).sort();\n}\n\nexport interface ControllerRegistrationOptions {\n /** Directory where controller source files live */\n controllersDir: string;\n /** Directory where the generated file will be written */\n outDir: string;\n /** If true, unresolved controllers emit a warning comment instead of throwing. Default: false. */\n allowUnresolved?: boolean;\n}\n\n/**\n * Generate a TypeScript registration file that statically imports every\n * controller referenced in operations and exports a function to register\n * them with a WoodRouter.\n */\nexport function generateControllerRegistration(\n operations: Map<string, OperationDef>,\n options: ControllerRegistrationOptions,\n): string {\n const controllerNames = extractControllerNames(operations);\n const outDirResolved = path.resolve(options.outDir);\n\n const imports: string[] = [];\n const registrations: string[] = [];\n const unresolved: string[] = [];\n\n for (const name of controllerNames) {\n const filePath = resolveControllerFile(name, options.controllersDir);\n\n if (!filePath) {\n unresolved.push(name);\n continue;\n }\n\n const importPath = computeImportPath(outDirResolved, filePath);\n const className = toControllerClassName(name);\n\n imports.push(`import ${className} from '${importPath}';`);\n registrations.push(` router.registerController('${name}', ${className});`);\n }\n\n // Fail by default when controllers cannot be resolved\n if (unresolved.length > 0 && !options.allowUnresolved) {\n throw new Error(\n `Controller registration codegen failed: ${unresolved.length} controller(s) not found on disk:\\n` +\n unresolved.map(name => ` - \"${name}\" (searched in ${options.controllersDir})`).join('\\n') +\n `\\n\\nEnsure each controller file exists with one of these suffixes: .ts, .controller.ts, .js, .controller.js`\n );\n }\n\n const lines: string[] = [\n '// AUTO-GENERATED by @noego/wood -- do not edit',\n '//',\n '// Static imports ensure all controllers are bundled and available at startup.',\n '// registerController() puts each class directly in the router\\'s cache.',\n ];\n\n if (unresolved.length > 0) {\n lines.push(\n '',\n '// WARNING: The following controllers could not be resolved on disk:',\n ...unresolved.map((name) => `// - ${name}`),\n );\n }\n\n // Static imports\n lines.push('');\n for (const imp of imports) {\n lines.push(imp);\n }\n\n // Registration function\n lines.push(\n '',\n 'export function registerControllers(router: { registerController(name: string, cls: any): void }) {',\n );\n for (const reg of registrations) {\n lines.push(reg);\n }\n lines.push(\n '}',\n '',\n );\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["path"]}
1
+ {"version":3,"sources":["../../src/codegen/controller_registration_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { OperationDef } from '../types/index.cjs';\nimport { generatedFileNotice } from './generated_file_notice.cjs';\n\nconst CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS = [\n 'To change controller registration, edit the operations YAML configured by',\n 'wood.config.yml main.openapi and the matching controller files.',\n] as const;\n\n/**\n * Candidate file extensions to search when resolving a controller name to a file.\n * Matches the order used by ControllerResolver.resolveClass().\n */\nconst CANDIDATE_SUFFIXES = [\n '.ts',\n '.controller.ts',\n '.js',\n '.controller.js',\n] as const;\n\nconst INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\n/**\n * Resolve a controller name to a file path on disk.\n * Returns the first candidate that exists, or undefined if none match.\n */\nfunction resolveControllerFile(controllerName: string, controllersDir: string): string | undefined {\n const basePath = path.resolve(controllersDir, controllerName);\n\n for (const suffix of CANDIDATE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) return candidate;\n }\n\n for (const indexFile of INDEX_CANDIDATES) {\n const candidate = path.resolve(controllersDir, controllerName, indexFile);\n if (existsSync(candidate)) return candidate;\n }\n\n return undefined;\n}\n\n/**\n * Compute a relative import path from the generated file's directory to the\n * resolved controller file. Strips `.ts` extensions since TypeScript imports\n * should not include them.\n */\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n // Ensure the path starts with ./ or ../\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n // Strip .ts extension (TypeScript convention)\n if (rel.endsWith('.ts')) {\n rel = rel.slice(0, -3);\n }\n\n // Normalize path separators to forward slashes (Windows compat)\n return rel.replace(/\\\\/g, '/');\n}\n\n/**\n * Convert a controller name to a valid PascalCase identifier for the import.\n * e.g. \"chat\" -> \"ChatController\", \"multi_agent\" -> \"MultiAgentController\"\n */\nfunction toControllerClassName(controllerName: string): string {\n const pascal = controllerName\n .split(/[_\\-.]/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return pascal + 'Controller';\n}\n\n/**\n * Extract all unique controller names from a set of parsed operations.\n */\nfunction extractControllerNames(operations: Map<string, OperationDef>): string[] {\n const names = new Set<string>();\n\n operations.forEach((op) => {\n names.add(op.controller);\n });\n\n return Array.from(names).sort();\n}\n\nexport interface ControllerRegistrationOptions {\n /** Directory where controller source files live */\n controllersDir: string;\n /** Directory where the generated file will be written */\n outDir: string;\n /** If true, unresolved controllers emit a warning comment instead of throwing. Default: false. */\n allowUnresolved?: boolean;\n}\n\n/**\n * Generate a TypeScript registration file that statically imports every\n * controller referenced in operations and exports a function to register\n * them with a WoodRouter.\n */\nexport function generateControllerRegistration(\n operations: Map<string, OperationDef>,\n options: ControllerRegistrationOptions,\n): string {\n const controllerNames = extractControllerNames(operations);\n const outDirResolved = path.resolve(options.outDir);\n\n const imports: string[] = [];\n const registrations: string[] = [];\n const unresolved: string[] = [];\n\n for (const name of controllerNames) {\n const filePath = resolveControllerFile(name, options.controllersDir);\n\n if (!filePath) {\n unresolved.push(name);\n continue;\n }\n\n const importPath = computeImportPath(outDirResolved, filePath);\n const className = toControllerClassName(name);\n\n imports.push(`import ${className} from '${importPath}';`);\n registrations.push(` router.registerController('${name}', ${className});`);\n }\n\n // Fail by default when controllers cannot be resolved\n if (unresolved.length > 0 && !options.allowUnresolved) {\n throw new Error(\n `Controller registration codegen failed: ${unresolved.length} controller(s) not found on disk:\\n` +\n unresolved.map(name => ` - \"${name}\" (searched in ${options.controllersDir})`).join('\\n') +\n `\\n\\nEnsure each controller file exists with one of these suffixes: .ts, .controller.ts, .js, .controller.js`\n );\n }\n\n const lines: string[] = [\n generatedFileNotice(CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS),\n '//',\n '// Static imports ensure all controllers are bundled and available at startup.',\n '// registerController() puts each class directly in the router\\'s cache.',\n ];\n\n if (unresolved.length > 0) {\n lines.push(\n '',\n '// WARNING: The following controllers could not be resolved on disk:',\n ...unresolved.map((name) => `// - ${name}`),\n );\n }\n\n // Static imports\n lines.push('');\n for (const imp of imports) {\n lines.push(imp);\n }\n\n // Registration function\n lines.push(\n '',\n 'export function registerControllers(router: { registerController(name: string, cls: any): void }) {',\n );\n for (const reg of registrations) {\n lines.push(reg);\n }\n lines.push(\n '}',\n '',\n );\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAA2B;AAC3B,kBAAiB;AAEjB,mCAAoC;AAEpC,MAAM,8CAA8C;AAAA,EAClD;AAAA,EACA;AACF;AAMA,MAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,mBAAmB;AAAA,EACvB;AAAA,EACA;AACF;AAMA,SAAS,sBAAsB,gBAAwB,gBAA4C;AACjG,QAAM,WAAW,YAAAA,QAAK,QAAQ,gBAAgB,cAAc;AAE5D,aAAW,UAAU,oBAAoB;AACvC,UAAM,YAAY,WAAW;AAC7B,YAAI,sBAAW,SAAS,EAAG,QAAO;AAAA,EACpC;AAEA,aAAW,aAAa,kBAAkB;AACxC,UAAM,YAAY,YAAAA,QAAK,QAAQ,gBAAgB,gBAAgB,SAAS;AACxE,YAAI,sBAAW,SAAS,EAAG,QAAO;AAAA,EACpC;AAEA,SAAO;AACT;AAOA,SAAS,kBAAkB,SAAiB,QAAwB;AAClE,MAAI,MAAM,YAAAA,QAAK,SAAS,SAAS,MAAM;AAGvC,MAAI,CAAC,IAAI,WAAW,GAAG,GAAG;AACxB,UAAM,OAAO;AAAA,EACf;AAGA,MAAI,IAAI,SAAS,KAAK,GAAG;AACvB,UAAM,IAAI,MAAM,GAAG,EAAE;AAAA,EACvB;AAGA,SAAO,IAAI,QAAQ,OAAO,GAAG;AAC/B;AAMA,SAAS,sBAAsB,gBAAgC;AAC7D,QAAM,SAAS,eACZ,MAAM,QAAQ,EACd,IAAI,CAAC,YAAY,QAAQ,OAAO,CAAC,EAAE,YAAY,IAAI,QAAQ,MAAM,CAAC,CAAC,EACnE,KAAK,EAAE;AAEV,SAAO,SAAS;AAClB;AAKA,SAAS,uBAAuB,YAAiD;AAC/E,QAAM,QAAQ,oBAAI,IAAY;AAE9B,aAAW,QAAQ,CAAC,OAAO;AACzB,UAAM,IAAI,GAAG,UAAU;AAAA,EACzB,CAAC;AAED,SAAO,MAAM,KAAK,KAAK,EAAE,KAAK;AAChC;AAgBO,SAAS,+BACd,YACA,SACQ;AACR,QAAM,kBAAkB,uBAAuB,UAAU;AACzD,QAAM,iBAAiB,YAAAA,QAAK,QAAQ,QAAQ,MAAM;AAElD,QAAM,UAAoB,CAAC;AAC3B,QAAM,gBAA0B,CAAC;AACjC,QAAM,aAAuB,CAAC;AAE9B,aAAW,QAAQ,iBAAiB;AAClC,UAAM,WAAW,sBAAsB,MAAM,QAAQ,cAAc;AAEnE,QAAI,CAAC,UAAU;AACb,iBAAW,KAAK,IAAI;AACpB;AAAA,IACF;AAEA,UAAM,aAAa,kBAAkB,gBAAgB,QAAQ;AAC7D,UAAM,YAAY,sBAAsB,IAAI;AAE5C,YAAQ,KAAK,UAAU,SAAS,UAAU,UAAU,IAAI;AACxD,kBAAc,KAAK,gCAAgC,IAAI,MAAM,SAAS,IAAI;AAAA,EAC5E;AAGA,MAAI,WAAW,SAAS,KAAK,CAAC,QAAQ,iBAAiB;AACrD,UAAM,IAAI;AAAA,MACR,2CAA2C,WAAW,MAAM;AAAA,IAC5D,WAAW,IAAI,UAAQ,QAAQ,IAAI,kBAAkB,QAAQ,cAAc,GAAG,EAAE,KAAK,IAAI,IACzF;AAAA;AAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAkB;AAAA,QACtB,kDAAoB,2CAA2C;AAAA,IAC/D;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,GAAG,WAAW,IAAI,CAAC,SAAS,UAAU,IAAI,EAAE;AAAA,IAC9C;AAAA,EACF;AAGA,QAAM,KAAK,EAAE;AACb,aAAW,OAAO,SAAS;AACzB,UAAM,KAAK,GAAG;AAAA,EAChB;AAGA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AACA,aAAW,OAAO,eAAe;AAC/B,UAAM,KAAK,GAAG;AAAA,EAChB;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["path"]}
@@ -1,5 +1,10 @@
1
1
  import { existsSync } from "fs";
2
2
  import path from "path";
3
+ import { generatedFileNotice } from "./generated_file_notice.js";
4
+ const CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS = [
5
+ "To change controller registration, edit the operations YAML configured by",
6
+ "wood.config.yml main.openapi and the matching controller files."
7
+ ];
3
8
  const CANDIDATE_SUFFIXES = [
4
9
  ".ts",
5
10
  ".controller.ts",
@@ -69,7 +74,7 @@ Ensure each controller file exists with one of these suffixes: .ts, .controller.
69
74
  );
70
75
  }
71
76
  const lines = [
72
- "// AUTO-GENERATED by @noego/wood -- do not edit",
77
+ generatedFileNotice(CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS),
73
78
  "//",
74
79
  "// Static imports ensure all controllers are bundled and available at startup.",
75
80
  "// registerController() puts each class directly in the router's cache."
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/codegen/controller_registration_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { OperationDef } from '../types/index.js';\n\n/**\n * Candidate file extensions to search when resolving a controller name to a file.\n * Matches the order used by ControllerResolver.resolveClass().\n */\nconst CANDIDATE_SUFFIXES = [\n '.ts',\n '.controller.ts',\n '.js',\n '.controller.js',\n] as const;\n\nconst INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\n/**\n * Resolve a controller name to a file path on disk.\n * Returns the first candidate that exists, or undefined if none match.\n */\nfunction resolveControllerFile(controllerName: string, controllersDir: string): string | undefined {\n const basePath = path.resolve(controllersDir, controllerName);\n\n for (const suffix of CANDIDATE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) return candidate;\n }\n\n for (const indexFile of INDEX_CANDIDATES) {\n const candidate = path.resolve(controllersDir, controllerName, indexFile);\n if (existsSync(candidate)) return candidate;\n }\n\n return undefined;\n}\n\n/**\n * Compute a relative import path from the generated file's directory to the\n * resolved controller file. Strips `.ts` extensions since TypeScript imports\n * should not include them.\n */\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n // Ensure the path starts with ./ or ../\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n // Strip .ts extension (TypeScript convention)\n if (rel.endsWith('.ts')) {\n rel = rel.slice(0, -3);\n }\n\n // Normalize path separators to forward slashes (Windows compat)\n return rel.replace(/\\\\/g, '/');\n}\n\n/**\n * Convert a controller name to a valid PascalCase identifier for the import.\n * e.g. \"chat\" -> \"ChatController\", \"multi_agent\" -> \"MultiAgentController\"\n */\nfunction toControllerClassName(controllerName: string): string {\n const pascal = controllerName\n .split(/[_\\-.]/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return pascal + 'Controller';\n}\n\n/**\n * Extract all unique controller names from a set of parsed operations.\n */\nfunction extractControllerNames(operations: Map<string, OperationDef>): string[] {\n const names = new Set<string>();\n\n operations.forEach((op) => {\n names.add(op.controller);\n });\n\n return Array.from(names).sort();\n}\n\nexport interface ControllerRegistrationOptions {\n /** Directory where controller source files live */\n controllersDir: string;\n /** Directory where the generated file will be written */\n outDir: string;\n /** If true, unresolved controllers emit a warning comment instead of throwing. Default: false. */\n allowUnresolved?: boolean;\n}\n\n/**\n * Generate a TypeScript registration file that statically imports every\n * controller referenced in operations and exports a function to register\n * them with a WoodRouter.\n */\nexport function generateControllerRegistration(\n operations: Map<string, OperationDef>,\n options: ControllerRegistrationOptions,\n): string {\n const controllerNames = extractControllerNames(operations);\n const outDirResolved = path.resolve(options.outDir);\n\n const imports: string[] = [];\n const registrations: string[] = [];\n const unresolved: string[] = [];\n\n for (const name of controllerNames) {\n const filePath = resolveControllerFile(name, options.controllersDir);\n\n if (!filePath) {\n unresolved.push(name);\n continue;\n }\n\n const importPath = computeImportPath(outDirResolved, filePath);\n const className = toControllerClassName(name);\n\n imports.push(`import ${className} from '${importPath}';`);\n registrations.push(` router.registerController('${name}', ${className});`);\n }\n\n // Fail by default when controllers cannot be resolved\n if (unresolved.length > 0 && !options.allowUnresolved) {\n throw new Error(\n `Controller registration codegen failed: ${unresolved.length} controller(s) not found on disk:\\n` +\n unresolved.map(name => ` - \"${name}\" (searched in ${options.controllersDir})`).join('\\n') +\n `\\n\\nEnsure each controller file exists with one of these suffixes: .ts, .controller.ts, .js, .controller.js`\n );\n }\n\n const lines: string[] = [\n '// AUTO-GENERATED by @noego/wood -- do not edit',\n '//',\n '// Static imports ensure all controllers are bundled and available at startup.',\n '// registerController() puts each class directly in the router\\'s cache.',\n ];\n\n if (unresolved.length > 0) {\n lines.push(\n '',\n '// WARNING: The following controllers could not be resolved on disk:',\n ...unresolved.map((name) => `// - ${name}`),\n );\n }\n\n // Static imports\n lines.push('');\n for (const imp of imports) {\n lines.push(imp);\n }\n\n // Registration function\n lines.push(\n '',\n 'export function registerControllers(router: { registerController(name: string, cls: any): void }) {',\n );\n for (const reg of registrations) {\n lines.push(reg);\n }\n lines.push(\n '}',\n '',\n );\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/codegen/controller_registration_generator.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport path from 'path';\nimport type { OperationDef } from '../types/index.js';\nimport { generatedFileNotice } from './generated_file_notice.js';\n\nconst CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS = [\n 'To change controller registration, edit the operations YAML configured by',\n 'wood.config.yml main.openapi and the matching controller files.',\n] as const;\n\n/**\n * Candidate file extensions to search when resolving a controller name to a file.\n * Matches the order used by ControllerResolver.resolveClass().\n */\nconst CANDIDATE_SUFFIXES = [\n '.ts',\n '.controller.ts',\n '.js',\n '.controller.js',\n] as const;\n\nconst INDEX_CANDIDATES = [\n 'index.ts',\n 'index.js',\n] as const;\n\n/**\n * Resolve a controller name to a file path on disk.\n * Returns the first candidate that exists, or undefined if none match.\n */\nfunction resolveControllerFile(controllerName: string, controllersDir: string): string | undefined {\n const basePath = path.resolve(controllersDir, controllerName);\n\n for (const suffix of CANDIDATE_SUFFIXES) {\n const candidate = basePath + suffix;\n if (existsSync(candidate)) return candidate;\n }\n\n for (const indexFile of INDEX_CANDIDATES) {\n const candidate = path.resolve(controllersDir, controllerName, indexFile);\n if (existsSync(candidate)) return candidate;\n }\n\n return undefined;\n}\n\n/**\n * Compute a relative import path from the generated file's directory to the\n * resolved controller file. Strips `.ts` extensions since TypeScript imports\n * should not include them.\n */\nfunction computeImportPath(fromDir: string, toFile: string): string {\n let rel = path.relative(fromDir, toFile);\n\n // Ensure the path starts with ./ or ../\n if (!rel.startsWith('.')) {\n rel = './' + rel;\n }\n\n // Strip .ts extension (TypeScript convention)\n if (rel.endsWith('.ts')) {\n rel = rel.slice(0, -3);\n }\n\n // Normalize path separators to forward slashes (Windows compat)\n return rel.replace(/\\\\/g, '/');\n}\n\n/**\n * Convert a controller name to a valid PascalCase identifier for the import.\n * e.g. \"chat\" -> \"ChatController\", \"multi_agent\" -> \"MultiAgentController\"\n */\nfunction toControllerClassName(controllerName: string): string {\n const pascal = controllerName\n .split(/[_\\-.]/)\n .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))\n .join('');\n\n return pascal + 'Controller';\n}\n\n/**\n * Extract all unique controller names from a set of parsed operations.\n */\nfunction extractControllerNames(operations: Map<string, OperationDef>): string[] {\n const names = new Set<string>();\n\n operations.forEach((op) => {\n names.add(op.controller);\n });\n\n return Array.from(names).sort();\n}\n\nexport interface ControllerRegistrationOptions {\n /** Directory where controller source files live */\n controllersDir: string;\n /** Directory where the generated file will be written */\n outDir: string;\n /** If true, unresolved controllers emit a warning comment instead of throwing. Default: false. */\n allowUnresolved?: boolean;\n}\n\n/**\n * Generate a TypeScript registration file that statically imports every\n * controller referenced in operations and exports a function to register\n * them with a WoodRouter.\n */\nexport function generateControllerRegistration(\n operations: Map<string, OperationDef>,\n options: ControllerRegistrationOptions,\n): string {\n const controllerNames = extractControllerNames(operations);\n const outDirResolved = path.resolve(options.outDir);\n\n const imports: string[] = [];\n const registrations: string[] = [];\n const unresolved: string[] = [];\n\n for (const name of controllerNames) {\n const filePath = resolveControllerFile(name, options.controllersDir);\n\n if (!filePath) {\n unresolved.push(name);\n continue;\n }\n\n const importPath = computeImportPath(outDirResolved, filePath);\n const className = toControllerClassName(name);\n\n imports.push(`import ${className} from '${importPath}';`);\n registrations.push(` router.registerController('${name}', ${className});`);\n }\n\n // Fail by default when controllers cannot be resolved\n if (unresolved.length > 0 && !options.allowUnresolved) {\n throw new Error(\n `Controller registration codegen failed: ${unresolved.length} controller(s) not found on disk:\\n` +\n unresolved.map(name => ` - \"${name}\" (searched in ${options.controllersDir})`).join('\\n') +\n `\\n\\nEnsure each controller file exists with one of these suffixes: .ts, .controller.ts, .js, .controller.js`\n );\n }\n\n const lines: string[] = [\n generatedFileNotice(CONTROLLER_REGISTRATION_SOURCE_INSTRUCTIONS),\n '//',\n '// Static imports ensure all controllers are bundled and available at startup.',\n '// registerController() puts each class directly in the router\\'s cache.',\n ];\n\n if (unresolved.length > 0) {\n lines.push(\n '',\n '// WARNING: The following controllers could not be resolved on disk:',\n ...unresolved.map((name) => `// - ${name}`),\n );\n }\n\n // Static imports\n lines.push('');\n for (const imp of imports) {\n lines.push(imp);\n }\n\n // Registration function\n lines.push(\n '',\n 'export function registerControllers(router: { registerController(name: string, cls: any): void }) {',\n );\n for (const reg of registrations) {\n lines.push(reg);\n }\n lines.push(\n '}',\n '',\n );\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var generated_file_notice_exports = {};
20
+ __export(generated_file_notice_exports, {
21
+ generatedFileNotice: () => generatedFileNotice
22
+ });
23
+ module.exports = __toCommonJS(generated_file_notice_exports);
24
+ const BASE_NOTICE_LINES = [
25
+ "// AUTO-GENERATED by @noego/wood -- do not edit.",
26
+ "//",
27
+ "// Changes here are overwritten by `wood dev` and `wood build`.",
28
+ "// Update the source files that feed Wood codegen instead."
29
+ ];
30
+ function generatedFileNotice(sourceInstructions) {
31
+ return [
32
+ ...BASE_NOTICE_LINES,
33
+ ...sourceInstructions.map((instruction) => `// ${instruction}`)
34
+ ].join("\n");
35
+ }
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ generatedFileNotice
39
+ });
40
+ //# sourceMappingURL=generated_file_notice.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/codegen/generated_file_notice.ts"],"sourcesContent":["const BASE_NOTICE_LINES = [\n '// AUTO-GENERATED by @noego/wood -- do not edit.',\n '//',\n '// Changes here are overwritten by `wood dev` and `wood build`.',\n '// Update the source files that feed Wood codegen instead.',\n] as const;\n\nexport function generatedFileNotice(sourceInstructions: readonly string[]): string {\n return [\n ...BASE_NOTICE_LINES,\n ...sourceInstructions.map((instruction) => `// ${instruction}`),\n ].join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,oBAAoB,oBAA+C;AACjF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,mBAAmB,IAAI,CAAC,gBAAgB,MAAM,WAAW,EAAE;AAAA,EAChE,EAAE,KAAK,IAAI;AACb;","names":[]}
@@ -0,0 +1,3 @@
1
+ declare function generatedFileNotice(sourceInstructions: readonly string[]): string;
2
+
3
+ export { generatedFileNotice };
@@ -0,0 +1,3 @@
1
+ declare function generatedFileNotice(sourceInstructions: readonly string[]): string;
2
+
3
+ export { generatedFileNotice };
@@ -0,0 +1,16 @@
1
+ const BASE_NOTICE_LINES = [
2
+ "// AUTO-GENERATED by @noego/wood -- do not edit.",
3
+ "//",
4
+ "// Changes here are overwritten by `wood dev` and `wood build`.",
5
+ "// Update the source files that feed Wood codegen instead."
6
+ ];
7
+ function generatedFileNotice(sourceInstructions) {
8
+ return [
9
+ ...BASE_NOTICE_LINES,
10
+ ...sourceInstructions.map((instruction) => `// ${instruction}`)
11
+ ].join("\n");
12
+ }
13
+ export {
14
+ generatedFileNotice
15
+ };
16
+ //# sourceMappingURL=generated_file_notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/codegen/generated_file_notice.ts"],"sourcesContent":["const BASE_NOTICE_LINES = [\n '// AUTO-GENERATED by @noego/wood -- do not edit.',\n '//',\n '// Changes here are overwritten by `wood dev` and `wood build`.',\n '// Update the source files that feed Wood codegen instead.',\n] as const;\n\nexport function generatedFileNotice(sourceInstructions: readonly string[]): string {\n return [\n ...BASE_NOTICE_LINES,\n ...sourceInstructions.map((instruction) => `// ${instruction}`),\n ].join('\\n');\n}\n"],"mappings":"AAAA,MAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,oBAAoB,oBAA+C;AACjF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAG,mBAAmB,IAAI,CAAC,gBAAgB,MAAM,WAAW,EAAE;AAAA,EAChE,EAAE,KAAK,IAAI;AACb;","names":[]}
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var codegen_exports = {};
20
20
  __export(codegen_exports, {
21
+ generateChannelMap: () => import_channel_map_generator.generateChannelMap,
21
22
  generateControllerRegistration: () => import_controller_registration_generator.generateControllerRegistration,
22
23
  generatePreload: () => import_preload_generator.generatePreload,
23
24
  generateRendererApp: () => import_renderer_app_generator.generateRendererApp,
@@ -30,6 +31,7 @@ __export(codegen_exports, {
30
31
  module.exports = __toCommonJS(codegen_exports);
31
32
  var import_preload_generator = require("./preload_generator.cjs");
32
33
  var import_type_generator = require("./type_generator.cjs");
34
+ var import_channel_map_generator = require("./channel_map_generator.cjs");
33
35
  var import_test_mock_generator = require("./test_mock_generator.cjs");
34
36
  var import_controller_registration_generator = require("./controller_registration_generator.cjs");
35
37
  var import_runtime_generator = require("./runtime_generator.cjs");
@@ -38,6 +40,7 @@ var import_renderer_controller_registration_generator = require("./renderer_cont
38
40
  var import_renderer_app_generator = require("./renderer_app_generator.cjs");
39
41
  // Annotate the CommonJS export names for ESM import in node:
40
42
  0 && (module.exports = {
43
+ generateChannelMap,
41
44
  generateControllerRegistration,
42
45
  generatePreload,
43
46
  generateRendererApp,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/codegen/index.ts"],"sourcesContent":["export { generatePreload } from './preload_generator.cjs';\nexport { generateTypes } from './type_generator.cjs';\nexport { generateTestMock } from './test_mock_generator.cjs';\nexport { generateControllerRegistration } from './controller_registration_generator.cjs';\nexport type { ControllerRegistrationOptions } from './controller_registration_generator.cjs';\nexport { generateRuntime } from './runtime_generator.cjs';\nexport type { RuntimeGeneratorOptions } from './runtime_generator.cjs';\nexport { generateRendererViewManifest } from './renderer_view_manifest_generator.cjs';\nexport type { RendererViewManifestGeneratorOptions } from './renderer_view_manifest_generator.cjs';\nexport { generateRendererControllerRegistration } from './renderer_controller_registration_generator.cjs';\nexport type { RendererControllerRegistrationOptions } from './renderer_controller_registration_generator.cjs';\nexport { generateRendererApp } from './renderer_app_generator.cjs';\nexport type { RendererAppGeneratorOptions } from './renderer_app_generator.cjs';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAgC;AAChC,4BAA8B;AAC9B,iCAAiC;AACjC,+CAA+C;AAE/C,+BAAgC;AAEhC,8CAA6C;AAE7C,wDAAuD;AAEvD,oCAAoC;","names":[]}
1
+ {"version":3,"sources":["../../src/codegen/index.ts"],"sourcesContent":["export { generatePreload } from './preload_generator.cjs';\nexport { generateTypes } from './type_generator.cjs';\nexport { generateChannelMap } from './channel_map_generator.cjs';\nexport { generateTestMock } from './test_mock_generator.cjs';\nexport { generateControllerRegistration } from './controller_registration_generator.cjs';\nexport type { ControllerRegistrationOptions } from './controller_registration_generator.cjs';\nexport { generateRuntime } from './runtime_generator.cjs';\nexport type { RuntimeGeneratorOptions } from './runtime_generator.cjs';\nexport { generateRendererViewManifest } from './renderer_view_manifest_generator.cjs';\nexport type { RendererViewManifestGeneratorOptions } from './renderer_view_manifest_generator.cjs';\nexport { generateRendererControllerRegistration } from './renderer_controller_registration_generator.cjs';\nexport type { RendererControllerRegistrationOptions } from './renderer_controller_registration_generator.cjs';\nexport { generateRendererApp } from './renderer_app_generator.cjs';\nexport type { RendererAppGeneratorOptions } from './renderer_app_generator.cjs';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAgC;AAChC,4BAA8B;AAC9B,mCAAmC;AACnC,iCAAiC;AACjC,+CAA+C;AAE/C,+BAAgC;AAEhC,8CAA6C;AAE7C,wDAAuD;AAEvD,oCAAoC;","names":[]}
@@ -1,5 +1,6 @@
1
1
  export { generatePreload } from './preload_generator.cjs';
2
2
  export { generateTypes } from './type_generator.cjs';
3
+ export { generateChannelMap } from './channel_map_generator.cjs';
3
4
  export { generateTestMock } from './test_mock_generator.cjs';
4
5
  export { ControllerRegistrationOptions, generateControllerRegistration } from './controller_registration_generator.cjs';
5
6
  export { RuntimeGeneratorOptions, generateRuntime } from './runtime_generator.cjs';
@@ -1,5 +1,6 @@
1
1
  export { generatePreload } from './preload_generator.js';
2
2
  export { generateTypes } from './type_generator.js';
3
+ export { generateChannelMap } from './channel_map_generator.js';
3
4
  export { generateTestMock } from './test_mock_generator.js';
4
5
  export { ControllerRegistrationOptions, generateControllerRegistration } from './controller_registration_generator.js';
5
6
  export { RuntimeGeneratorOptions, generateRuntime } from './runtime_generator.js';
@@ -1,5 +1,6 @@
1
1
  import { generatePreload } from "./preload_generator.js";
2
2
  import { generateTypes } from "./type_generator.js";
3
+ import { generateChannelMap } from "./channel_map_generator.js";
3
4
  import { generateTestMock } from "./test_mock_generator.js";
4
5
  import { generateControllerRegistration } from "./controller_registration_generator.js";
5
6
  import { generateRuntime } from "./runtime_generator.js";
@@ -7,6 +8,7 @@ import { generateRendererViewManifest } from "./renderer_view_manifest_generator
7
8
  import { generateRendererControllerRegistration } from "./renderer_controller_registration_generator.js";
8
9
  import { generateRendererApp } from "./renderer_app_generator.js";
9
10
  export {
11
+ generateChannelMap,
10
12
  generateControllerRegistration,
11
13
  generatePreload,
12
14
  generateRendererApp,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/codegen/index.ts"],"sourcesContent":["export { generatePreload } from './preload_generator.js';\nexport { generateTypes } from './type_generator.js';\nexport { generateTestMock } from './test_mock_generator.js';\nexport { generateControllerRegistration } from './controller_registration_generator.js';\nexport type { ControllerRegistrationOptions } from './controller_registration_generator.js';\nexport { generateRuntime } from './runtime_generator.js';\nexport type { RuntimeGeneratorOptions } from './runtime_generator.js';\nexport { generateRendererViewManifest } from './renderer_view_manifest_generator.js';\nexport type { RendererViewManifestGeneratorOptions } from './renderer_view_manifest_generator.js';\nexport { generateRendererControllerRegistration } from './renderer_controller_registration_generator.js';\nexport type { RendererControllerRegistrationOptions } from './renderer_controller_registration_generator.js';\nexport { generateRendererApp } from './renderer_app_generator.js';\nexport type { RendererAppGeneratorOptions } from './renderer_app_generator.js';\n"],"mappings":"AAAA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,wBAAwB;AACjC,SAAS,sCAAsC;AAE/C,SAAS,uBAAuB;AAEhC,SAAS,oCAAoC;AAE7C,SAAS,8CAA8C;AAEvD,SAAS,2BAA2B;","names":[]}
1
+ {"version":3,"sources":["../../src/codegen/index.ts"],"sourcesContent":["export { generatePreload } from './preload_generator.js';\nexport { generateTypes } from './type_generator.js';\nexport { generateChannelMap } from './channel_map_generator.js';\nexport { generateTestMock } from './test_mock_generator.js';\nexport { generateControllerRegistration } from './controller_registration_generator.js';\nexport type { ControllerRegistrationOptions } from './controller_registration_generator.js';\nexport { generateRuntime } from './runtime_generator.js';\nexport type { RuntimeGeneratorOptions } from './runtime_generator.js';\nexport { generateRendererViewManifest } from './renderer_view_manifest_generator.js';\nexport type { RendererViewManifestGeneratorOptions } from './renderer_view_manifest_generator.js';\nexport { generateRendererControllerRegistration } from './renderer_controller_registration_generator.js';\nexport type { RendererControllerRegistrationOptions } from './renderer_controller_registration_generator.js';\nexport { generateRendererApp } from './renderer_app_generator.js';\nexport type { RendererAppGeneratorOptions } from './renderer_app_generator.js';\n"],"mappings":"AAAA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,sCAAsC;AAE/C,SAAS,uBAAuB;AAEhC,SAAS,oCAAoC;AAE7C,SAAS,8CAA8C;AAEvD,SAAS,2BAA2B;","names":[]}
@@ -21,6 +21,11 @@ __export(preload_generator_exports, {
21
21
  generatePreload: () => generatePreload
22
22
  });
23
23
  module.exports = __toCommonJS(preload_generator_exports);
24
+ var import_generated_file_notice = require("./generated_file_notice.cjs");
25
+ const PRELOAD_SOURCE_INSTRUCTIONS = [
26
+ "To change IPC channels or preload bridge methods, edit the operations YAML configured by",
27
+ "wood.config.yml main.openapi and regenerate with `wood dev` or `wood build`."
28
+ ];
24
29
  function operationHasInput(op) {
25
30
  return Boolean(
26
31
  op.inputSchema && op.inputSchema.type === "object" && Object.keys(op.inputSchema.properties ?? {}).length > 0
@@ -82,8 +87,15 @@ function generatePreload(operations) {
82
87
  const tree = buildNestedTree(operations);
83
88
  const operationsCode = generateTreeCode(tree);
84
89
  const rpcManifestCode = generateRpcManifestCode(operations);
85
- return `// AUTO-GENERATED by @noego/wood -- do not edit
90
+ return `${(0, import_generated_file_notice.generatedFileNotice)(PRELOAD_SOURCE_INSTRUCTIONS)}
86
91
  import { contextBridge, ipcRenderer } from 'electron';
92
+ import {
93
+ RendererTraceBatchTransport,
94
+ type WoodRendererTraceInput,
95
+ type WoodTraceBatch,
96
+ type WoodTraceBatchAcknowledgement,
97
+ type WoodTraceBatchPort,
98
+ } from '@noego/wood/trace-transport';
87
99
 
88
100
  ${rpcManifestCode}
89
101
 
@@ -152,6 +164,8 @@ ${operationsCode}
152
164
  // Trace transport via MessagePort (activated when main process sends a port)
153
165
  let __tracePort: MessagePort | null = null;
154
166
  const __traceBuffer: unknown[] = [];
167
+ const __TRACE_BUFFER_CAPACITY = 256;
168
+ let __traceDroppedCapacity = 0;
155
169
 
156
170
  function __traceFlush(): void {
157
171
  while (__traceBuffer.length > 0 && __tracePort) {
@@ -169,10 +183,92 @@ contextBridge.exposeInMainWorld('__NOEGO_TRACE__', {
169
183
  send: (data: unknown) => {
170
184
  if (__tracePort) {
171
185
  __tracePort.postMessage(data);
186
+ } else if (__traceBuffer.length >= __TRACE_BUFFER_CAPACITY) {
187
+ __traceDroppedCapacity += 1;
172
188
  } else {
173
189
  __traceBuffer.push(data);
174
190
  }
175
191
  },
192
+ status: () => ({
193
+ queuedEvents: __traceBuffer.length,
194
+ droppedCapacity: __traceDroppedCapacity,
195
+ }),
196
+ });
197
+
198
+ const __traceBatchProducerId = typeof globalThis.crypto?.randomUUID === 'function'
199
+ ? globalThis.crypto.randomUUID()
200
+ : \`wood-renderer-\${Date.now()}-\${Math.random().toString(16).slice(2)}\`;
201
+ let __traceBatchListener: ((message: unknown) => void) | null = null;
202
+
203
+ const __traceBatchPort: WoodTraceBatchPort = {
204
+ postMessage: (batch: WoodTraceBatch) => {
205
+ void ipcRenderer.invoke('__traceBatch', batch)
206
+ .then((acknowledgement: WoodTraceBatchAcknowledgement) => {
207
+ __traceBatchListener?.(acknowledgement);
208
+ })
209
+ .catch((error: unknown) => {
210
+ __traceBatchListener?.({
211
+ kind: 'retryable',
212
+ batchId: batch.batchId,
213
+ reason: error instanceof Error ? error.message : String(error),
214
+ retryAfterMs: 100,
215
+ });
216
+ });
217
+ },
218
+ subscribe: (listener: (message: unknown) => void) => {
219
+ __traceBatchListener = listener;
220
+ return () => {
221
+ if (__traceBatchListener === listener) {
222
+ __traceBatchListener = null;
223
+ }
224
+ };
225
+ },
226
+ close: () => {
227
+ __traceBatchListener = null;
228
+ },
229
+ };
230
+
231
+ const __traceBatchTransport = new RendererTraceBatchTransport({
232
+ producerId: __traceBatchProducerId,
233
+ maxQueuedEvents: 256,
234
+ maxBatchEvents: 32,
235
+ acknowledgementTimeoutMs: 1_000,
236
+ retryLimit: 3,
237
+ retryBaseDelayMs: 25,
238
+ now: () => Date.now(),
239
+ schedule: (work: () => void) => queueMicrotask(work),
240
+ delay: (durationMs: number) => new Promise((resolve) => setTimeout(resolve, durationMs)),
241
+ });
242
+ __traceBatchTransport.attachPort(__traceBatchPort);
243
+
244
+ contextBridge.exposeInMainWorld('__NOEGO_WOOD_TRACE_BATCH__', {
245
+ send: (event: WoodRendererTraceInput) => __traceBatchTransport.admit(event),
246
+ status: () => __traceBatchTransport.status(),
247
+ flush: (deadlineMs: number) => __traceBatchTransport.flush(deadlineMs),
248
+ close: (deadlineMs: number) => __traceBatchTransport.close(deadlineMs),
249
+ });
250
+
251
+ ipcRenderer.on('__woodTraceDrainRequest', (_event, payload: unknown) => {
252
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
253
+ return;
254
+ }
255
+ const requestId = Reflect.get(payload, 'requestId');
256
+ const deadlineMs = Reflect.get(payload, 'deadlineMs');
257
+ if (typeof requestId !== 'string'
258
+ || typeof deadlineMs !== 'number'
259
+ || !Number.isFinite(deadlineMs)) {
260
+ return;
261
+ }
262
+ void __traceBatchTransport.close(deadlineMs).finally(() => {
263
+ ipcRenderer.send('__woodTraceDrainAck', {
264
+ requestId,
265
+ status: __traceBatchTransport.status(),
266
+ });
267
+ });
268
+ });
269
+
270
+ globalThis.addEventListener?.('pagehide', () => {
271
+ void __traceBatchTransport.flush(Date.now() + 100);
176
272
  });
177
273
  `;
178
274
  }