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