@danielfgray/pg-sourcerer 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/bin/pgsourcerer +2 -0
  2. package/dist/__tests__/fixtures/index.d.ts +15 -0
  3. package/dist/__tests__/fixtures/index.d.ts.map +1 -0
  4. package/dist/__tests__/fixtures/index.js +19 -0
  5. package/dist/__tests__/fixtures/index.js.map +1 -0
  6. package/dist/__tests__/fixtures/introspection.json +40522 -0
  7. package/dist/cli.d.ts +0 -1
  8. package/dist/cli.js +7 -46
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +38 -5
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +13 -2
  13. package/dist/config.js.map +1 -1
  14. package/dist/{lib/conjure.d.ts → conjure/index.d.ts} +62 -3
  15. package/dist/conjure/index.d.ts.map +1 -0
  16. package/dist/{lib/conjure.js → conjure/index.js} +124 -3
  17. package/dist/conjure/index.js.map +1 -0
  18. package/dist/conjure/signature.d.ts +85 -0
  19. package/dist/conjure/signature.d.ts.map +1 -0
  20. package/dist/conjure/signature.js +130 -0
  21. package/dist/conjure/signature.js.map +1 -0
  22. package/dist/conjure/types.d.ts +97 -0
  23. package/dist/conjure/types.d.ts.map +1 -0
  24. package/dist/conjure/types.js +206 -0
  25. package/dist/conjure/types.js.map +1 -0
  26. package/dist/errors.d.ts +114 -139
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +82 -36
  29. package/dist/errors.js.map +1 -1
  30. package/dist/generate.d.ts +45 -46
  31. package/dist/generate.d.ts.map +1 -1
  32. package/dist/generate.js +86 -59
  33. package/dist/generate.js.map +1 -1
  34. package/dist/hex/builder.d.ts +12 -0
  35. package/dist/hex/builder.d.ts.map +1 -0
  36. package/dist/hex/builder.js +64 -0
  37. package/dist/hex/builder.js.map +1 -0
  38. package/dist/hex/ddl.d.ts +53 -0
  39. package/dist/hex/ddl.d.ts.map +1 -0
  40. package/dist/hex/ddl.js +306 -0
  41. package/dist/hex/ddl.js.map +1 -0
  42. package/dist/hex/index.d.ts +105 -0
  43. package/dist/hex/index.d.ts.map +1 -0
  44. package/dist/hex/index.js +81 -0
  45. package/dist/hex/index.js.map +1 -0
  46. package/dist/hex/primitives.d.ts +23 -0
  47. package/dist/hex/primitives.d.ts.map +1 -0
  48. package/dist/hex/primitives.js +38 -0
  49. package/dist/hex/primitives.js.map +1 -0
  50. package/dist/hex/query.d.ts +116 -0
  51. package/dist/hex/query.d.ts.map +1 -0
  52. package/dist/hex/query.js +219 -0
  53. package/dist/hex/query.js.map +1 -0
  54. package/dist/hex/types.d.ts +287 -0
  55. package/dist/hex/types.d.ts.map +1 -0
  56. package/dist/hex/types.js +431 -0
  57. package/dist/hex/types.js.map +1 -0
  58. package/dist/index.d.ts +17 -25
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +33 -44
  61. package/dist/index.js.map +1 -1
  62. package/dist/init.d.ts.map +1 -1
  63. package/dist/init.js +76 -140
  64. package/dist/init.js.map +1 -1
  65. package/dist/ir/extensions/queries.d.ts +6 -6
  66. package/dist/ir/extensions/queries.d.ts.map +1 -1
  67. package/dist/ir/extensions/queries.js +6 -4
  68. package/dist/ir/extensions/queries.js.map +1 -1
  69. package/dist/ir/extensions/schema-builder.d.ts.map +1 -1
  70. package/dist/ir/extensions/schema-builder.js.map +1 -1
  71. package/dist/ir/index.d.ts.map +1 -1
  72. package/dist/ir/index.js.map +1 -1
  73. package/dist/ir/relation-graph.d.ts.map +1 -1
  74. package/dist/ir/relation-graph.js +8 -8
  75. package/dist/ir/relation-graph.js.map +1 -1
  76. package/dist/ir/semantic-ir.d.ts +38 -0
  77. package/dist/ir/semantic-ir.d.ts.map +1 -1
  78. package/dist/ir/semantic-ir.js +50 -2
  79. package/dist/ir/semantic-ir.js.map +1 -1
  80. package/dist/ir/smart-tags.d.ts.map +1 -1
  81. package/dist/ir/smart-tags.js.map +1 -1
  82. package/dist/lib/field-utils.d.ts.map +1 -1
  83. package/dist/lib/field-utils.js +7 -7
  84. package/dist/lib/field-utils.js.map +1 -1
  85. package/dist/lib/join-graph.d.ts +95 -0
  86. package/dist/lib/join-graph.d.ts.map +1 -0
  87. package/dist/lib/join-graph.js +305 -0
  88. package/dist/lib/join-graph.js.map +1 -0
  89. package/dist/lib/picker.d.ts +60 -0
  90. package/dist/lib/picker.d.ts.map +1 -0
  91. package/dist/lib/picker.js +325 -0
  92. package/dist/lib/picker.js.map +1 -0
  93. package/dist/plugins/arktype.d.ts +20 -24
  94. package/dist/plugins/arktype.d.ts.map +1 -1
  95. package/dist/plugins/arktype.js +462 -386
  96. package/dist/plugins/arktype.js.map +1 -1
  97. package/dist/plugins/effect/http.d.ts +7 -0
  98. package/dist/plugins/effect/http.d.ts.map +1 -0
  99. package/dist/plugins/effect/http.js +460 -0
  100. package/dist/plugins/effect/http.js.map +1 -0
  101. package/dist/plugins/effect/index.d.ts +22 -0
  102. package/dist/plugins/effect/index.d.ts.map +1 -0
  103. package/dist/plugins/effect/index.js +65 -0
  104. package/dist/plugins/effect/index.js.map +1 -0
  105. package/dist/plugins/effect/models.d.ts +6 -0
  106. package/dist/plugins/effect/models.d.ts.map +1 -0
  107. package/dist/plugins/effect/models.js +116 -0
  108. package/dist/plugins/effect/models.js.map +1 -0
  109. package/dist/plugins/effect/repos.d.ts +21 -0
  110. package/dist/plugins/effect/repos.d.ts.map +1 -0
  111. package/dist/plugins/effect/repos.js +131 -0
  112. package/dist/plugins/effect/repos.js.map +1 -0
  113. package/dist/plugins/effect/schemas.d.ts +7 -0
  114. package/dist/plugins/effect/schemas.d.ts.map +1 -0
  115. package/dist/plugins/effect/schemas.js +75 -0
  116. package/dist/plugins/effect/schemas.js.map +1 -0
  117. package/dist/plugins/effect/shared.d.ts +116 -0
  118. package/dist/plugins/effect/shared.d.ts.map +1 -0
  119. package/dist/plugins/effect/shared.js +164 -0
  120. package/dist/plugins/effect/shared.js.map +1 -0
  121. package/dist/plugins/http-elysia.d.ts +20 -27
  122. package/dist/plugins/http-elysia.d.ts.map +1 -1
  123. package/dist/plugins/http-elysia.js +350 -475
  124. package/dist/plugins/http-elysia.js.map +1 -1
  125. package/dist/plugins/http-express.d.ts +20 -31
  126. package/dist/plugins/http-express.d.ts.map +1 -1
  127. package/dist/plugins/http-express.js +281 -268
  128. package/dist/plugins/http-express.js.map +1 -1
  129. package/dist/plugins/http-hono.d.ts +17 -33
  130. package/dist/plugins/http-hono.d.ts.map +1 -1
  131. package/dist/plugins/http-hono.js +317 -341
  132. package/dist/plugins/http-hono.js.map +1 -1
  133. package/dist/plugins/http-orpc.d.ts +34 -33
  134. package/dist/plugins/http-orpc.d.ts.map +1 -1
  135. package/dist/plugins/http-orpc.js +345 -257
  136. package/dist/plugins/http-orpc.js.map +1 -1
  137. package/dist/plugins/http-trpc.d.ts +33 -35
  138. package/dist/plugins/http-trpc.d.ts.map +1 -1
  139. package/dist/plugins/http-trpc.js +337 -241
  140. package/dist/plugins/http-trpc.js.map +1 -1
  141. package/dist/plugins/kysely.d.ts +54 -59
  142. package/dist/plugins/kysely.d.ts.map +1 -1
  143. package/dist/plugins/kysely.js +826 -687
  144. package/dist/plugins/kysely.js.map +1 -1
  145. package/dist/plugins/sql-queries.d.ts +38 -44
  146. package/dist/plugins/sql-queries.d.ts.map +1 -1
  147. package/dist/plugins/sql-queries.js +497 -897
  148. package/dist/plugins/sql-queries.js.map +1 -1
  149. package/dist/plugins/types.d.ts +12 -20
  150. package/dist/plugins/types.d.ts.map +1 -1
  151. package/dist/plugins/types.js +84 -227
  152. package/dist/plugins/types.js.map +1 -1
  153. package/dist/plugins/valibot.d.ts +7 -44
  154. package/dist/plugins/valibot.d.ts.map +1 -1
  155. package/dist/plugins/valibot.js +376 -382
  156. package/dist/plugins/valibot.js.map +1 -1
  157. package/dist/plugins/zod.d.ts +20 -24
  158. package/dist/plugins/zod.d.ts.map +1 -1
  159. package/dist/plugins/zod.js +370 -367
  160. package/dist/plugins/zod.js.map +1 -1
  161. package/dist/runtime/emit.d.ts +64 -0
  162. package/dist/runtime/emit.d.ts.map +1 -0
  163. package/dist/runtime/emit.js +445 -0
  164. package/dist/runtime/emit.js.map +1 -0
  165. package/dist/runtime/errors.d.ts +36 -0
  166. package/dist/runtime/errors.d.ts.map +1 -0
  167. package/dist/runtime/errors.js +29 -0
  168. package/dist/runtime/errors.js.map +1 -0
  169. package/dist/runtime/file-assignment.d.ts +161 -0
  170. package/dist/runtime/file-assignment.d.ts.map +1 -0
  171. package/dist/runtime/file-assignment.js +195 -0
  172. package/dist/runtime/file-assignment.js.map +1 -0
  173. package/dist/runtime/orchestrator.d.ts +62 -0
  174. package/dist/runtime/orchestrator.d.ts.map +1 -0
  175. package/dist/runtime/orchestrator.js +99 -0
  176. package/dist/runtime/orchestrator.js.map +1 -0
  177. package/dist/runtime/registry.d.ts +268 -0
  178. package/dist/runtime/registry.d.ts.map +1 -0
  179. package/dist/runtime/registry.js +436 -0
  180. package/dist/runtime/registry.js.map +1 -0
  181. package/dist/runtime/types.d.ts +182 -0
  182. package/dist/runtime/types.d.ts.map +1 -0
  183. package/dist/runtime/types.js +2 -0
  184. package/dist/runtime/types.js.map +1 -0
  185. package/dist/runtime/validation.d.ts +41 -0
  186. package/dist/runtime/validation.d.ts.map +1 -0
  187. package/dist/runtime/validation.js +70 -0
  188. package/dist/runtime/validation.js.map +1 -0
  189. package/dist/services/config-loader.d.ts.map +1 -1
  190. package/dist/services/config-loader.js +15 -6
  191. package/dist/services/config-loader.js.map +1 -1
  192. package/dist/services/config.d.ts +55 -25
  193. package/dist/services/config.d.ts.map +1 -1
  194. package/dist/services/config.js +60 -34
  195. package/dist/services/config.js.map +1 -1
  196. package/dist/services/file-writer.d.ts +3 -3
  197. package/dist/services/file-writer.d.ts.map +1 -1
  198. package/dist/services/file-writer.js +6 -8
  199. package/dist/services/file-writer.js.map +1 -1
  200. package/dist/services/inflection.d.ts +126 -27
  201. package/dist/services/inflection.d.ts.map +1 -1
  202. package/dist/services/inflection.js +300 -72
  203. package/dist/services/inflection.js.map +1 -1
  204. package/dist/services/introspection.d.ts.map +1 -1
  205. package/dist/services/introspection.js +6 -6
  206. package/dist/services/introspection.js.map +1 -1
  207. package/dist/services/ir-builder.d.ts.map +1 -1
  208. package/dist/services/ir-builder.js +73 -77
  209. package/dist/services/ir-builder.js.map +1 -1
  210. package/dist/services/ir.d.ts.map +1 -1
  211. package/dist/services/ir.js.map +1 -1
  212. package/dist/services/pg-types.d.ts.map +1 -1
  213. package/dist/services/pg-types.js +3 -3
  214. package/dist/services/pg-types.js.map +1 -1
  215. package/dist/services/smart-tags-parser.d.ts.map +1 -1
  216. package/dist/services/smart-tags-parser.js +4 -4
  217. package/dist/services/smart-tags-parser.js.map +1 -1
  218. package/dist/services/type-hints.d.ts.map +1 -1
  219. package/dist/services/type-hints.js +1 -1
  220. package/dist/services/type-hints.js.map +1 -1
  221. package/dist/services/user-module-parser.d.ts +46 -0
  222. package/dist/services/user-module-parser.d.ts.map +1 -0
  223. package/dist/services/user-module-parser.js +181 -0
  224. package/dist/services/user-module-parser.js.map +1 -0
  225. package/dist/shared/converters.d.ts +60 -0
  226. package/dist/shared/converters.d.ts.map +1 -0
  227. package/dist/shared/converters.js +168 -0
  228. package/dist/shared/converters.js.map +1 -0
  229. package/dist/shared/query-types.d.ts +95 -0
  230. package/dist/shared/query-types.d.ts.map +1 -0
  231. package/dist/shared/query-types.js +9 -0
  232. package/dist/shared/query-types.js.map +1 -0
  233. package/dist/testing.d.ts +125 -37
  234. package/dist/testing.d.ts.map +1 -1
  235. package/dist/testing.js +134 -42
  236. package/dist/testing.js.map +1 -1
  237. package/dist/user-module.d.ts +86 -0
  238. package/dist/user-module.d.ts.map +1 -0
  239. package/dist/user-module.js +55 -0
  240. package/dist/user-module.js.map +1 -0
  241. package/package.json +10 -6
  242. package/dist/lib/conjure.d.ts.map +0 -1
  243. package/dist/lib/conjure.js.map +0 -1
  244. package/dist/lib/hex.d.ts +0 -119
  245. package/dist/lib/hex.d.ts.map +0 -1
  246. package/dist/lib/hex.js +0 -188
  247. package/dist/lib/hex.js.map +0 -1
  248. package/dist/plugins/effect.d.ts +0 -53
  249. package/dist/plugins/effect.d.ts.map +0 -1
  250. package/dist/plugins/effect.js +0 -1074
  251. package/dist/plugins/effect.js.map +0 -1
  252. package/dist/plugins/kysely/queries.d.ts +0 -92
  253. package/dist/plugins/kysely/queries.d.ts.map +0 -1
  254. package/dist/plugins/kysely/queries.js +0 -1169
  255. package/dist/plugins/kysely/queries.js.map +0 -1
  256. package/dist/plugins/kysely/shared.d.ts +0 -59
  257. package/dist/plugins/kysely/shared.d.ts.map +0 -1
  258. package/dist/plugins/kysely/shared.js +0 -247
  259. package/dist/plugins/kysely/shared.js.map +0 -1
  260. package/dist/plugins/kysely/types.d.ts +0 -22
  261. package/dist/plugins/kysely/types.d.ts.map +0 -1
  262. package/dist/plugins/kysely/types.js +0 -428
  263. package/dist/plugins/kysely/types.js.map +0 -1
  264. package/dist/services/artifact-store.d.ts +0 -65
  265. package/dist/services/artifact-store.d.ts.map +0 -1
  266. package/dist/services/artifact-store.js +0 -57
  267. package/dist/services/artifact-store.js.map +0 -1
  268. package/dist/services/core-providers.d.ts +0 -15
  269. package/dist/services/core-providers.d.ts.map +0 -1
  270. package/dist/services/core-providers.js +0 -23
  271. package/dist/services/core-providers.js.map +0 -1
  272. package/dist/services/emissions.d.ts +0 -103
  273. package/dist/services/emissions.d.ts.map +0 -1
  274. package/dist/services/emissions.js +0 -241
  275. package/dist/services/emissions.js.map +0 -1
  276. package/dist/services/execution.d.ts +0 -35
  277. package/dist/services/execution.d.ts.map +0 -1
  278. package/dist/services/execution.js +0 -86
  279. package/dist/services/execution.js.map +0 -1
  280. package/dist/services/file-builder.d.ts +0 -85
  281. package/dist/services/file-builder.d.ts.map +0 -1
  282. package/dist/services/file-builder.js +0 -112
  283. package/dist/services/file-builder.js.map +0 -1
  284. package/dist/services/plugin-meta.d.ts +0 -33
  285. package/dist/services/plugin-meta.d.ts.map +0 -1
  286. package/dist/services/plugin-meta.js +0 -24
  287. package/dist/services/plugin-meta.js.map +0 -1
  288. package/dist/services/plugin-runner.d.ts +0 -42
  289. package/dist/services/plugin-runner.d.ts.map +0 -1
  290. package/dist/services/plugin-runner.js +0 -84
  291. package/dist/services/plugin-runner.js.map +0 -1
  292. package/dist/services/plugin.d.ts +0 -421
  293. package/dist/services/plugin.d.ts.map +0 -1
  294. package/dist/services/plugin.js +0 -197
  295. package/dist/services/plugin.js.map +0 -1
  296. package/dist/services/resolution.d.ts +0 -38
  297. package/dist/services/resolution.d.ts.map +0 -1
  298. package/dist/services/resolution.js +0 -242
  299. package/dist/services/resolution.js.map +0 -1
  300. package/dist/services/service-registry.d.ts +0 -74
  301. package/dist/services/service-registry.d.ts.map +0 -1
  302. package/dist/services/service-registry.js +0 -61
  303. package/dist/services/service-registry.js.map +0 -1
  304. package/dist/services/symbols.d.ts +0 -144
  305. package/dist/services/symbols.d.ts.map +0 -1
  306. package/dist/services/symbols.js +0 -144
  307. package/dist/services/symbols.js.map +0 -1
@@ -1,48 +1,49 @@
1
1
  /**
2
- * HTTP tRPC Plugin - Generate tRPC routers from query plugins
2
+ * HTTP tRPC Plugin - Generates tRPC routers from query symbols
3
3
  *
4
- * Consumes method symbols from sql-queries or kysely-queries via the symbol registry
5
- * and generates type-safe tRPC routers with schema validation.
4
+ * Consumes "queries" and "schema" capabilities (provider-agnostic).
5
+ * Works with any queries provider (kysely, drizzle, effect-sql, etc.)
6
+ * and any schema provider (zod, arktype, effect, etc.).
7
+ *
8
+ * Uses the SymbolRegistry to resolve query functions and optionally
9
+ * schema symbols for request validation.
10
+ *
11
+ * Imports are resolved via the cross-reference system:
12
+ * - Calls registry.import(queryCapability).ref() during render
13
+ * - Emit phase generates imports from the recorded references
6
14
  */
7
- import { Schema as S } from "effect";
8
- import { definePlugin } from "../services/plugin.js";
9
- import { conjure, cast } from "../lib/conjure.js";
10
- import { inflect } from "../services/inflection.js";
11
- import { SCHEMA_BUILDER_KIND, } from "../ir/extensions/schema-builder.js";
12
- const { b } = conjure;
15
+ import { Effect, Schema as S } from "effect";
16
+ import { IR } from "../services/ir.js";
17
+ import { Inflection } from "../services/inflection.js";
18
+ import { SymbolRegistry } from "../runtime/registry.js";
19
+ import { isTableEntity } from "../ir/semantic-ir.js";
20
+ import { conjure, cast } from "../conjure/index.js";
21
+ import { normalizeFileNaming } from "../runtime/file-assignment.js";
22
+ const b = conjure.b;
23
+ const stmt = conjure.stmt;
24
+ const PLUGIN_NAME = "trpc-http";
13
25
  // ============================================================================
14
26
  // Configuration
15
27
  // ============================================================================
28
+ const DEFAULT_OUTPUT_DIR = "";
29
+ const DEFAULT_ROUTES_FILE = "trpc.ts";
30
+ const DEFAULT_APP_FILE = "trpc.ts";
31
+ /**
32
+ * Schema-validated portion of the config (simple types only).
33
+ */
16
34
  const HttpTrpcConfigSchema = S.Struct({
17
- /** Output directory for generated router files. Default: "trpc" */
18
- outputDir: S.optionalWith(S.String, { default: () => "trpc" }),
19
- /**
20
- * Header content to prepend to each generated file.
21
- * MUST import `router` and a base procedure (e.g., `publicProcedure`).
22
- *
23
- * @example
24
- * ```typescript
25
- * header: `import { router, publicProcedure } from "../trpc.js";`
26
- * ```
27
- */
28
- header: S.String,
29
- /**
30
- * Name of the base procedure to use in generated code.
31
- * Must match an import from your header.
32
- * Default: "publicProcedure"
33
- */
35
+ outputDir: S.optionalWith(S.String, { default: () => DEFAULT_OUTPUT_DIR }),
36
+ /** Name of the base procedure to use. Default: "publicProcedure" */
34
37
  baseProcedure: S.optionalWith(S.String, { default: () => "publicProcedure" }),
35
38
  /** Name of the aggregated router export. Default: "appRouter" */
36
39
  aggregatorName: S.optionalWith(S.String, { default: () => "appRouter" }),
37
40
  });
38
41
  // ============================================================================
39
- // String Helpers
42
+ // String Helpers - removed, now using inflection service:
43
+ // - inflection.variableName(entity, "Router") for router variable names
44
+ // - inflection.camelCase(entity) for merged router keys
45
+ // - inflection.pascalCase(field) for lookup field suffix
40
46
  // ============================================================================
41
- /** Convert PascalCase/camelCase to kebab-case */
42
- const toKebabCase = (str) => str
43
- .replace(/([a-z])([A-Z])/g, "$1-$2")
44
- .replace(/_/g, "-")
45
- .toLowerCase();
46
47
  // ============================================================================
47
48
  // Procedure Builders
48
49
  // ============================================================================
@@ -63,14 +64,20 @@ const kindToProcedureType = (kind) => {
63
64
  }
64
65
  };
65
66
  /**
66
- * Build the handler function body for a procedure.
67
- * tRPC handlers receive { input, ctx } and return data directly.
67
+ * Check if a param needs coercion (comes from URL string).
68
68
  */
69
- const buildProcedureBody = (method) => {
70
- const queryFnName = method.name;
69
+ function needsCoercion(param) {
70
+ return (param.source === "pk" ||
71
+ param.source === "fk" ||
72
+ param.source === "lookup" ||
73
+ param.source === "pagination");
74
+ }
75
+ /**
76
+ * Build the handler function body for a tRPC procedure.
77
+ * tRPC handlers receive { input } and return data directly.
78
+ */
79
+ function buildProcedureBody(method) {
71
80
  const callSig = method.callSignature ?? { style: "named" };
72
- const statements = [];
73
- // Build the function call arguments based on callSignature
74
81
  const args = [];
75
82
  if (callSig.style === "positional") {
76
83
  // Positional: fn(a, b, c)
@@ -79,7 +86,7 @@ const buildProcedureBody = (method) => {
79
86
  }
80
87
  }
81
88
  else {
82
- // Named: fn({ a, b, c }) or fn(input) for body
89
+ // Named style
83
90
  const bodyParam = method.params.find((p) => p.source === "body");
84
91
  if (bodyParam && callSig.bodyStyle === "spread") {
85
92
  // Body fields spread directly: fn(input)
@@ -87,29 +94,19 @@ const buildProcedureBody = (method) => {
87
94
  }
88
95
  else if (bodyParam && callSig.bodyStyle === "property") {
89
96
  // Body wrapped in property: fn({ id, data })
90
- // Collect non-body params that need to be extracted from input
91
97
  const nonBodyParams = method.params.filter((p) => p.source === "pk" || p.source === "fk" || p.source === "lookup" || p.source === "pagination");
92
98
  if (nonBodyParams.length > 0) {
93
- // Generate: const { id, ...data } = input;
94
- const destructureProps = nonBodyParams.map((p) => b.property.from({ kind: "init", key: b.identifier(p.name), value: b.identifier(p.name), shorthand: true }));
95
- const restId = b.identifier(bodyParam.name);
96
- const restElem = b.restElement(restId);
97
- const pattern = b.objectPattern([...destructureProps, restElem]);
98
- const destructureDecl = b.variableDeclaration("const", [
99
- b.variableDeclarator(pattern, b.identifier("input")),
100
- ]);
101
- statements.push(destructureDecl);
102
- // Build: { id, data } using the destructured variables
99
+ // Build object with non-body params + body property
103
100
  let objBuilder = conjure.obj();
104
101
  for (const param of nonBodyParams) {
105
- objBuilder = objBuilder.shorthand(param.name);
102
+ objBuilder = objBuilder.prop(param.name, b.memberExpression(b.identifier("input"), b.identifier(param.name)));
106
103
  }
107
- objBuilder = objBuilder.shorthand(bodyParam.name);
104
+ objBuilder = objBuilder.prop(bodyParam.name, b.memberExpression(b.identifier("input"), b.identifier(bodyParam.name)));
108
105
  args.push(objBuilder.build());
109
106
  }
110
107
  else {
111
- // No non-body params, just wrap input: fn({ data: input })
112
- args.push(conjure.obj().prop(bodyParam.name, b.identifier("input")).build());
108
+ // No non-body params, just pass input
109
+ args.push(b.identifier("input"));
113
110
  }
114
111
  }
115
112
  else if (method.params.length > 0) {
@@ -117,73 +114,98 @@ const buildProcedureBody = (method) => {
117
114
  args.push(b.identifier("input"));
118
115
  }
119
116
  }
120
- // Build: return await queryFn(args)
121
- const queryCall = b.callExpression(b.identifier(queryFnName), args.map(cast.toExpr));
122
- const awaitExpr = b.awaitExpression(queryCall);
117
+ // Build: queryFn(args)
118
+ const queryCall = b.callExpression(b.identifier(method.name), args.map(cast.toExpr));
119
+ // Add the appropriate .execute*() method based on query kind
120
+ const executeMethod = method.kind === "read" || (method.kind === "lookup" && method.isUniqueLookup)
121
+ ? "executeTakeFirst"
122
+ : method.kind === "create" || method.kind === "update"
123
+ ? "executeTakeFirstOrThrow"
124
+ : "execute";
125
+ const queryWithExecute = b.callExpression(b.memberExpression(queryCall, b.identifier(executeMethod)), []);
126
+ const awaitExpr = b.awaitExpression(queryWithExecute);
123
127
  // For delete, return success object
124
128
  if (method.kind === "delete") {
125
- statements.push(b.expressionStatement(awaitExpr));
126
- statements.push(b.returnStatement(conjure.obj().prop("success", b.booleanLiteral(true)).build()));
127
- return statements;
129
+ return [
130
+ b.expressionStatement(awaitExpr),
131
+ b.returnStatement(conjure.obj().prop("success", b.booleanLiteral(true)).build()),
132
+ ];
128
133
  }
129
- statements.push(b.returnStatement(awaitExpr));
130
- return statements;
131
- };
134
+ return [b.returnStatement(awaitExpr)];
135
+ }
132
136
  /**
133
- * Determine if a method needs body validation and which schema to use.
137
+ * Build Zod type expression for a param.
134
138
  */
135
- const getBodySchemaImport = (method, entityName) => {
139
+ function buildZodParamType(param) {
140
+ const baseType = param.type.toLowerCase();
141
+ let zodCall;
142
+ switch (baseType) {
143
+ case "number":
144
+ zodCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("z"), b.identifier("coerce")), b.identifier("number")), []);
145
+ break;
146
+ case "boolean":
147
+ zodCall = b.callExpression(b.memberExpression(b.identifier("z"), b.identifier("boolean")), []);
148
+ break;
149
+ case "date":
150
+ zodCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("z"), b.identifier("coerce")), b.identifier("date")), []);
151
+ break;
152
+ case "string":
153
+ default:
154
+ zodCall = b.callExpression(b.memberExpression(b.identifier("z"), b.identifier("string")), []);
155
+ break;
156
+ }
157
+ if (!param.required) {
158
+ zodCall = b.callExpression(b.memberExpression(cast.toExpr(zodCall), b.identifier("optional")), []);
159
+ }
160
+ return zodCall;
161
+ }
162
+ /**
163
+ * Get the body schema name for a method if it needs validation.
164
+ */
165
+ function getBodySchemaName(method, entityName) {
136
166
  if (method.kind === "create") {
137
- return { entity: entityName, shape: "insert", schemaName: `${entityName}Insert` };
167
+ return `${entityName}Insert`;
138
168
  }
139
169
  if (method.kind === "update") {
140
- return { entity: entityName, shape: "update", schemaName: `${entityName}Update` };
170
+ // Use UpdateInput schema: required PK + optional non-PK fields
171
+ return `${entityName}UpdateInput`;
141
172
  }
142
173
  return null;
143
- };
174
+ }
144
175
  /**
145
- * Build the input schema expression for a procedure.
176
+ * Build input schema expression for a procedure.
177
+ * Returns the schema expression and whether we need z import.
146
178
  */
147
- const buildInputSchema = (method, entityName, requestSchema) => {
148
- const bodySchema = getBodySchemaImport(method, entityName);
179
+ function buildInputSchema(method, entityName) {
180
+ const bodySchemaName = getBodySchemaName(method, entityName);
149
181
  const nonBodyParams = method.params.filter((p) => p.source !== "body");
150
182
  const callSig = method.callSignature ?? { style: "named" };
151
- // For update with bodyStyle: "property", we need to merge PK params with body schema
152
- if (bodySchema && nonBodyParams.length > 0 && callSig.bodyStyle === "property") {
153
- // Build: z.object({ id: z.coerce.number() }).merge(PostUpdate)
183
+ // For update with bodyStyle: "property", merge PK params with body schema
184
+ if (bodySchemaName && nonBodyParams.length > 0 && callSig.bodyStyle === "property") {
154
185
  let objBuilder = conjure.obj();
155
186
  for (const p of nonBodyParams) {
156
187
  objBuilder = objBuilder.prop(p.name, buildZodParamType(p));
157
188
  }
158
189
  const zodObject = b.callExpression(b.memberExpression(b.identifier("z"), b.identifier("object")), [cast.toExpr(objBuilder.build())]);
159
- const mergedSchema = b.callExpression(b.memberExpression(zodObject, b.identifier("merge")), [b.identifier(bodySchema.schemaName)]);
190
+ const mergedSchema = b.callExpression(b.memberExpression(zodObject, b.identifier("merge")), [b.identifier(bodySchemaName)]);
160
191
  return {
161
192
  inputExpr: mergedSchema,
162
- bodySchema,
163
- schemaBuilderImport: { names: ["z"], from: "zod" },
193
+ bodySchemaName,
194
+ needsZodImport: true,
164
195
  };
165
196
  }
166
197
  // Body params only use imported entity schemas
167
- if (bodySchema) {
198
+ if (bodySchemaName) {
168
199
  return {
169
- inputExpr: b.identifier(bodySchema.schemaName),
170
- bodySchema,
171
- schemaBuilderImport: null,
200
+ inputExpr: b.identifier(bodySchemaName),
201
+ bodySchemaName,
202
+ needsZodImport: false,
172
203
  };
173
204
  }
174
- // Non-body params: use schema builder
205
+ // Non-body params: build inline z.object
175
206
  if (nonBodyParams.length === 0) {
176
- return { inputExpr: null, bodySchema: null, schemaBuilderImport: null };
177
- }
178
- const schemaResult = requestSchema(nonBodyParams);
179
- if (schemaResult) {
180
- return {
181
- inputExpr: schemaResult.ast,
182
- bodySchema: null,
183
- schemaBuilderImport: schemaResult.importSpec,
184
- };
207
+ return { inputExpr: null, bodySchemaName: null, needsZodImport: false };
185
208
  }
186
- // Fallback: build inline z.object (tRPC requires Zod)
187
209
  let objBuilder = conjure.obj();
188
210
  for (const param of nonBodyParams) {
189
211
  const zodType = buildZodParamType(param);
@@ -191,45 +213,19 @@ const buildInputSchema = (method, entityName, requestSchema) => {
191
213
  }
192
214
  return {
193
215
  inputExpr: b.callExpression(b.memberExpression(b.identifier("z"), b.identifier("object")), [cast.toExpr(objBuilder.build())]),
194
- bodySchema: null,
195
- schemaBuilderImport: { names: ["z"], from: "zod" },
216
+ bodySchemaName: null,
217
+ needsZodImport: true,
196
218
  };
197
- };
198
- /**
199
- * Build Zod type expression for a param (fallback when no schema-builder).
200
- */
201
- const buildZodParamType = (param) => {
202
- const baseType = param.type.toLowerCase();
203
- let zodCall;
204
- switch (baseType) {
205
- case "number":
206
- zodCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("z"), b.identifier("coerce")), b.identifier("number")), []);
207
- break;
208
- case "boolean":
209
- zodCall = b.callExpression(b.memberExpression(b.identifier("z"), b.identifier("boolean")), []);
210
- break;
211
- case "date":
212
- zodCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("z"), b.identifier("coerce")), b.identifier("date")), []);
213
- break;
214
- case "string":
215
- default:
216
- zodCall = b.callExpression(b.memberExpression(b.identifier("z"), b.identifier("string")), []);
217
- break;
218
- }
219
- if (!param.required) {
220
- zodCall = b.callExpression(b.memberExpression(cast.toExpr(zodCall), b.identifier("optional")), []);
221
- }
222
- return zodCall;
223
- };
219
+ }
224
220
  /**
225
- * Build a single tRPC procedure.
221
+ * Build a single tRPC procedure expression.
226
222
  */
227
- const buildProcedure = (method, entityName, baseProcedure, requestSchema) => {
223
+ function buildProcedure(method, entityName, baseProcedure) {
228
224
  const procedureType = kindToProcedureType(method.kind);
229
225
  // Start with base procedure
230
226
  let chainExpr = b.identifier(baseProcedure);
231
227
  // Build input schema
232
- const { inputExpr, bodySchema, schemaBuilderImport } = buildInputSchema(method, entityName, requestSchema);
228
+ const { inputExpr, bodySchemaName, needsZodImport } = buildInputSchema(method, entityName);
233
229
  // Add .input(schema) if there are params
234
230
  if (inputExpr) {
235
231
  chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("input")), [cast.toExpr(inputExpr)]);
@@ -246,147 +242,247 @@ const buildProcedure = (method, entityName, baseProcedure, requestSchema) => {
246
242
  handler.async = true;
247
243
  // Add .query() or .mutation()
248
244
  chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier(procedureType)), [handler]);
249
- return { procedureExpr: chainExpr, bodySchema, schemaBuilderImport };
250
- };
245
+ return { procedureExpr: chainExpr, bodySchemaName, needsZodImport };
246
+ }
247
+ /**
248
+ * Get the capability suffix for a query method.
249
+ */
250
+ function getMethodCapabilitySuffix(method, inflection) {
251
+ switch (method.kind) {
252
+ case "read":
253
+ return "findById";
254
+ case "list":
255
+ return "list";
256
+ case "create":
257
+ return "create";
258
+ case "update":
259
+ return "update";
260
+ case "delete":
261
+ return "delete";
262
+ case "lookup":
263
+ if (method.lookupField) {
264
+ const pascalField = inflection.pascalCase(method.lookupField);
265
+ return `findBy${pascalField}`;
266
+ }
267
+ return "lookup";
268
+ case "function":
269
+ return method.name;
270
+ }
271
+ }
272
+ /**
273
+ * Generate tRPC router for an entity.
274
+ */
275
+ function generateTrpcRouter(entityName, queries, config, registry, inflection) {
276
+ const routerName = inflection.variableName(entityName, "Router");
277
+ let needsZodImport = false;
278
+ const bodySchemaNames = [];
279
+ // Build router object
280
+ let routerObjBuilder = conjure.obj();
281
+ for (const method of queries.methods) {
282
+ // Record cross-reference for this query method
283
+ const methodCapability = `queries:${entityName}:${getMethodCapabilitySuffix(method, inflection)}`;
284
+ if (registry.has(methodCapability)) {
285
+ registry.import(methodCapability).ref();
286
+ }
287
+ const { procedureExpr, bodySchemaName, needsZodImport: methodNeedsZod } = buildProcedure(method, entityName, config.baseProcedure);
288
+ if (bodySchemaName && !bodySchemaNames.includes(bodySchemaName)) {
289
+ bodySchemaNames.push(bodySchemaName);
290
+ // Import schema via cross-reference system
291
+ const schemaCapability = `schema:${bodySchemaName}`;
292
+ if (registry.has(schemaCapability)) {
293
+ registry.import(schemaCapability).ref();
294
+ }
295
+ }
296
+ if (methodNeedsZod)
297
+ needsZodImport = true;
298
+ routerObjBuilder = routerObjBuilder.prop(method.name, procedureExpr);
299
+ }
300
+ // Build: export const userRouter = router({ ... })
301
+ const routerCall = b.callExpression(b.identifier("router"), [cast.toExpr(routerObjBuilder.build())]);
302
+ const variableDeclarator = b.variableDeclarator(b.identifier(routerName), cast.toExpr(routerCall));
303
+ const variableDeclaration = b.variableDeclaration("const", [variableDeclarator]);
304
+ const externalImports = [];
305
+ if (needsZodImport) {
306
+ externalImports.push({ from: "zod", names: ["z"] });
307
+ }
308
+ return {
309
+ statements: [variableDeclaration],
310
+ externalImports,
311
+ };
312
+ }
313
+ /**
314
+ * Generate aggregator router that combines all entity routers.
315
+ */
316
+ function generateAggregator(entities, config, registry, inflection) {
317
+ const entityEntries = Array.from(entities.entries());
318
+ if (entityEntries.length === 0) {
319
+ return { statements: [], externalImports: [] };
320
+ }
321
+ // Build: router({ user: userRouter, post: postRouter, ... })
322
+ let routerObjBuilder = conjure.obj();
323
+ for (const [entityName] of entityEntries) {
324
+ const routerName = inflection.variableName(entityName, "Router");
325
+ const key = inflection.camelCase(entityName);
326
+ routerObjBuilder = routerObjBuilder.prop(key, b.identifier(routerName));
327
+ // Record cross-reference to the entity's router capability
328
+ const routeCapability = `http-routes:trpc:${entityName}`;
329
+ if (registry.has(routeCapability)) {
330
+ registry.import(routeCapability).ref();
331
+ }
332
+ }
333
+ const routerCall = b.callExpression(b.identifier("router"), [cast.toExpr(routerObjBuilder.build())]);
334
+ const variableDeclarator = b.variableDeclarator(b.identifier(config.aggregatorName), cast.toExpr(routerCall));
335
+ const variableDeclaration = b.variableDeclaration("const", [variableDeclarator]);
336
+ // Also export the type: export type AppRouter = typeof appRouter
337
+ const typeExport = b.exportNamedDeclaration(b.tsTypeAliasDeclaration(b.identifier("AppRouter"), b.tsTypeQuery(b.identifier(config.aggregatorName))));
338
+ return {
339
+ statements: [variableDeclaration, typeExport],
340
+ externalImports: [],
341
+ };
342
+ }
251
343
  // ============================================================================
252
344
  // Plugin Definition
253
345
  // ============================================================================
254
346
  /**
255
- * Create an http-trpc provider that generates tRPC routers.
347
+ * Create an http-trpc plugin that generates tRPC routers.
256
348
  *
257
349
  * @example
258
350
  * ```typescript
259
- * import { httpTrpc } from "pg-sourcerer"
351
+ * import { trpc } from "pg-sourcerer"
260
352
  *
261
353
  * export default defineConfig({
262
354
  * plugins: [
263
355
  * zod(),
264
- * sqlQueries(),
265
- * httpTrpc({
266
- * header: `import { router, publicProcedure } from "../trpc.js";`,
356
+ * kyselyQueries(),
357
+ * trpc({
358
+ * baseProcedure: "publicProcedure",
267
359
  * }),
268
360
  * ],
269
361
  * })
270
362
  * ```
271
363
  */
272
- export function httpTrpc(config) {
273
- const parsed = S.decodeUnknownSync(HttpTrpcConfigSchema)(config);
274
- return definePlugin({
275
- name: "http-trpc",
276
- kind: "http-routes",
277
- singleton: true,
278
- canProvide: () => true,
279
- requires: () => [
280
- { kind: "queries", params: {} },
281
- { kind: "schemas", params: {} },
364
+ export function trpc(config) {
365
+ const schemaConfig = S.decodeSync(HttpTrpcConfigSchema)(config ?? {});
366
+ const resolvedConfig = {
367
+ outputDir: schemaConfig.outputDir,
368
+ baseProcedure: schemaConfig.baseProcedure,
369
+ aggregatorName: schemaConfig.aggregatorName,
370
+ routesFile: normalizeFileNaming(config?.routesFile, DEFAULT_ROUTES_FILE),
371
+ appFile: normalizeFileNaming(config?.appFile, DEFAULT_APP_FILE),
372
+ trpcImport: config?.trpcImport,
373
+ };
374
+ return {
375
+ name: PLUGIN_NAME,
376
+ provides: [],
377
+ fileDefaults: [
378
+ // Entity routers use routesFile config
379
+ {
380
+ pattern: "http-routes:trpc:",
381
+ outputDir: resolvedConfig.outputDir,
382
+ fileNaming: resolvedConfig.routesFile,
383
+ },
384
+ // App aggregator uses appFile config (more specific pattern wins)
385
+ {
386
+ pattern: "http-routes:trpc:app",
387
+ outputDir: resolvedConfig.outputDir,
388
+ fileNaming: resolvedConfig.appFile,
389
+ },
282
390
  ],
283
- provide: (_params, _deps, ctx) => {
284
- const { outputDir, baseProcedure, header, aggregatorName } = parsed;
285
- // Get all entities with registered query methods
286
- const entityNames = ctx.symbols.getEntitiesWithMethods();
287
- if (entityNames.length === 0) {
288
- return;
289
- }
290
- // Track generated routers for aggregator
291
- const generatedRouters = [];
292
- // Create schema builder function
293
- const requestSchema = (params) => {
294
- if (params.length === 0)
295
- return undefined;
296
- try {
297
- const request = { variant: "params", params };
298
- return ctx.request(SCHEMA_BUILDER_KIND, request);
299
- }
300
- catch {
301
- return undefined;
302
- }
303
- };
304
- // Generate router for each entity
305
- for (const entityName of entityNames) {
306
- const entityMethods = ctx.symbols.getEntityMethods(entityName);
307
- if (!entityMethods || entityMethods.methods.length === 0)
391
+ declare: Effect.gen(function* () {
392
+ const ir = yield* IR;
393
+ const inflection = yield* Inflection;
394
+ const declarations = [];
395
+ // Declare routers for all table entities that might have queries
396
+ for (const entity of ir.entities.values()) {
397
+ if (!isTableEntity(entity))
308
398
  continue;
309
- const filePath = `${outputDir}/${inflect.uncapitalize(entityName)}.ts`;
310
- const routerName = `${inflect.uncapitalize(entityName)}Router`;
311
- const file = ctx.file(filePath);
312
- // Header provides router and baseProcedure imports
313
- file.header(header);
314
- // Import query functions
315
- const queryFunctionNames = entityMethods.methods.map((m) => m.name);
316
- const queriesImportPath = `../${entityMethods.importPath.replace(/\.ts$/, ".js")}`;
317
- file.import({
318
- kind: "relative",
319
- names: queryFunctionNames,
320
- from: queriesImportPath,
321
- });
322
- // Build router object
323
- let routerObjBuilder = conjure.obj();
324
- const bodySchemaImports = [];
325
- let schemaLibraryImport = null;
326
- for (const method of entityMethods.methods) {
327
- const { procedureExpr, bodySchema, schemaBuilderImport } = buildProcedure(method, entityName, baseProcedure, requestSchema);
328
- if (bodySchema)
329
- bodySchemaImports.push(bodySchema);
330
- if (schemaBuilderImport)
331
- schemaLibraryImport = schemaBuilderImport;
332
- routerObjBuilder = routerObjBuilder.prop(method.name, procedureExpr);
333
- }
334
- // Import schema library if needed
335
- if (schemaLibraryImport) {
336
- if (schemaLibraryImport.names) {
337
- file.import({
338
- kind: "package",
339
- names: [...schemaLibraryImport.names],
340
- from: schemaLibraryImport.from,
341
- });
342
- }
343
- }
344
- // Import body schemas
345
- for (const schemaImport of bodySchemaImports) {
346
- file.import({
347
- kind: "symbol",
348
- ref: {
349
- capability: "schemas",
350
- entity: schemaImport.entity,
351
- shape: schemaImport.shape,
352
- },
399
+ if (entity.tags.omit === true)
400
+ continue;
401
+ const hasAnyPermissions = entity.permissions.canSelect ||
402
+ entity.permissions.canInsert ||
403
+ entity.permissions.canUpdate ||
404
+ entity.permissions.canDelete;
405
+ if (hasAnyPermissions) {
406
+ declarations.push({
407
+ name: inflection.variableName(entity.name, "Router"),
408
+ capability: `http-routes:trpc:${entity.name}`,
409
+ baseEntityName: entity.name,
353
410
  });
354
411
  }
355
- // Build: export const userRouter = router({ ... })
356
- const routerCall = b.callExpression(b.identifier("router"), [cast.toExpr(routerObjBuilder.build())]);
357
- const exportStmt = conjure.export.const(routerName, routerCall);
358
- file.ast(conjure.program(exportStmt)).emit();
359
- generatedRouters.push({
360
- fileName: `${inflect.uncapitalize(entityName)}.js`,
361
- routerName,
412
+ }
413
+ // Also declare the aggregator
414
+ declarations.push({
415
+ name: resolvedConfig.aggregatorName,
416
+ capability: "http-routes:trpc:app",
417
+ });
418
+ return declarations;
419
+ }),
420
+ render: Effect.gen(function* () {
421
+ const ir = yield* IR;
422
+ const registry = yield* SymbolRegistry;
423
+ const inflection = yield* Inflection;
424
+ const rendered = [];
425
+ // Query the registry for all entity query capabilities
426
+ const entityQueries = new Map();
427
+ const queryCapabilities = registry.query("queries:");
428
+ for (const decl of queryCapabilities) {
429
+ // Only look at aggregate capabilities (queries:impl:EntityName, not queries:impl:EntityName:method)
430
+ const parts = decl.capability.split(":");
431
+ if (parts.length !== 3)
432
+ continue;
433
+ const entityName = parts[2];
434
+ const metadata = registry.getMetadata(decl.capability);
435
+ if (metadata && typeof metadata === "object" && "methods" in metadata) {
436
+ entityQueries.set(entityName, metadata);
437
+ }
438
+ }
439
+ // User module imports for router and procedure (if configured)
440
+ const trpcUserImports = resolvedConfig.trpcImport
441
+ ? [resolvedConfig.trpcImport]
442
+ : undefined;
443
+ for (const [entityName, queries] of entityQueries) {
444
+ const entity = ir.entities.get(entityName);
445
+ if (!entity || !isTableEntity(entity))
446
+ continue;
447
+ const capability = `http-routes:trpc:${entityName}`;
448
+ // Scope cross-references to this specific capability
449
+ const { statements, externalImports } = registry.forSymbol(capability, () => generateTrpcRouter(entityName, queries, resolvedConfig, registry, inflection));
450
+ rendered.push({
451
+ name: inflection.variableName(entityName, "Router"),
452
+ capability,
453
+ node: statements[0],
454
+ exports: "named",
455
+ externalImports,
456
+ userImports: trpcUserImports,
362
457
  });
363
458
  }
364
- // Generate aggregator index.ts
365
- if (generatedRouters.length > 0) {
366
- const indexPath = `${outputDir}/index.ts`;
367
- const indexFile = ctx.file(indexPath);
368
- // Header provides router import
369
- indexFile.header(header);
370
- for (const route of generatedRouters) {
371
- indexFile.import({
372
- kind: "relative",
373
- names: [route.routerName],
374
- from: `./${route.fileName}`,
459
+ if (entityQueries.size > 0) {
460
+ const appCapability = "http-routes:trpc:app";
461
+ // Scope cross-references to the app capability
462
+ const { statements, externalImports } = registry.forSymbol(appCapability, () => generateAggregator(entityQueries, resolvedConfig, registry, inflection));
463
+ // The aggregator has multiple statements (const + type export)
464
+ // We need to handle this differently - wrap in a program or return multiple
465
+ rendered.push({
466
+ name: resolvedConfig.aggregatorName,
467
+ capability: appCapability,
468
+ node: statements[0], // The const declaration
469
+ exports: "named",
470
+ externalImports,
471
+ userImports: trpcUserImports,
472
+ });
473
+ // Add the type export as a separate rendered symbol
474
+ if (statements[1]) {
475
+ rendered.push({
476
+ name: "AppRouter",
477
+ capability: "http-routes:trpc:app:type",
478
+ node: statements[1],
479
+ exports: false, // Already has export in the node
480
+ // No userImports needed for type export
375
481
  });
376
482
  }
377
- // Build: export const appRouter = router({ user: userRouter, ... })
378
- let routerObjBuilder = conjure.obj();
379
- for (const route of generatedRouters) {
380
- const key = route.routerName.replace(/Router$/, "");
381
- routerObjBuilder = routerObjBuilder.prop(key, b.identifier(route.routerName));
382
- }
383
- const routerCall = b.callExpression(b.identifier("router"), [cast.toExpr(routerObjBuilder.build())]);
384
- const exportStmt = conjure.export.const(aggregatorName, routerCall);
385
- // Also export the type
386
- const typeExport = b.exportNamedDeclaration(b.tsTypeAliasDeclaration(b.identifier("AppRouter"), b.tsTypeQuery(b.identifier(aggregatorName))));
387
- indexFile.ast(conjure.program(exportStmt, typeExport)).emit();
388
483
  }
389
- },
390
- });
484
+ return rendered;
485
+ }),
486
+ };
391
487
  }
392
488
  //# sourceMappingURL=http-trpc.js.map