@danielfgray/pg-sourcerer 0.3.0 → 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,57 +1,70 @@
1
1
  /**
2
- * HTTP Express Plugin - Generate Express route handlers from query plugins
2
+ * HTTP Express Plugin - Generates Express route handlers from query symbols
3
3
  *
4
- * Consumes method symbols from sql-queries or kysely-queries via the symbol registry
5
- * and generates type-safe Express HTTP route handlers.
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.).
6
7
  *
7
- * Supports validation via schema plugins (zod, valibot, arktype):
8
- * - Body validation: imports entity schemas (UserInsert, UserUpdate) and uses .parse(req.body)
9
- * - Path/query param validation: uses schema builder for type coercion
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
10
14
  */
11
- import { Schema as S } from "effect";
12
- import { definePlugin } from "../services/plugin.js";
13
- import { conjure, cast } from "../lib/conjure.js";
15
+ import { Effect, Schema as S } from "effect";
16
+ import { IR } from "../services/ir.js";
17
+ import { Inflection } from "../services/inflection.js";
14
18
  import { inflect } from "../services/inflection.js";
15
- const { b, stmt } = conjure;
16
- const getBodySchemaImport = (method, entityName) => {
17
- if (method.kind === "create") {
18
- return { entity: entityName, shape: "insert", schemaName: `${entityName}Insert` };
19
- }
20
- if (method.kind === "update") {
21
- return { entity: entityName, shape: "update", schemaName: `${entityName}Update` };
22
- }
23
- return null;
24
- };
25
- // ============================================================================
26
- // Configuration
27
- // ============================================================================
19
+ import { SymbolRegistry } from "../runtime/registry.js";
20
+ import { isTableEntity } from "../ir/semantic-ir.js";
21
+ import { conjure, cast } from "../conjure/index.js";
22
+ import { normalizeFileNaming } from "../runtime/file-assignment.js";
23
+ const b = conjure.b;
24
+ const PLUGIN_NAME = "express-http";
25
+ const DEFAULT_OUTPUT_DIR = "";
26
+ const DEFAULT_ROUTES_FILE = "routes.ts";
27
+ const DEFAULT_APP_FILE = "index.ts";
28
+ /**
29
+ * Schema-validated portion of the config (simple types only).
30
+ * FileNaming functions are handled separately since Schema can't validate functions.
31
+ */
28
32
  const HttpExpressConfigSchema = S.Struct({
29
- /** Output directory for generated route files. Default: "routes" */
30
- outputDir: S.optionalWith(S.String, { default: () => "routes" }),
31
- /** Base path for all routes. Default: "/api" */
32
- basePath: S.optionalWith(S.String, { default: () => "/api" }),
33
- /** Header content to prepend to each generated file */
34
- header: S.optional(S.String),
35
- /**
36
- * Name of the aggregated router export.
37
- * Default: "api"
38
- */
39
- aggregatorName: S.optionalWith(S.String, { default: () => "api" }),
33
+ outputDir: S.optionalWith(S.String, { default: () => DEFAULT_OUTPUT_DIR }),
34
+ basePath: S.optionalWith(S.String, { default: () => "" }),
40
35
  });
41
- // ============================================================================
42
- // String Helpers
43
- // ============================================================================
44
- /** Convert PascalCase/camelCase to kebab-case */
45
- const toKebabCase = (str) => str
46
- .replace(/([a-z])([A-Z])/g, "$1-$2")
47
- .replace(/_/g, "-")
48
- .toLowerCase();
49
- /** Convert entity name to URL path segment (kebab-case plural) */
50
- const entityToPathSegment = (entityName) => inflect.pluralize(toKebabCase(entityName));
51
- // ============================================================================
52
- // Route Generation Helpers
53
- // ============================================================================
54
- /** Map query method kind to HTTP method */
36
+ /**
37
+ * Coerce a URL param (always string) to the expected type.
38
+ * Returns an expression that wraps the identifier with the appropriate coercion.
39
+ */
40
+ function coerceParam(paramName, paramType) {
41
+ const ident = b.identifier(paramName);
42
+ const lowerType = paramType.toLowerCase();
43
+ // Numeric types
44
+ if (lowerType === "number" || lowerType === "int" || lowerType === "integer" || lowerType === "bigint") {
45
+ return b.callExpression(b.identifier("Number"), [ident]);
46
+ }
47
+ // Date types
48
+ if (lowerType === "date" || lowerType.includes("timestamp") || lowerType.includes("datetime")) {
49
+ return b.newExpression(b.identifier("Date"), [ident]);
50
+ }
51
+ // Boolean
52
+ if (lowerType === "boolean" || lowerType === "bool") {
53
+ // "true" -> true, anything else -> false
54
+ return b.binaryExpression("===", ident, b.stringLiteral("true"));
55
+ }
56
+ // String, UUID, and other types - no coercion needed
57
+ return ident;
58
+ }
59
+ /**
60
+ * Check if a param needs coercion (comes from URL string).
61
+ */
62
+ function needsCoercion(param) {
63
+ return (param.source === "pk" ||
64
+ param.source === "fk" ||
65
+ param.source === "lookup" ||
66
+ param.source === "pagination");
67
+ }
55
68
  const kindToHttpMethod = (kind) => {
56
69
  switch (kind) {
57
70
  case "read":
@@ -68,136 +81,81 @@ const kindToHttpMethod = (kind) => {
68
81
  return "post";
69
82
  }
70
83
  };
71
- /** Get the route path for a method */
72
- const getRoutePath = (method) => {
84
+ const getRoutePath = (method, entityName, inflection) => {
73
85
  switch (method.kind) {
74
86
  case "read":
75
87
  case "update":
76
88
  case "delete": {
77
- const pkParam = method.params.find(p => p.source === "pk");
89
+ const pkParam = method.params.find((p) => p.source === "pk");
78
90
  const paramName = pkParam?.name ?? "id";
79
91
  return `/:${paramName}`;
80
92
  }
81
93
  case "list":
94
+ return "/";
82
95
  case "create":
83
96
  return "/";
84
97
  case "lookup": {
85
98
  const field = method.lookupField ?? "field";
86
- const fieldKebab = toKebabCase(field);
87
- const lookupParam = method.params.find(p => p.source === "lookup" || p.source === "fk");
99
+ const lookupParam = method.params.find((p) => p.source === "lookup" || p.source === "fk");
88
100
  const paramName = lookupParam?.name ?? field;
89
- return `/by-${fieldKebab}/:${paramName}`;
101
+ const kebab = inflection.kebabCase(field);
102
+ return `/by-${kebab}/:${paramName}`;
90
103
  }
91
104
  case "function": {
92
- return `/${toKebabCase(method.name)}`;
105
+ return `/${inflection.kebabCase(method.name)}`;
93
106
  }
94
107
  }
95
108
  };
109
+ const stmt = conjure.stmt;
96
110
  /**
97
- * Build the handler function body for a query method.
98
- * In Express, we use req.params, req.query, req.body, res.json()
99
- *
100
- * When validation is enabled, uses schema.parse() for body and converts path params.
111
+ * Get the body schema name for a method if it needs validation.
101
112
  */
102
- const buildHandlerBody = (method, entityName, options) => {
103
- const statements = [];
104
- const callSig = method.callSignature ?? { style: "named" };
105
- const { hasSchemaProvider, bodySchema, pathParams } = options;
106
- // Extract path params with validation if schema provider available
107
- if (pathParams.length > 0 && hasSchemaProvider) {
108
- const firstParam = pathParams[0];
109
- // Use schema builder to validate and coerce path params
110
- // const _params = UserIdParams["~standard"].validate({ id: req.params.id });
111
- // if (_params.issues) return res.status(400).json({ error: _params.issues });
112
- // const { id } = _params.value;
113
- const paramsObject = b.objectExpression([
114
- b.property("init", b.stringLiteral(firstParam.name), b.memberExpression(b.memberExpression(b.identifier("req"), b.identifier("params")), b.identifier(firstParam.name))),
115
- ]);
116
- const paramsSchemaName = `${entityName}${firstParam.name.charAt(0).toUpperCase() + firstParam.name.slice(1)}Params`;
117
- const standardMember = b.memberExpression(b.identifier(paramsSchemaName), b.stringLiteral("~standard"), true);
118
- const validateCall = b.memberExpression(cast.toExpr(standardMember), b.identifier("validate"));
119
- const validationExpr = b.callExpression(validateCall, [paramsObject]);
120
- const validationVar = b.variableDeclarator(b.identifier("_params"), b.awaitExpression(validationExpr));
121
- statements.push(b.variableDeclaration("const", [validationVar]));
122
- // if (_params.issues) return res.status(400).json({ error: _params.issues });
123
- const errorResponse = b.callExpression(b.memberExpression(b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("status")), [b.numericLiteral(400)]), b.identifier("json")), [b.objectExpression([b.property("init", b.stringLiteral("error"), b.memberExpression(b.identifier("_params"), b.identifier("issues")))])]);
124
- const ifStatement = b.ifStatement(b.memberExpression(b.identifier("_params"), b.identifier("issues")), b.blockStatement([b.returnStatement(errorResponse)]));
125
- statements.push(ifStatement);
126
- // Destructure validated params: const { id } = _params.value;
127
- const pattern = b.objectPattern(pathParams.map(p => {
128
- const prop = b.property("init", b.identifier(p.name), b.identifier(p.name));
129
- prop.shorthand = true;
130
- return prop;
131
- }));
132
- statements.push(b.variableDeclaration("const", [
133
- b.variableDeclarator(pattern, b.memberExpression(b.identifier("_params"), b.identifier("value"))),
134
- ]));
113
+ function getBodySchemaName(method, entityName) {
114
+ if (method.kind === "create") {
115
+ return `${entityName}Insert`;
116
+ }
117
+ if (method.kind === "update") {
118
+ return `${entityName}Update`;
135
119
  }
136
- else if (pathParams.length > 0) {
137
- // No schema provider - just extract without validation
138
- const pattern = b.objectPattern(pathParams.map(p => {
120
+ return null;
121
+ }
122
+ function buildHandlerBody(method, inflection) {
123
+ const callSig = method.callSignature ?? { style: "named" };
124
+ const statements = [];
125
+ // Extract path params: const { id } = req.params
126
+ const pathParams = method.params.filter((p) => p.source === "pk" || p.source === "fk" || p.source === "lookup");
127
+ if (pathParams.length > 0) {
128
+ const pattern = b.objectPattern(pathParams.map((p) => {
139
129
  const prop = b.property("init", b.identifier(p.name), b.identifier(p.name));
140
130
  prop.shorthand = true;
141
131
  return prop;
142
132
  }));
143
- statements.push(b.variableDeclaration("const", [
144
- b.variableDeclarator(pattern, b.memberExpression(b.identifier("req"), b.identifier("params"))),
145
- ]));
133
+ statements.push(stmt.const(pathParams.map(p => p.name).join(", "), b.memberExpression(b.identifier("req"), b.identifier("params"))));
146
134
  }
147
135
  // Extract query params: const { limit, offset } = req.query
148
- const queryParams = method.params.filter(p => p.source === "pagination");
136
+ const queryParams = method.params.filter((p) => p.source === "pagination");
149
137
  if (queryParams.length > 0) {
150
- const pattern = b.objectPattern(queryParams.map(p => {
138
+ const pattern = b.objectPattern(queryParams.map((p) => {
151
139
  const prop = b.property("init", b.identifier(p.name), b.identifier(p.name));
152
140
  prop.shorthand = true;
153
141
  return prop;
154
142
  }));
155
- statements.push(b.variableDeclaration("const", [
156
- b.variableDeclarator(pattern, b.memberExpression(b.identifier("req"), b.identifier("query"))),
157
- ]));
143
+ statements.push(stmt.const(queryParams.map(p => p.name).join(", "), b.memberExpression(b.identifier("req"), b.identifier("query"))));
158
144
  }
159
- // Extract body with optional validation
160
- const needsBody = method.params.some(p => p.source === "body") ||
145
+ // Extract body: const body = req.body
146
+ const needsBody = method.params.some((p) => p.source === "body") ||
161
147
  method.kind === "create" ||
162
148
  method.kind === "update" ||
163
- (method.kind === "function" && method.params.some(p => !p.source));
149
+ (method.kind === "function" && method.params.some((p) => !p.source));
164
150
  if (needsBody) {
165
- if (bodySchema && hasSchemaProvider) {
166
- // Validation handled below using Standard Schema interface
167
- }
168
- else {
169
- // const body = req.body
170
- statements.push(stmt.const("body", b.memberExpression(b.identifier("req"), b.identifier("body"))));
171
- }
172
- }
173
- // Validate body using Standard Schema interface (works with zod, valibot, arktype)
174
- if (bodySchema && hasSchemaProvider) {
175
- // const validation = UserInsert["~standard"].validate(req.body);
176
- // if (validation.issues) throw new Error("Validation failed");
177
- // const body = validation.value;
178
- const standardMember = b.memberExpression(b.identifier(bodySchema.schemaName), b.stringLiteral("~standard"), true);
179
- const validateCall = b.memberExpression(cast.toExpr(standardMember), b.identifier("validate"));
180
- const validationExpr = b.callExpression(validateCall, [
181
- b.memberExpression(b.identifier("req"), b.identifier("body")),
182
- ]);
183
- const validationVar = b.variableDeclarator(b.identifier("validation"), b.awaitExpression(validationExpr));
184
- statements.push(b.variableDeclaration("const", [validationVar]));
185
- // if (validation.issues) return res.status(400).json({ error: validation.issues });
186
- const errorResponse = b.callExpression(b.memberExpression(b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("status")), [b.numericLiteral(400)]), b.identifier("json")), [b.objectExpression([b.property("init", b.stringLiteral("error"), b.memberExpression(b.identifier("validation"), b.identifier("issues")))])]);
187
- const ifStatement = b.ifStatement(b.memberExpression(b.identifier("validation"), b.identifier("issues")), b.blockStatement([b.returnStatement(errorResponse)]));
188
- statements.push(ifStatement);
189
- // const body = validation.value;
190
- statements.push(stmt.const("body", b.memberExpression(b.identifier("validation"), b.identifier("value"))));
151
+ statements.push(stmt.const("body", b.memberExpression(b.identifier("req"), b.identifier("body"))));
191
152
  }
192
153
  // Build the function call arguments
193
154
  const args = [];
194
155
  if (callSig.style === "positional") {
195
156
  for (const param of method.params) {
196
- if (param.source === "pk" ||
197
- param.source === "fk" ||
198
- param.source === "lookup" ||
199
- param.source === "pagination") {
200
- args.push(b.identifier(param.name));
157
+ if (needsCoercion(param)) {
158
+ args.push(coerceParam(param.name, param.type));
201
159
  }
202
160
  else if (param.source === "body") {
203
161
  args.push(b.identifier("body"));
@@ -208,18 +166,15 @@ const buildHandlerBody = (method, entityName, options) => {
208
166
  }
209
167
  }
210
168
  else {
211
- const bodyParam = method.params.find(p => p.source === "body");
169
+ const bodyParam = method.params.find((p) => p.source === "body");
212
170
  if (bodyParam && callSig.bodyStyle === "spread") {
213
171
  args.push(b.identifier("body"));
214
172
  }
215
173
  else if (bodyParam && callSig.bodyStyle === "property") {
216
174
  let objBuilder = conjure.obj();
217
175
  for (const param of method.params) {
218
- if (param.source === "pk" ||
219
- param.source === "fk" ||
220
- param.source === "lookup" ||
221
- param.source === "pagination") {
222
- objBuilder = objBuilder.shorthand(param.name);
176
+ if (needsCoercion(param)) {
177
+ objBuilder = objBuilder.prop(param.name, coerceParam(param.name, param.type));
223
178
  }
224
179
  }
225
180
  objBuilder = objBuilder.prop(bodyParam.name, b.identifier("body"));
@@ -228,11 +183,8 @@ const buildHandlerBody = (method, entityName, options) => {
228
183
  else {
229
184
  let objBuilder = conjure.obj();
230
185
  for (const param of method.params) {
231
- if (param.source === "pk" ||
232
- param.source === "fk" ||
233
- param.source === "lookup" ||
234
- param.source === "pagination") {
235
- objBuilder = objBuilder.shorthand(param.name);
186
+ if (needsCoercion(param)) {
187
+ objBuilder = objBuilder.prop(param.name, coerceParam(param.name, param.type));
236
188
  }
237
189
  }
238
190
  if (method.params.length > 0) {
@@ -240,149 +192,210 @@ const buildHandlerBody = (method, entityName, options) => {
240
192
  }
241
193
  }
242
194
  }
243
- // Build: const result = await Queries.queryFn(args)
244
- const queryCall = b.callExpression(b.memberExpression(b.identifier("Queries"), b.identifier(method.name)), args.map(cast.toExpr));
195
+ // Call the query function: const result = await Queries.queryFn(args)
196
+ const queryCall = b.callExpression(b.identifier(method.name), args.map(cast.toExpr));
245
197
  statements.push(stmt.const("result", b.awaitExpression(queryCall)));
246
- // Handle 404 for read/lookup that returns null
198
+ // Handle 404 for read/lookup that returns null/undefined
247
199
  if (method.kind === "read" || (method.kind === "lookup" && method.isUniqueLookup)) {
248
- // if (!result) return res.status(404).json({ error: 'Not found' })
249
- const notFoundResponse = b.callExpression(b.memberExpression(b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("status")), [
250
- b.numericLiteral(404),
251
- ]), b.identifier("json")), [conjure.obj().prop("error", b.stringLiteral("Not found")).build()]);
200
+ const notFoundResponse = b.callExpression(b.memberExpression(b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("status")), [b.numericLiteral(404)]), b.identifier("json")), [
201
+ conjure
202
+ .obj()
203
+ .prop("error", b.stringLiteral("Not found"))
204
+ .build(),
205
+ ]);
252
206
  statements.push(b.ifStatement(b.unaryExpression("!", b.identifier("result")), b.returnStatement(notFoundResponse)));
253
207
  }
254
- // return res.json(result) or res.status(201).json(result) for create
208
+ // Return response: res.json(result) or res.status(201).json(result) for create
255
209
  let responseExpr;
256
210
  if (method.kind === "create") {
257
- // res.status(201).json(result)
258
- responseExpr = b.callExpression(b.memberExpression(b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("status")), [
259
- b.numericLiteral(201),
260
- ]), b.identifier("json")), [b.identifier("result")]);
211
+ responseExpr = b.callExpression(b.memberExpression(b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("status")), [b.numericLiteral(201)]), b.identifier("json")), [b.identifier("result")]);
261
212
  }
262
213
  else {
263
- // res.json(result)
264
- responseExpr = b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("json")), [
265
- b.identifier("result"),
266
- ]);
214
+ responseExpr = b.callExpression(b.memberExpression(b.identifier("res"), b.identifier("json")), [b.identifier("result")]);
267
215
  }
268
216
  statements.push(b.returnStatement(cast.toExpr(responseExpr)));
269
217
  return statements;
270
- };
271
- /**
272
- * Build a single route method call: router.get('/path', handler)
273
- */
274
- const buildRouteCall = (method, entityName, options) => {
218
+ }
219
+ function buildRouteCall(method, entityName, inflection) {
275
220
  const httpMethod = kindToHttpMethod(method.kind);
276
- const path = getRoutePath(method);
277
- // Build handler: async (req, res) => { ... }
278
- const handlerBody = buildHandlerBody(method, entityName, options);
221
+ const path = getRoutePath(method, entityName, inflection);
222
+ const handlerBody = buildHandlerBody(method, inflection);
279
223
  const handler = b.arrowFunctionExpression([b.identifier("req"), b.identifier("res")], b.blockStatement(handlerBody.map(cast.toStmt)));
280
224
  handler.async = true;
281
225
  return { httpMethod, path, handler };
282
- };
283
- // ============================================================================
284
- // Plugin Definition
285
- // ============================================================================
286
- export function httpExpress(config = {}) {
287
- const parsed = S.decodeUnknownSync(HttpExpressConfigSchema)(config);
288
- return definePlugin({
289
- name: "http-express",
290
- kind: "http-routes",
291
- singleton: true,
292
- canProvide: () => true,
293
- requires: () => [{ kind: "queries", params: {} }],
294
- optionalRequires: () => [{ kind: "schemas", params: {} }],
295
- provide: (_params, _deps, ctx) => {
296
- const { outputDir, basePath, header, aggregatorName } = parsed;
297
- const entityNames = ctx.symbols.getEntitiesWithMethods();
298
- if (entityNames.length === 0) {
299
- return;
226
+ }
227
+ /**
228
+ * Generate Express routes for an entity.
229
+ *
230
+ * @param entityName - The entity name
231
+ * @param queries - Query extension metadata
232
+ * @param config - Plugin config
233
+ * @param registry - Symbol registry for recording cross-references
234
+ * @param inflection - Inflection service for naming
235
+ */
236
+ function generateExpressRoutes(entityName, queries, config, registry, inflection) {
237
+ const routesVarName = `${inflect.uncapitalize(entityName)}Routes`;
238
+ let chainExpr = b.callExpression(b.identifier("Router"), []);
239
+ for (const method of queries.methods) {
240
+ const methodCapability = `queries:${entityName}:${getMethodCapabilitySuffix(method, inflection)}`;
241
+ if (registry.has(methodCapability)) {
242
+ registry.import(methodCapability).ref();
243
+ }
244
+ const { httpMethod, path, handler } = buildRouteCall(method, entityName, inflection);
245
+ chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier(httpMethod)), [b.stringLiteral(path), handler]);
246
+ }
247
+ const variableDeclarator = b.variableDeclarator(b.identifier(routesVarName), cast.toExpr(chainExpr));
248
+ const variableDeclaration = b.variableDeclaration("const", [variableDeclarator]);
249
+ const externalImports = [
250
+ { from: "express", names: ["Router"] },
251
+ ];
252
+ return {
253
+ statements: [variableDeclaration],
254
+ externalImports,
255
+ };
256
+ }
257
+ /**
258
+ * Get the capability suffix for a query method.
259
+ * E.g., "findById", "list", "create", "update", "delete", "findByEmail"
260
+ */
261
+ function getMethodCapabilitySuffix(method, inflection) {
262
+ switch (method.kind) {
263
+ case "read":
264
+ return "findById";
265
+ case "list":
266
+ return "list";
267
+ case "create":
268
+ return "create";
269
+ case "update":
270
+ return "update";
271
+ case "delete":
272
+ return "delete";
273
+ case "lookup":
274
+ if (method.lookupField) {
275
+ const pascalField = inflection.pascalCase(method.lookupField);
276
+ return `findBy${pascalField}`;
300
277
  }
301
- const generatedRoutes = [];
302
- for (const entityName of entityNames) {
303
- const entityMethods = ctx.symbols.getEntityMethods(entityName);
304
- if (!entityMethods || entityMethods.methods.length === 0)
278
+ return "lookup";
279
+ case "function":
280
+ return method.name;
281
+ }
282
+ }
283
+ function generateAggregator(entities, config, registry, inflection) {
284
+ const entityEntries = Array.from(entities.entries());
285
+ if (entityEntries.length === 0) {
286
+ return { statements: [], externalImports: [] };
287
+ }
288
+ let chainExpr = b.callExpression(b.identifier("Router"), []);
289
+ const externalImports = [{ from: "express", names: ["Router"] }];
290
+ for (const [entityName, queries] of entityEntries) {
291
+ const routesVarName = `${inflect.uncapitalize(entityName)}Routes`;
292
+ chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("use")), [b.identifier(routesVarName)]);
293
+ const routeCapability = `http-routes:express:${entityName}`;
294
+ if (registry.has(routeCapability)) {
295
+ registry.import(routeCapability).ref();
296
+ }
297
+ }
298
+ const variableDeclarator = b.variableDeclarator(b.identifier("api"), cast.toExpr(chainExpr));
299
+ const variableDeclaration = b.variableDeclaration("const", [variableDeclarator]);
300
+ return {
301
+ statements: [variableDeclaration],
302
+ externalImports,
303
+ };
304
+ }
305
+ export function express(config) {
306
+ const schemaConfig = S.decodeSync(HttpExpressConfigSchema)(config ?? {});
307
+ const resolvedConfig = {
308
+ outputDir: schemaConfig.outputDir,
309
+ basePath: schemaConfig.basePath,
310
+ routesFile: normalizeFileNaming(config?.routesFile, DEFAULT_ROUTES_FILE),
311
+ appFile: normalizeFileNaming(config?.appFile, DEFAULT_APP_FILE),
312
+ };
313
+ return {
314
+ name: PLUGIN_NAME,
315
+ provides: [],
316
+ fileDefaults: [
317
+ {
318
+ pattern: "http-routes:express:",
319
+ outputDir: resolvedConfig.outputDir,
320
+ fileNaming: resolvedConfig.routesFile,
321
+ },
322
+ {
323
+ pattern: "http-routes:express:app",
324
+ outputDir: resolvedConfig.outputDir,
325
+ fileNaming: resolvedConfig.appFile,
326
+ },
327
+ ],
328
+ declare: Effect.gen(function* () {
329
+ const ir = yield* IR;
330
+ const inflection = yield* Inflection;
331
+ const declarations = [];
332
+ for (const entity of ir.entities.values()) {
333
+ if (!isTableEntity(entity))
305
334
  continue;
306
- const pathSegment = entityToPathSegment(entityName);
307
- const filePath = `${outputDir}/${inflect.uncapitalize(entityName)}.ts`;
308
- const routesVarName = `${inflect.uncapitalize(entityName)}Routes`;
309
- const file = ctx.file(filePath);
310
- if (header) {
311
- file.header(header);
312
- }
313
- // Import Router from express
314
- file.import({ kind: "package", names: ["Router"], from: "express" });
315
- // Import queries as namespace
316
- const queriesImportPath = `../${entityMethods.importPath.replace(/\.ts$/, ".js")}`;
317
- file.import({
318
- kind: "relative",
319
- namespace: "Queries",
320
- from: queriesImportPath,
321
- });
322
- // Check if a schema provider is available for body validation
323
- const hasSchemaProvider = ctx.symbols.resolve({
324
- capability: "schemas",
325
- entity: entityName,
326
- shape: "insert",
327
- }) !== undefined;
328
- // Collect body schemas needed for imports
329
- const bodySchemasNeeded = [];
330
- // Build the Express router chain
331
- // Router().get('/path', handler).post('/path', handler)...
332
- let chainExpr = b.callExpression(b.identifier("Router"), []);
333
- for (const method of entityMethods.methods) {
334
- const pathParams = method.params.filter(p => p.source === "pk" || p.source === "fk" || p.source === "lookup");
335
- const bodySchema = getBodySchemaImport(method, entityName);
336
- if (bodySchema && hasSchemaProvider) {
337
- bodySchemasNeeded.push(bodySchema);
338
- }
339
- const { httpMethod, path, handler } = buildRouteCall(method, entityName, {
340
- hasSchemaProvider,
341
- bodySchema,
342
- pathParams,
335
+ if (entity.tags.omit === true)
336
+ continue;
337
+ const hasAnyPermissions = entity.permissions.canSelect ||
338
+ entity.permissions.canInsert ||
339
+ entity.permissions.canUpdate ||
340
+ entity.permissions.canDelete;
341
+ if (hasAnyPermissions) {
342
+ declarations.push({
343
+ name: `${inflect.uncapitalize(entity.name)}Routes`,
344
+ capability: `http-routes:express:${entity.name}`,
345
+ baseEntityName: entity.name,
343
346
  });
344
- chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier(httpMethod)), [b.stringLiteral(path), handler]);
345
347
  }
346
- // Import schemas needed for body validation
347
- for (const schema of bodySchemasNeeded) {
348
- file.import({
349
- kind: "symbol",
350
- ref: { capability: "schemas", entity: schema.entity, shape: schema.shape },
351
- });
348
+ }
349
+ declarations.push({
350
+ name: "api",
351
+ capability: "http-routes:express:app",
352
+ });
353
+ return declarations;
354
+ }),
355
+ render: Effect.gen(function* () {
356
+ const ir = yield* IR;
357
+ const registry = yield* SymbolRegistry;
358
+ const inflection = yield* Inflection;
359
+ const rendered = [];
360
+ const entityQueries = new Map();
361
+ const queryCapabilities = registry.query("queries:");
362
+ for (const decl of queryCapabilities) {
363
+ const parts = decl.capability.split(":");
364
+ if (parts.length !== 3)
365
+ continue;
366
+ const entityName = parts[2];
367
+ const metadata = registry.getMetadata(decl.capability);
368
+ if (metadata && typeof metadata === "object" && "methods" in metadata) {
369
+ entityQueries.set(entityName, metadata);
352
370
  }
353
- const exportStmt = conjure.export.const(routesVarName, chainExpr);
354
- file.ast(conjure.program(exportStmt)).emit();
355
- generatedRoutes.push({
356
- fileName: `${inflect.uncapitalize(entityName)}.js`,
357
- exportName: routesVarName,
358
- pathSegment,
371
+ }
372
+ for (const [entityName, queries] of entityQueries) {
373
+ const entity = ir.entities.get(entityName);
374
+ if (!entity || !isTableEntity(entity))
375
+ continue;
376
+ const capability = `http-routes:express:${entityName}`;
377
+ const { statements, externalImports } = registry.forSymbol(capability, () => generateExpressRoutes(entityName, queries, resolvedConfig, registry, inflection));
378
+ rendered.push({
379
+ name: `${inflect.uncapitalize(entityName)}Routes`,
380
+ capability,
381
+ node: statements[0],
382
+ exports: "named",
383
+ externalImports,
359
384
  });
360
385
  }
361
- // Generate aggregator index.ts
362
- if (generatedRoutes.length > 0) {
363
- const indexPath = `${outputDir}/index.ts`;
364
- const indexFile = ctx.file(indexPath);
365
- if (header) {
366
- indexFile.header(header);
367
- }
368
- indexFile.import({ kind: "package", names: ["Router"], from: "express" });
369
- for (const route of generatedRoutes) {
370
- indexFile.import({
371
- kind: "relative",
372
- names: [route.exportName],
373
- from: `./${route.fileName}`,
374
- });
375
- }
376
- // Build: Router().use('/users', userRoutes).use('/posts', postRoutes)...
377
- let chainExpr = b.callExpression(b.identifier("Router"), []);
378
- // Add routes with their path prefixes
379
- for (const route of generatedRoutes) {
380
- chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("use")), [b.stringLiteral(`${basePath}/${route.pathSegment}`), b.identifier(route.exportName)]);
381
- }
382
- const exportStmt = conjure.export.const(aggregatorName, chainExpr);
383
- indexFile.ast(conjure.program(exportStmt)).emit();
386
+ if (entityQueries.size > 0) {
387
+ const appCapability = "http-routes:express:app";
388
+ const { statements, externalImports } = registry.forSymbol(appCapability, () => generateAggregator(entityQueries, resolvedConfig, registry, inflection));
389
+ rendered.push({
390
+ name: "api",
391
+ capability: appCapability,
392
+ node: statements[0],
393
+ exports: "default",
394
+ externalImports,
395
+ });
384
396
  }
385
- },
386
- });
397
+ return rendered;
398
+ }),
399
+ };
387
400
  }
388
401
  //# sourceMappingURL=http-express.js.map