@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,51 +1,61 @@
1
1
  /**
2
- * HTTP Hono Plugin - Generate Hono route handlers from query plugins
2
+ * Hono HTTP Plugin - Generates Hono 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 Hono 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, valibot, etc.).
6
7
  *
7
- * Schema validation is provided via @hono/standard-validator when a schema plugin
8
- * (zod, valibot, arktype) is configured. Without a schema plugin, routes are
9
- * generated without validation middleware.
8
+ * Uses the SymbolRegistry to resolve query functions and optionally
9
+ * schema symbols for request validation.
10
+ *
11
+ * For param/query validation, uses the SchemaBuilder service when available.
12
+ * For body validation, imports schema symbols via registry.
13
+ *
14
+ * Routes use @hono/standard-validator for middleware-based validation.
15
+ */
16
+ import { Effect, Schema as S } from "effect";
17
+ import { IR } from "../services/ir.js";
18
+ import { Inflection } from "../services/inflection.js";
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 = "hono-http";
25
+ const DEFAULT_OUTPUT_DIR = "";
26
+ const DEFAULT_ROUTES_FILE = "routes.ts";
27
+ const DEFAULT_APP_FILE = "routes.ts";
28
+ /**
29
+ * Coerce a URL param (always string) to the expected type.
30
+ * Returns an expression that wraps the identifier with the appropriate coercion.
31
+ */
32
+ function coerceParam(paramName, paramType) {
33
+ const ident = b.identifier(paramName);
34
+ const lowerType = paramType.toLowerCase();
35
+ if (lowerType === "number" || lowerType === "int" || lowerType === "integer" || lowerType === "bigint") {
36
+ return b.callExpression(b.identifier("Number"), [ident]);
37
+ }
38
+ if (lowerType === "date" || lowerType.includes("timestamp") || lowerType.includes("datetime")) {
39
+ return b.newExpression(b.identifier("Date"), [ident]);
40
+ }
41
+ if (lowerType === "boolean" || lowerType === "bool") {
42
+ return b.binaryExpression("===", ident, b.stringLiteral("true"));
43
+ }
44
+ return ident;
45
+ }
46
+ function needsCoercion(param) {
47
+ return (param.source === "pk" ||
48
+ param.source === "fk" ||
49
+ param.source === "lookup" ||
50
+ param.source === "pagination");
51
+ }
52
+ /**
53
+ * Schema-validated portion of the config (simple types only).
10
54
  */
11
- import { Schema as S } from "effect";
12
- import { definePlugin } from "../services/plugin.js";
13
- import { conjure, cast } from "../lib/conjure.js";
14
- import { inflect } from "../services/inflection.js";
15
- import { SCHEMA_BUILDER_KIND, } from "../ir/extensions/schema-builder.js";
16
- import { getTableEntities } from "../ir/semantic-ir.js";
17
- import { TsType } from "../services/pg-types.js";
18
- const { b, stmt } = conjure;
19
- // ============================================================================
20
- // Configuration
21
- // ============================================================================
22
55
  const HttpHonoConfigSchema = S.Struct({
23
- /** Output directory for generated route files. Default: "routes" */
24
- outputDir: S.optionalWith(S.String, { default: () => "routes" }),
25
- /** Base path for all routes. Default: "/api" */
26
- basePath: S.optionalWith(S.String, { default: () => "/api" }),
27
- /** Header content to prepend to each generated file */
28
- header: S.optional(S.String),
29
- /**
30
- * Name of the aggregated router export.
31
- * Default: "api"
32
- */
33
- aggregatorName: S.optionalWith(S.String, { default: () => "api" }),
56
+ outputDir: S.optionalWith(S.String, { default: () => DEFAULT_OUTPUT_DIR }),
57
+ basePath: S.optionalWith(S.String, { default: () => "" }),
34
58
  });
35
- // ============================================================================
36
- // String Helpers
37
- // ============================================================================
38
- /** Convert PascalCase/camelCase to kebab-case */
39
- const toKebabCase = (str) => str
40
- .replace(/([a-z])([A-Z])/g, "$1-$2")
41
- .replace(/_/g, "-")
42
- .toLowerCase();
43
- /** Convert entity name to URL path segment (kebab-case plural) */
44
- const entityToPathSegment = (entityName) => inflect.pluralize(toKebabCase(entityName));
45
- // ============================================================================
46
- // Route Generation Helpers
47
- // ============================================================================
48
- /** Map query method kind to HTTP method */
49
59
  const kindToHttpMethod = (kind) => {
50
60
  switch (kind) {
51
61
  case "read":
@@ -62,8 +72,7 @@ const kindToHttpMethod = (kind) => {
62
72
  return "post";
63
73
  }
64
74
  };
65
- /** Get the route path for a method */
66
- const getRoutePath = (method) => {
75
+ const getRoutePath = (method, entityName, inflection) => {
67
76
  switch (method.kind) {
68
77
  case "read":
69
78
  case "update":
@@ -73,113 +82,36 @@ const getRoutePath = (method) => {
73
82
  return `/:${paramName}`;
74
83
  }
75
84
  case "list":
85
+ if (/ListBy/i.test(method.name) || /listBy/i.test(method.name)) {
86
+ const match = method.name.match(/(?:ListBy|listBy)(.+)/i);
87
+ if (match && match[1]) {
88
+ const columnKebab = inflection.kebabCase(match[1]);
89
+ return `/by-${columnKebab}`;
90
+ }
91
+ }
92
+ return "/";
76
93
  case "create":
77
94
  return "/";
78
95
  case "lookup": {
79
96
  const field = method.lookupField ?? "field";
80
- const fieldKebab = toKebabCase(field);
97
+ const fieldKebab = inflection.kebabCase(field);
81
98
  const lookupParam = method.params.find((p) => p.source === "lookup" || p.source === "fk");
82
99
  const paramName = lookupParam?.name ?? field;
83
100
  return `/by-${fieldKebab}/:${paramName}`;
84
101
  }
85
102
  case "function": {
86
- return `/${toKebabCase(method.name)}`;
103
+ return `/${inflection.kebabCase(method.name)}`;
87
104
  }
88
105
  }
89
106
  };
90
- /**
91
- * Build sValidator('target', schema) middleware call
92
- */
93
- const buildSValidator = (target, schema) => b.callExpression(b.identifier("sValidator"), [b.stringLiteral(target), cast.toExpr(schema)]);
94
- /**
95
- * Determine if a method needs body validation and which schema to use.
96
- */
97
- const getBodySchemaImport = (method, entityName) => {
98
- if (method.kind === "create") {
99
- return { entity: entityName, shape: "insert", schemaName: `${entityName}Insert` };
100
- }
101
- if (method.kind === "update") {
102
- return { entity: entityName, shape: "update", schemaName: `${entityName}Update` };
103
- }
104
- return null;
105
- };
106
- // ============================================================================
107
- // Handler Body Generation
108
- // ============================================================================
109
- /**
110
- * Build the handler function body for a query method.
111
- *
112
- * When hasValidation is true, uses c.req.valid() for validated data.
113
- * Otherwise falls back to manual extraction via c.req.param/query/json.
114
- */
115
- const buildHandlerBody = (method, hasValidation) => {
116
- const statements = [];
107
+ function buildHandlerBody(method) {
117
108
  const callSig = method.callSignature ?? { style: "named" };
118
- const pathParams = method.params.filter((p) => p.source === "pk" || p.source === "fk" || p.source === "lookup");
119
- const queryParams = method.params.filter((p) => p.source === "pagination");
120
- const needsBody = method.params.some((p) => p.source === "body") ||
121
- method.kind === "create" ||
122
- method.kind === "update" ||
123
- (method.kind === "function" && method.params.some((p) => !p.source));
124
- if (hasValidation) {
125
- // Use c.req.valid() for validated data
126
- if (pathParams.length > 0) {
127
- // const { id, slug } = c.req.valid('param')
128
- const paramPattern = b.objectPattern(pathParams.map((p) => {
129
- const prop = b.property("init", b.identifier(p.name), b.identifier(p.name));
130
- prop.shorthand = true;
131
- return prop;
132
- }));
133
- const validCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("c"), b.identifier("req")), b.identifier("valid")), [b.stringLiteral("param")]);
134
- statements.push(b.variableDeclaration("const", [b.variableDeclarator(paramPattern, validCall)]));
135
- }
136
- if (queryParams.length > 0) {
137
- // const { limit, offset } = c.req.valid('query')
138
- const queryPattern = b.objectPattern(queryParams.map((p) => {
139
- const prop = b.property("init", b.identifier(p.name), b.identifier(p.name));
140
- prop.shorthand = true;
141
- return prop;
142
- }));
143
- const validCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("c"), b.identifier("req")), b.identifier("valid")), [b.stringLiteral("query")]);
144
- statements.push(b.variableDeclaration("const", [b.variableDeclarator(queryPattern, validCall)]));
145
- }
146
- if (needsBody) {
147
- // const body = c.req.valid('json')
148
- const validCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("c"), b.identifier("req")), b.identifier("valid")), [b.stringLiteral("json")]);
149
- statements.push(stmt.const("body", validCall));
150
- }
151
- }
152
- else {
153
- // No validation - manual extraction
154
- for (const param of pathParams) {
155
- const paramCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("c"), b.identifier("req")), b.identifier("param")), [b.stringLiteral(param.name)]);
156
- statements.push(stmt.const(param.name, paramCall));
157
- }
158
- for (const param of queryParams) {
159
- const queryCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("c"), b.identifier("req")), b.identifier("query")), [b.stringLiteral(param.name)]);
160
- // Parse as number if needed
161
- if (param.type === TsType.Number) {
162
- const parsed = b.callExpression(b.identifier("Number"), [queryCall]);
163
- statements.push(stmt.const(param.name, parsed));
164
- }
165
- else {
166
- statements.push(stmt.const(param.name, queryCall));
167
- }
168
- }
169
- if (needsBody) {
170
- const jsonCall = b.callExpression(b.memberExpression(b.memberExpression(b.identifier("c"), b.identifier("req")), b.identifier("json")), []);
171
- statements.push(stmt.const("body", b.awaitExpression(jsonCall)));
172
- }
173
- }
174
- // Build the function call arguments
109
+ const statements = [];
175
110
  const args = [];
176
111
  if (callSig.style === "positional") {
177
112
  for (const param of method.params) {
178
- if (param.source === "pk" ||
179
- param.source === "fk" ||
180
- param.source === "lookup" ||
181
- param.source === "pagination") {
182
- args.push(b.identifier(param.name));
113
+ if (needsCoercion(param)) {
114
+ args.push(coerceParam(param.name, param.type));
183
115
  }
184
116
  else if (param.source === "body") {
185
117
  args.push(b.identifier("body"));
@@ -197,11 +129,8 @@ const buildHandlerBody = (method, hasValidation) => {
197
129
  else if (bodyParam && callSig.bodyStyle === "property") {
198
130
  let objBuilder = conjure.obj();
199
131
  for (const param of method.params) {
200
- if (param.source === "pk" ||
201
- param.source === "fk" ||
202
- param.source === "lookup" ||
203
- param.source === "pagination") {
204
- objBuilder = objBuilder.shorthand(param.name);
132
+ if (needsCoercion(param)) {
133
+ objBuilder = objBuilder.prop(param.name, coerceParam(param.name, param.type));
205
134
  }
206
135
  }
207
136
  objBuilder = objBuilder.prop(bodyParam.name, b.identifier("body"));
@@ -210,11 +139,8 @@ const buildHandlerBody = (method, hasValidation) => {
210
139
  else {
211
140
  let objBuilder = conjure.obj();
212
141
  for (const param of method.params) {
213
- if (param.source === "pk" ||
214
- param.source === "fk" ||
215
- param.source === "lookup" ||
216
- param.source === "pagination") {
217
- objBuilder = objBuilder.shorthand(param.name);
142
+ if (needsCoercion(param)) {
143
+ objBuilder = objBuilder.prop(param.name, coerceParam(param.name, param.type));
218
144
  }
219
145
  }
220
146
  if (method.params.length > 0) {
@@ -222,15 +148,16 @@ const buildHandlerBody = (method, hasValidation) => {
222
148
  }
223
149
  }
224
150
  }
225
- // Build: const result = await Queries.queryFn(args)
226
- const queryCall = b.callExpression(b.memberExpression(b.identifier("Queries"), b.identifier(method.name)), args.map(cast.toExpr));
227
- statements.push(stmt.const("result", b.awaitExpression(queryCall)));
228
- // Handle 404 for read/lookup that returns null
151
+ const queryCall = b.callExpression(b.identifier(method.name), args.map(cast.toExpr));
152
+ const awaitExpr = b.awaitExpression(queryCall);
153
+ statements.push(conjure.stmt.const("result", awaitExpr));
229
154
  if (method.kind === "read" || (method.kind === "lookup" && method.isUniqueLookup)) {
230
- const notFoundResponse = b.callExpression(b.memberExpression(b.identifier("c"), b.identifier("json")), [conjure.obj().prop("error", b.stringLiteral("Not found")).build(), b.numericLiteral(404)]);
155
+ const notFoundResponse = b.callExpression(b.memberExpression(b.identifier("c"), b.identifier("json")), [
156
+ conjure.obj().prop("error", b.stringLiteral("Not found")).build(),
157
+ b.numericLiteral(404),
158
+ ].map(cast.toExpr));
231
159
  statements.push(b.ifStatement(b.unaryExpression("!", b.identifier("result")), b.returnStatement(notFoundResponse)));
232
160
  }
233
- // return c.json(result) or c.json(result, 201) for create
234
161
  const statusCode = method.kind === "create" ? 201 : undefined;
235
162
  const jsonArgs = [b.identifier("result")];
236
163
  if (statusCode) {
@@ -239,215 +166,264 @@ const buildHandlerBody = (method, hasValidation) => {
239
166
  const jsonResponse = b.callExpression(b.memberExpression(b.identifier("c"), b.identifier("json")), jsonArgs.map(cast.toExpr));
240
167
  statements.push(b.returnStatement(jsonResponse));
241
168
  return statements;
242
- };
243
- // ============================================================================
244
- // Route Validation Builders
245
- // ============================================================================
169
+ }
170
+ const stmt = conjure.stmt;
171
+ function getBodySchemaName(method, entityName) {
172
+ if (method.kind === "create") {
173
+ return `${entityName}Insert`;
174
+ }
175
+ if (method.kind === "update") {
176
+ return `${entityName}Update`;
177
+ }
178
+ return null;
179
+ }
246
180
  /**
247
- * Build validators for a route method.
248
- *
249
- * Returns:
250
- * - validators: Array of sValidator() middleware expressions
251
- * - needsSValidator: Whether to import sValidator from @hono/standard-validator
252
- * - bodySchema: Body schema symbol import info (for symbol registry lookup)
253
- * - schemaBuilderImport: Import spec for the schema library (zod, valibot, etc.)
181
+ * Build sValidator('target', schema) middleware call.
182
+ */
183
+ function buildSValidator(target, schema) {
184
+ return b.callExpression(b.identifier("sValidator"), [b.stringLiteral(target), cast.toExpr(schema)]);
185
+ }
186
+ /**
187
+ * Build a single route with optional validation middleware.
254
188
  */
255
- const buildRouteValidators = (method, entityName, requestSchema, hasSchemaProvider) => {
189
+ function buildRouteCall(method, entityName, inflection, schemaBuilder) {
190
+ const httpMethod = kindToHttpMethod(method.kind);
191
+ const path = getRoutePath(method, entityName, inflection);
256
192
  const validators = [];
257
- let needsSValidator = false;
258
- let schemaBuilderImport = null;
259
- // Build param validator for path parameters
260
193
  const pathParams = method.params.filter((p) => p.source === "pk" || p.source === "fk" || p.source === "lookup");
261
- if (pathParams.length > 0) {
262
- const schemaResult = requestSchema(pathParams);
263
- if (schemaResult) {
264
- validators.push(buildSValidator("param", schemaResult.ast));
265
- needsSValidator = true;
266
- schemaBuilderImport = schemaResult.importSpec;
194
+ if (pathParams.length > 0 && schemaBuilder) {
195
+ const request = { variant: "params", params: pathParams };
196
+ const result = schemaBuilder.build(request);
197
+ if (result) {
198
+ validators.push(buildSValidator("param", result.ast));
267
199
  }
268
200
  }
269
- // Build query validator for pagination parameters
270
201
  const queryParams = method.params.filter((p) => p.source === "pagination");
271
- if (queryParams.length > 0) {
272
- const schemaResult = requestSchema(queryParams);
273
- if (schemaResult) {
274
- validators.push(buildSValidator("query", schemaResult.ast));
275
- needsSValidator = true;
276
- if (!schemaBuilderImport) {
277
- schemaBuilderImport = schemaResult.importSpec;
278
- }
202
+ if (queryParams.length > 0 && schemaBuilder) {
203
+ const request = { variant: "query", params: queryParams };
204
+ const result = schemaBuilder.build(request);
205
+ if (result) {
206
+ validators.push(buildSValidator("query", result.ast));
279
207
  }
280
208
  }
281
- // Body validation - use imported schema from schema provider
282
- const bodySchemaInfo = getBodySchemaImport(method, entityName);
283
- let bodySchema = null;
284
- if (bodySchemaInfo && hasSchemaProvider) {
285
- // Import schema from schema provider and use it
286
- bodySchema = bodySchemaInfo;
287
- validators.push(buildSValidator("json", b.identifier(bodySchema.schemaName)));
288
- needsSValidator = true;
209
+ const bodySchemaName = getBodySchemaName(method, entityName);
210
+ if (bodySchemaName) {
211
+ validators.push(buildSValidator("json", b.identifier(bodySchemaName)));
289
212
  }
290
- return { validators, needsSValidator, bodySchema, schemaBuilderImport };
291
- };
292
- /**
293
- * Build a single route with optional validation middleware.
294
- */
295
- const buildRouteCall = (method, entityName, requestSchema, hasSchemaProvider) => {
296
- const httpMethod = kindToHttpMethod(method.kind);
297
- const path = getRoutePath(method);
298
- const { validators, needsSValidator, bodySchema, schemaBuilderImport } = buildRouteValidators(method, entityName, requestSchema, hasSchemaProvider);
299
- // Build handler with or without validation
300
- const hasValidation = validators.length > 0;
301
- const handlerBody = buildHandlerBody(method, hasValidation);
213
+ const handlerBody = buildHandlerBody(method);
302
214
  const handler = b.arrowFunctionExpression([b.identifier("c")], b.blockStatement(handlerBody.map(cast.toStmt)));
303
215
  handler.async = true;
304
- return { httpMethod, path, handler, validators, needsSValidator, bodySchema, schemaBuilderImport };
305
- };
306
- // ============================================================================
307
- // Plugin Definition
308
- // ============================================================================
309
- export function httpHono(config = {}) {
310
- const parsed = S.decodeUnknownSync(HttpHonoConfigSchema)(config);
311
- return definePlugin({
312
- name: "http-hono",
313
- kind: "http-routes",
314
- singleton: true,
315
- canProvide: () => true,
316
- requires: () => [{ kind: "queries", params: {} }],
317
- optionalRequires: () => [{ kind: "schemas", params: {} }],
318
- provide: (_params, _deps, ctx) => {
319
- const { outputDir, basePath, header, aggregatorName } = parsed;
320
- const entityNames = ctx.symbols.getEntitiesWithMethods();
321
- if (entityNames.length === 0) {
322
- return;
216
+ return { httpMethod, path, handler, validators, bodySchemaName };
217
+ }
218
+ /**
219
+ * Generate Hono routes for an entity.
220
+ */
221
+ function generateHonoRoutes(entityName, queries, config, registry, inflection) {
222
+ const routesVarName = inflection.variableName(entityName, "HonoRoutes");
223
+ let chainExpr = b.newExpression(b.identifier("Hono"), []);
224
+ const schemaCapabilities = [];
225
+ for (const method of queries.methods) {
226
+ const methodCapability = `queries:${entityName}:${getMethodCapabilitySuffix(method, inflection)}`;
227
+ if (registry.has(methodCapability)) {
228
+ registry.import(methodCapability).ref();
229
+ }
230
+ const schemaBuilder = getSchemaBuilder(registry);
231
+ const { httpMethod, path, handler, validators, bodySchemaName } = buildRouteCall(method, entityName, inflection, schemaBuilder);
232
+ if (bodySchemaName) {
233
+ const schemaCapability = `schema:${bodySchemaName}`;
234
+ if (registry.has(schemaCapability)) {
235
+ registry.import(schemaCapability).ref();
236
+ schemaCapabilities.push(schemaCapability);
237
+ }
238
+ }
239
+ const callArgs = [b.stringLiteral(path), ...validators, handler];
240
+ chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier(httpMethod)), callArgs.map(cast.toExpr));
241
+ }
242
+ const variableDeclarator = b.variableDeclarator(b.identifier(routesVarName), cast.toExpr(chainExpr));
243
+ const variableDeclaration = b.variableDeclaration("const", [variableDeclarator]);
244
+ const externalImports = [{ from: "hono", names: ["Hono"] }];
245
+ const needsSValidator = schemaCapabilities.length > 0 ||
246
+ queries.methods.some(m => m.params.some(p => p.source === "pk" || p.source === "fk" || p.source === "lookup" || p.source === "pagination"));
247
+ return {
248
+ statements: [variableDeclaration],
249
+ externalImports,
250
+ needsSValidator,
251
+ };
252
+ }
253
+ /**
254
+ * Get the capability suffix for a query method.
255
+ */
256
+ function getMethodCapabilitySuffix(method, inflection) {
257
+ switch (method.kind) {
258
+ case "read":
259
+ return "findById";
260
+ case "list":
261
+ return "list";
262
+ case "create":
263
+ return "create";
264
+ case "update":
265
+ return "update";
266
+ case "delete":
267
+ return "delete";
268
+ case "lookup":
269
+ if (method.lookupField) {
270
+ const pascalField = inflection.pascalCase(method.lookupField);
271
+ return `findBy${pascalField}`;
323
272
  }
324
- const generatedRoutes = [];
325
- const tableEntities = getTableEntities(ctx.ir);
326
- for (const entityName of entityNames) {
327
- const entityMethods = ctx.symbols.getEntityMethods(entityName);
328
- if (!entityMethods || entityMethods.methods.length === 0)
273
+ return "lookup";
274
+ case "function":
275
+ return method.name;
276
+ }
277
+ }
278
+ /**
279
+ * Get the schema builder from registry if available.
280
+ */
281
+ function getSchemaBuilder(registry) {
282
+ const schemaBuilders = registry.query("schema:").filter(decl => decl.capability.endsWith(":builder"));
283
+ if (schemaBuilders.length === 0)
284
+ return undefined;
285
+ const metadata = registry.getMetadata(schemaBuilders[0].capability);
286
+ if (metadata && typeof metadata === "object" && "builder" in metadata) {
287
+ return metadata.builder;
288
+ }
289
+ return undefined;
290
+ }
291
+ function generateAggregator(entities, config, registry, inflection) {
292
+ const entityEntries = Array.from(entities.entries());
293
+ if (entityEntries.length === 0) {
294
+ return { statements: [], externalImports: [] };
295
+ }
296
+ let chainExpr = b.newExpression(b.identifier("Hono"), []);
297
+ const basePath = config.basePath.replace(/^\/+|\/+$/g, "");
298
+ if (basePath) {
299
+ chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("basePath")), [b.stringLiteral(`/${basePath}`)].map(cast.toExpr));
300
+ }
301
+ const externalImports = [{ from: "hono", names: ["Hono"] }];
302
+ for (const [entityName] of entityEntries) {
303
+ const routesVarName = inflection.variableName(entityName, "HonoRoutes");
304
+ const routeCapability = `http-routes:hono:${entityName}`;
305
+ const prefix = inflection.entityRoutePath(entityName);
306
+ chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("route")), [b.stringLiteral(prefix), b.identifier(routesVarName)].map(cast.toExpr));
307
+ if (registry.has(routeCapability)) {
308
+ registry.import(routeCapability).ref();
309
+ }
310
+ }
311
+ const variableDeclarator = b.variableDeclarator(b.identifier("app"), cast.toExpr(chainExpr));
312
+ const variableDeclaration = b.variableDeclaration("const", [variableDeclarator]);
313
+ return {
314
+ statements: [variableDeclaration],
315
+ externalImports,
316
+ };
317
+ }
318
+ export function hono(config) {
319
+ const schemaConfig = S.decodeSync(HttpHonoConfigSchema)(config ?? {});
320
+ const resolvedConfig = {
321
+ outputDir: schemaConfig.outputDir,
322
+ basePath: schemaConfig.basePath,
323
+ routesFile: normalizeFileNaming(config?.routesFile, DEFAULT_ROUTES_FILE),
324
+ appFile: normalizeFileNaming(config?.appFile, DEFAULT_APP_FILE),
325
+ };
326
+ return {
327
+ name: PLUGIN_NAME,
328
+ provides: [],
329
+ fileDefaults: [
330
+ {
331
+ pattern: "http-routes:hono:",
332
+ outputDir: resolvedConfig.outputDir,
333
+ fileNaming: resolvedConfig.routesFile,
334
+ },
335
+ {
336
+ pattern: "http-routes:hono:app",
337
+ outputDir: resolvedConfig.outputDir,
338
+ fileNaming: resolvedConfig.appFile,
339
+ },
340
+ ],
341
+ declare: Effect.gen(function* () {
342
+ const ir = yield* IR;
343
+ const inflection = yield* Inflection;
344
+ const declarations = [];
345
+ for (const entity of ir.entities.values()) {
346
+ if (!isTableEntity(entity))
329
347
  continue;
330
- const entity = tableEntities.find((e) => e.name === entityName);
331
- const pathSegment = entityToPathSegment(entityName);
332
- const filePath = `${outputDir}/${inflect.uncapitalize(entityName)}.ts`;
333
- const routesVarName = `${inflect.uncapitalize(entityName)}Routes`;
334
- const file = ctx.file(filePath);
335
- if (header) {
336
- file.header(header);
337
- }
338
- // Import Hono
339
- file.import({ kind: "package", names: ["Hono"], from: "hono" });
340
- // Import queries as namespace
341
- const queriesImportPath = `../${entityMethods.importPath.replace(/\.ts$/, ".js")}`;
342
- file.import({
343
- kind: "relative",
344
- namespace: "Queries",
345
- from: queriesImportPath,
346
- });
347
- // Create a schema builder function that requests from the service registry
348
- const requestSchema = (params) => {
349
- if (params.length === 0)
350
- return undefined;
351
- try {
352
- const request = { variant: "params", params };
353
- return ctx.request(SCHEMA_BUILDER_KIND, request);
354
- }
355
- catch {
356
- // No schema-builder registered, skip validation
357
- return undefined;
358
- }
359
- };
360
- // Check if a schema provider is available for body validation
361
- // by checking if insert schema is registered in symbol registry
362
- const hasSchemaProvider = ctx.symbols.resolve({
363
- capability: "schemas",
364
- entity: entityName,
365
- shape: "insert",
366
- }) !== undefined;
367
- // Build the Hono route chain
368
- let chainExpr = b.newExpression(b.identifier("Hono"), []);
369
- let fileNeedsSValidator = false;
370
- const bodySchemaImports = [];
371
- let schemaLibraryImport = null;
372
- for (const method of entityMethods.methods) {
373
- const { httpMethod, path, handler, validators, needsSValidator, bodySchema, schemaBuilderImport } = buildRouteCall(method, entityName, requestSchema, hasSchemaProvider);
374
- if (needsSValidator)
375
- fileNeedsSValidator = true;
376
- if (bodySchema)
377
- bodySchemaImports.push(bodySchema);
378
- if (schemaBuilderImport)
379
- schemaLibraryImport = schemaBuilderImport;
380
- // Build route call: .get('/path', validator1, validator2, handler)
381
- const callArgs = [b.stringLiteral(path), ...validators, handler];
382
- chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier(httpMethod)), callArgs.map(cast.toExpr));
383
- }
384
- // Add imports based on what we need
385
- if (fileNeedsSValidator) {
386
- file.import({ kind: "package", names: ["sValidator"], from: "@hono/standard-validator" });
387
- }
388
- // Import schema library (e.g., { z } from 'zod') if using schema-builder
389
- if (schemaLibraryImport) {
390
- if (schemaLibraryImport.names) {
391
- file.import({
392
- kind: "package",
393
- names: [...schemaLibraryImport.names],
394
- from: schemaLibraryImport.from,
395
- });
396
- }
397
- else if (schemaLibraryImport.namespace) {
398
- file.import({
399
- kind: "package",
400
- namespace: schemaLibraryImport.namespace,
401
- from: schemaLibraryImport.from,
402
- });
403
- }
404
- }
405
- // Import body schemas from schema plugins via symbol registry
406
- for (const schemaImport of bodySchemaImports) {
407
- file.import({
408
- kind: "symbol",
409
- ref: {
410
- capability: "schemas",
411
- entity: schemaImport.entity,
412
- shape: schemaImport.shape,
413
- },
348
+ if (entity.tags.omit === true)
349
+ continue;
350
+ const hasAnyPermissions = entity.permissions.canSelect ||
351
+ entity.permissions.canInsert ||
352
+ entity.permissions.canUpdate ||
353
+ entity.permissions.canDelete;
354
+ if (hasAnyPermissions) {
355
+ declarations.push({
356
+ name: inflection.variableName(entity.name, "HonoRoutes"),
357
+ capability: `http-routes:hono:${entity.name}`,
358
+ baseEntityName: entity.name,
414
359
  });
415
360
  }
416
- const exportStmt = conjure.export.const(routesVarName, chainExpr);
417
- file.ast(conjure.program(exportStmt)).emit();
418
- generatedRoutes.push({
419
- fileName: `${inflect.uncapitalize(entityName)}.js`,
420
- exportName: routesVarName,
421
- pathSegment,
422
- });
423
361
  }
424
- // Generate aggregator index.ts
425
- if (generatedRoutes.length > 0) {
426
- const indexPath = `${outputDir}/index.ts`;
427
- const indexFile = ctx.file(indexPath);
428
- if (header) {
429
- indexFile.header(header);
430
- }
431
- indexFile.import({ kind: "package", names: ["Hono"], from: "hono" });
432
- for (const route of generatedRoutes) {
433
- indexFile.import({
434
- kind: "relative",
435
- names: [route.exportName],
436
- from: `./${route.fileName}`,
437
- });
362
+ declarations.push({
363
+ name: "honoApp",
364
+ capability: "http-routes:hono:app",
365
+ });
366
+ return declarations;
367
+ }),
368
+ render: Effect.gen(function* () {
369
+ const ir = yield* IR;
370
+ const registry = yield* SymbolRegistry;
371
+ const inflection = yield* Inflection;
372
+ const rendered = [];
373
+ const entityQueries = new Map();
374
+ const queryCapabilities = registry.query("queries:");
375
+ for (const decl of queryCapabilities) {
376
+ const parts = decl.capability.split(":");
377
+ if (parts.length !== 3)
378
+ continue;
379
+ const entityName = parts[2];
380
+ const metadata = registry.getMetadata(decl.capability);
381
+ if (metadata && typeof metadata === "object" && "methods" in metadata) {
382
+ entityQueries.set(entityName, metadata);
438
383
  }
439
- // Build: new Hono().basePath('/api').route('/users', userRoutes).route('/posts', postRoutes)...
440
- let chainExpr = b.newExpression(b.identifier("Hono"), []);
441
- // Add basePath
442
- chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("basePath")), [b.stringLiteral(basePath)]);
443
- // Add routes
444
- for (const route of generatedRoutes) {
445
- chainExpr = b.callExpression(b.memberExpression(cast.toExpr(chainExpr), b.identifier("route")), [b.stringLiteral(`/${route.pathSegment}`), b.identifier(route.exportName)]);
384
+ }
385
+ const allNeedsSValidator = new Set();
386
+ for (const [entityName, queries] of entityQueries) {
387
+ const entity = ir.entities.get(entityName);
388
+ if (!entity || !isTableEntity(entity))
389
+ continue;
390
+ const capability = `http-routes:hono:${entityName}`;
391
+ const { statements, externalImports, needsSValidator } = registry.forSymbol(capability, () => generateHonoRoutes(entityName, queries, resolvedConfig, registry, inflection));
392
+ if (needsSValidator) {
393
+ allNeedsSValidator.add(capability);
446
394
  }
447
- const exportStmt = conjure.export.const(aggregatorName, chainExpr);
448
- indexFile.ast(conjure.program(exportStmt)).emit();
395
+ rendered.push({
396
+ name: inflection.variableName(entityName, "HonoRoutes"),
397
+ capability,
398
+ node: statements[0],
399
+ exports: "named",
400
+ externalImports,
401
+ });
449
402
  }
450
- },
451
- });
403
+ if (entityQueries.size > 0) {
404
+ const appCapability = "http-routes:hono:app";
405
+ const { statements, externalImports } = registry.forSymbol(appCapability, () => generateAggregator(entityQueries, resolvedConfig, registry, inflection));
406
+ rendered.push({
407
+ name: "honoApp",
408
+ capability: appCapability,
409
+ node: statements[0],
410
+ exports: "named",
411
+ externalImports,
412
+ });
413
+ }
414
+ return rendered.map((r) => {
415
+ const needsSValidator = allNeedsSValidator.has(r.capability);
416
+ return {
417
+ ...r,
418
+ externalImports: needsSValidator
419
+ ? [
420
+ ...(r.externalImports ?? []),
421
+ { from: "@hono/standard-validator", names: ["sValidator"] },
422
+ ]
423
+ : r.externalImports,
424
+ };
425
+ });
426
+ }),
427
+ };
452
428
  }
453
429
  //# sourceMappingURL=http-hono.js.map