@danielfgray/pg-sourcerer 0.3.0 → 0.5.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.
- package/bin/pgsourcerer +2 -0
- package/dist/__tests__/fixtures/index.d.ts +15 -0
- package/dist/__tests__/fixtures/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/index.js +19 -0
- package/dist/__tests__/fixtures/index.js.map +1 -0
- package/dist/__tests__/fixtures/introspection.json +40522 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +7 -46
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +38 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -2
- package/dist/config.js.map +1 -1
- package/dist/{lib/conjure.d.ts → conjure/index.d.ts} +62 -3
- package/dist/conjure/index.d.ts.map +1 -0
- package/dist/{lib/conjure.js → conjure/index.js} +124 -3
- package/dist/conjure/index.js.map +1 -0
- package/dist/conjure/signature.d.ts +85 -0
- package/dist/conjure/signature.d.ts.map +1 -0
- package/dist/conjure/signature.js +130 -0
- package/dist/conjure/signature.js.map +1 -0
- package/dist/conjure/types.d.ts +97 -0
- package/dist/conjure/types.d.ts.map +1 -0
- package/dist/conjure/types.js +206 -0
- package/dist/conjure/types.js.map +1 -0
- package/dist/errors.d.ts +114 -139
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +82 -36
- package/dist/errors.js.map +1 -1
- package/dist/generate.d.ts +45 -46
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +86 -59
- package/dist/generate.js.map +1 -1
- package/dist/hex/builder.d.ts +12 -0
- package/dist/hex/builder.d.ts.map +1 -0
- package/dist/hex/builder.js +64 -0
- package/dist/hex/builder.js.map +1 -0
- package/dist/hex/ddl.d.ts +53 -0
- package/dist/hex/ddl.d.ts.map +1 -0
- package/dist/hex/ddl.js +306 -0
- package/dist/hex/ddl.js.map +1 -0
- package/dist/hex/index.d.ts +105 -0
- package/dist/hex/index.d.ts.map +1 -0
- package/dist/hex/index.js +81 -0
- package/dist/hex/index.js.map +1 -0
- package/dist/hex/primitives.d.ts +23 -0
- package/dist/hex/primitives.d.ts.map +1 -0
- package/dist/hex/primitives.js +38 -0
- package/dist/hex/primitives.js.map +1 -0
- package/dist/hex/query.d.ts +116 -0
- package/dist/hex/query.d.ts.map +1 -0
- package/dist/hex/query.js +219 -0
- package/dist/hex/query.js.map +1 -0
- package/dist/hex/types.d.ts +287 -0
- package/dist/hex/types.d.ts.map +1 -0
- package/dist/hex/types.js +431 -0
- package/dist/hex/types.js.map +1 -0
- package/dist/index.d.ts +17 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -44
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +76 -140
- package/dist/init.js.map +1 -1
- package/dist/ir/extensions/queries.d.ts +6 -6
- package/dist/ir/extensions/queries.d.ts.map +1 -1
- package/dist/ir/extensions/queries.js +6 -4
- package/dist/ir/extensions/queries.js.map +1 -1
- package/dist/ir/extensions/schema-builder.d.ts.map +1 -1
- package/dist/ir/extensions/schema-builder.js.map +1 -1
- package/dist/ir/index.d.ts.map +1 -1
- package/dist/ir/index.js.map +1 -1
- package/dist/ir/relation-graph.d.ts.map +1 -1
- package/dist/ir/relation-graph.js +8 -8
- package/dist/ir/relation-graph.js.map +1 -1
- package/dist/ir/semantic-ir.d.ts +38 -0
- package/dist/ir/semantic-ir.d.ts.map +1 -1
- package/dist/ir/semantic-ir.js +50 -2
- package/dist/ir/semantic-ir.js.map +1 -1
- package/dist/ir/smart-tags.d.ts.map +1 -1
- package/dist/ir/smart-tags.js.map +1 -1
- package/dist/lib/field-utils.d.ts.map +1 -1
- package/dist/lib/field-utils.js +7 -7
- package/dist/lib/field-utils.js.map +1 -1
- package/dist/lib/join-graph.d.ts +95 -0
- package/dist/lib/join-graph.d.ts.map +1 -0
- package/dist/lib/join-graph.js +305 -0
- package/dist/lib/join-graph.js.map +1 -0
- package/dist/lib/picker.d.ts +60 -0
- package/dist/lib/picker.d.ts.map +1 -0
- package/dist/lib/picker.js +325 -0
- package/dist/lib/picker.js.map +1 -0
- package/dist/plugins/arktype.d.ts +20 -24
- package/dist/plugins/arktype.d.ts.map +1 -1
- package/dist/plugins/arktype.js +462 -386
- package/dist/plugins/arktype.js.map +1 -1
- package/dist/plugins/effect/http.d.ts +7 -0
- package/dist/plugins/effect/http.d.ts.map +1 -0
- package/dist/plugins/effect/http.js +460 -0
- package/dist/plugins/effect/http.js.map +1 -0
- package/dist/plugins/effect/index.d.ts +22 -0
- package/dist/plugins/effect/index.d.ts.map +1 -0
- package/dist/plugins/effect/index.js +65 -0
- package/dist/plugins/effect/index.js.map +1 -0
- package/dist/plugins/effect/models.d.ts +6 -0
- package/dist/plugins/effect/models.d.ts.map +1 -0
- package/dist/plugins/effect/models.js +116 -0
- package/dist/plugins/effect/models.js.map +1 -0
- package/dist/plugins/effect/repos.d.ts +21 -0
- package/dist/plugins/effect/repos.d.ts.map +1 -0
- package/dist/plugins/effect/repos.js +131 -0
- package/dist/plugins/effect/repos.js.map +1 -0
- package/dist/plugins/effect/schemas.d.ts +7 -0
- package/dist/plugins/effect/schemas.d.ts.map +1 -0
- package/dist/plugins/effect/schemas.js +75 -0
- package/dist/plugins/effect/schemas.js.map +1 -0
- package/dist/plugins/effect/shared.d.ts +116 -0
- package/dist/plugins/effect/shared.d.ts.map +1 -0
- package/dist/plugins/effect/shared.js +164 -0
- package/dist/plugins/effect/shared.js.map +1 -0
- package/dist/plugins/http-elysia.d.ts +20 -27
- package/dist/plugins/http-elysia.d.ts.map +1 -1
- package/dist/plugins/http-elysia.js +350 -475
- package/dist/plugins/http-elysia.js.map +1 -1
- package/dist/plugins/http-express.d.ts +20 -31
- package/dist/plugins/http-express.d.ts.map +1 -1
- package/dist/plugins/http-express.js +281 -268
- package/dist/plugins/http-express.js.map +1 -1
- package/dist/plugins/http-hono.d.ts +17 -33
- package/dist/plugins/http-hono.d.ts.map +1 -1
- package/dist/plugins/http-hono.js +317 -341
- package/dist/plugins/http-hono.js.map +1 -1
- package/dist/plugins/http-orpc.d.ts +34 -33
- package/dist/plugins/http-orpc.d.ts.map +1 -1
- package/dist/plugins/http-orpc.js +345 -257
- package/dist/plugins/http-orpc.js.map +1 -1
- package/dist/plugins/http-trpc.d.ts +33 -35
- package/dist/plugins/http-trpc.d.ts.map +1 -1
- package/dist/plugins/http-trpc.js +337 -241
- package/dist/plugins/http-trpc.js.map +1 -1
- package/dist/plugins/kysely.d.ts +54 -59
- package/dist/plugins/kysely.d.ts.map +1 -1
- package/dist/plugins/kysely.js +826 -687
- package/dist/plugins/kysely.js.map +1 -1
- package/dist/plugins/sql-queries.d.ts +38 -44
- package/dist/plugins/sql-queries.d.ts.map +1 -1
- package/dist/plugins/sql-queries.js +497 -897
- package/dist/plugins/sql-queries.js.map +1 -1
- package/dist/plugins/types.d.ts +12 -20
- package/dist/plugins/types.d.ts.map +1 -1
- package/dist/plugins/types.js +84 -227
- package/dist/plugins/types.js.map +1 -1
- package/dist/plugins/valibot.d.ts +7 -44
- package/dist/plugins/valibot.d.ts.map +1 -1
- package/dist/plugins/valibot.js +376 -382
- package/dist/plugins/valibot.js.map +1 -1
- package/dist/plugins/zod.d.ts +20 -24
- package/dist/plugins/zod.d.ts.map +1 -1
- package/dist/plugins/zod.js +370 -367
- package/dist/plugins/zod.js.map +1 -1
- package/dist/runtime/emit.d.ts +64 -0
- package/dist/runtime/emit.d.ts.map +1 -0
- package/dist/runtime/emit.js +445 -0
- package/dist/runtime/emit.js.map +1 -0
- package/dist/runtime/errors.d.ts +36 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +29 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/file-assignment.d.ts +161 -0
- package/dist/runtime/file-assignment.d.ts.map +1 -0
- package/dist/runtime/file-assignment.js +195 -0
- package/dist/runtime/file-assignment.js.map +1 -0
- package/dist/runtime/orchestrator.d.ts +62 -0
- package/dist/runtime/orchestrator.d.ts.map +1 -0
- package/dist/runtime/orchestrator.js +99 -0
- package/dist/runtime/orchestrator.js.map +1 -0
- package/dist/runtime/registry.d.ts +268 -0
- package/dist/runtime/registry.d.ts.map +1 -0
- package/dist/runtime/registry.js +436 -0
- package/dist/runtime/registry.js.map +1 -0
- package/dist/runtime/types.d.ts +182 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/validation.d.ts +41 -0
- package/dist/runtime/validation.d.ts.map +1 -0
- package/dist/runtime/validation.js +70 -0
- package/dist/runtime/validation.js.map +1 -0
- package/dist/services/config-loader.d.ts.map +1 -1
- package/dist/services/config-loader.js +15 -6
- package/dist/services/config-loader.js.map +1 -1
- package/dist/services/config.d.ts +55 -25
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +60 -34
- package/dist/services/config.js.map +1 -1
- package/dist/services/file-writer.d.ts +3 -3
- package/dist/services/file-writer.d.ts.map +1 -1
- package/dist/services/file-writer.js +6 -8
- package/dist/services/file-writer.js.map +1 -1
- package/dist/services/inflection.d.ts +126 -27
- package/dist/services/inflection.d.ts.map +1 -1
- package/dist/services/inflection.js +300 -72
- package/dist/services/inflection.js.map +1 -1
- package/dist/services/introspection.d.ts.map +1 -1
- package/dist/services/introspection.js +6 -6
- package/dist/services/introspection.js.map +1 -1
- package/dist/services/ir-builder.d.ts.map +1 -1
- package/dist/services/ir-builder.js +73 -77
- package/dist/services/ir-builder.js.map +1 -1
- package/dist/services/ir.d.ts.map +1 -1
- package/dist/services/ir.js.map +1 -1
- package/dist/services/pg-types.d.ts.map +1 -1
- package/dist/services/pg-types.js +3 -3
- package/dist/services/pg-types.js.map +1 -1
- package/dist/services/smart-tags-parser.d.ts.map +1 -1
- package/dist/services/smart-tags-parser.js +4 -4
- package/dist/services/smart-tags-parser.js.map +1 -1
- package/dist/services/type-hints.d.ts.map +1 -1
- package/dist/services/type-hints.js +1 -1
- package/dist/services/type-hints.js.map +1 -1
- package/dist/services/user-module-parser.d.ts +46 -0
- package/dist/services/user-module-parser.d.ts.map +1 -0
- package/dist/services/user-module-parser.js +181 -0
- package/dist/services/user-module-parser.js.map +1 -0
- package/dist/shared/converters.d.ts +60 -0
- package/dist/shared/converters.d.ts.map +1 -0
- package/dist/shared/converters.js +168 -0
- package/dist/shared/converters.js.map +1 -0
- package/dist/shared/query-types.d.ts +95 -0
- package/dist/shared/query-types.d.ts.map +1 -0
- package/dist/shared/query-types.js +9 -0
- package/dist/shared/query-types.js.map +1 -0
- package/dist/testing.d.ts +125 -37
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +134 -42
- package/dist/testing.js.map +1 -1
- package/dist/user-module.d.ts +86 -0
- package/dist/user-module.d.ts.map +1 -0
- package/dist/user-module.js +55 -0
- package/dist/user-module.js.map +1 -0
- package/package.json +10 -6
- package/dist/lib/conjure.d.ts.map +0 -1
- package/dist/lib/conjure.js.map +0 -1
- package/dist/lib/hex.d.ts +0 -119
- package/dist/lib/hex.d.ts.map +0 -1
- package/dist/lib/hex.js +0 -188
- package/dist/lib/hex.js.map +0 -1
- package/dist/plugins/effect.d.ts +0 -53
- package/dist/plugins/effect.d.ts.map +0 -1
- package/dist/plugins/effect.js +0 -1074
- package/dist/plugins/effect.js.map +0 -1
- package/dist/plugins/kysely/queries.d.ts +0 -92
- package/dist/plugins/kysely/queries.d.ts.map +0 -1
- package/dist/plugins/kysely/queries.js +0 -1169
- package/dist/plugins/kysely/queries.js.map +0 -1
- package/dist/plugins/kysely/shared.d.ts +0 -59
- package/dist/plugins/kysely/shared.d.ts.map +0 -1
- package/dist/plugins/kysely/shared.js +0 -247
- package/dist/plugins/kysely/shared.js.map +0 -1
- package/dist/plugins/kysely/types.d.ts +0 -22
- package/dist/plugins/kysely/types.d.ts.map +0 -1
- package/dist/plugins/kysely/types.js +0 -428
- package/dist/plugins/kysely/types.js.map +0 -1
- package/dist/services/artifact-store.d.ts +0 -65
- package/dist/services/artifact-store.d.ts.map +0 -1
- package/dist/services/artifact-store.js +0 -57
- package/dist/services/artifact-store.js.map +0 -1
- package/dist/services/core-providers.d.ts +0 -15
- package/dist/services/core-providers.d.ts.map +0 -1
- package/dist/services/core-providers.js +0 -23
- package/dist/services/core-providers.js.map +0 -1
- package/dist/services/emissions.d.ts +0 -103
- package/dist/services/emissions.d.ts.map +0 -1
- package/dist/services/emissions.js +0 -241
- package/dist/services/emissions.js.map +0 -1
- package/dist/services/execution.d.ts +0 -35
- package/dist/services/execution.d.ts.map +0 -1
- package/dist/services/execution.js +0 -86
- package/dist/services/execution.js.map +0 -1
- package/dist/services/file-builder.d.ts +0 -85
- package/dist/services/file-builder.d.ts.map +0 -1
- package/dist/services/file-builder.js +0 -112
- package/dist/services/file-builder.js.map +0 -1
- package/dist/services/plugin-meta.d.ts +0 -33
- package/dist/services/plugin-meta.d.ts.map +0 -1
- package/dist/services/plugin-meta.js +0 -24
- package/dist/services/plugin-meta.js.map +0 -1
- package/dist/services/plugin-runner.d.ts +0 -42
- package/dist/services/plugin-runner.d.ts.map +0 -1
- package/dist/services/plugin-runner.js +0 -84
- package/dist/services/plugin-runner.js.map +0 -1
- package/dist/services/plugin.d.ts +0 -421
- package/dist/services/plugin.d.ts.map +0 -1
- package/dist/services/plugin.js +0 -197
- package/dist/services/plugin.js.map +0 -1
- package/dist/services/resolution.d.ts +0 -38
- package/dist/services/resolution.d.ts.map +0 -1
- package/dist/services/resolution.js +0 -242
- package/dist/services/resolution.js.map +0 -1
- package/dist/services/service-registry.d.ts +0 -74
- package/dist/services/service-registry.d.ts.map +0 -1
- package/dist/services/service-registry.js +0 -61
- package/dist/services/service-registry.js.map +0 -1
- package/dist/services/symbols.d.ts +0 -144
- package/dist/services/symbols.d.ts.map +0 -1
- package/dist/services/symbols.js +0 -144
- package/dist/services/symbols.js.map +0 -1
|
@@ -1,51 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP
|
|
2
|
+
* Hono HTTP Plugin - Generates Hono route handlers from query symbols
|
|
3
3
|
*
|
|
4
|
-
* Consumes
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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 =
|
|
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 `/${
|
|
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
|
|
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
|
|
179
|
-
param.
|
|
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
|
|
201
|
-
param.
|
|
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
|
|
214
|
-
param.
|
|
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
|
-
|
|
226
|
-
const
|
|
227
|
-
statements.push(stmt.const("result",
|
|
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")), [
|
|
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
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
*
|
|
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
|
-
|
|
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
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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,
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
448
|
-
|
|
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
|