@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
package/dist/plugins/kysely.js
CHANGED
|
@@ -1,55 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Kysely Plugin -
|
|
2
|
+
* Kysely Plugin - Unified Kysely types and query functions
|
|
3
3
|
*
|
|
4
|
-
* Consolidates types and queries generation into a single plugin with unified config.
|
|
5
4
|
* Generates:
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
5
|
+
* 1. Kysely-compatible type definitions (DB interface, table types with Generated<T>)
|
|
6
|
+
* 2. Type-safe CRUD query functions using Kysely's fluent API
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* import { kysely } from "pg-sourcerer"
|
|
12
|
-
*
|
|
13
|
-
* export default defineConfig({
|
|
14
|
-
* plugins: [
|
|
15
|
-
* kysely({
|
|
16
|
-
* outputDir: "db",
|
|
17
|
-
* generateQueries: true,
|
|
18
|
-
* }),
|
|
19
|
-
* ],
|
|
20
|
-
* })
|
|
21
|
-
* ```
|
|
8
|
+
* This plugin is incompatible with other type-generation plugins (types, zod, etc.)
|
|
9
|
+
* since it provides its own type definitions optimized for Kysely.
|
|
22
10
|
*/
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
11
|
+
import { Effect } from "effect";
|
|
12
|
+
import { Schema as S } from "effect";
|
|
13
|
+
import { normalizeFileNaming } from "../runtime/file-assignment.js";
|
|
14
|
+
import { IR } from "../services/ir.js";
|
|
15
|
+
import { Inflection } from "../services/inflection.js";
|
|
16
|
+
import { getTableEntities, getEnumEntities, getCompositeEntities, getCursorPaginationCandidates, } from "../ir/semantic-ir.js";
|
|
17
|
+
import { conjure, cast } from "../conjure/index.js";
|
|
18
|
+
const { fn, stmt, ts, param, str, exp, b, chain, arrExpr } = conjure;
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Configuration
|
|
21
|
+
// ============================================================================
|
|
34
22
|
const KyselyConfigSchema = S.Struct({
|
|
35
|
-
|
|
36
|
-
dbTypeName: S.optionalWith(S.String, { default: () => "DB" }),
|
|
37
|
-
camelCase: S.optionalWith(S.Boolean, { default: () => true }),
|
|
38
|
-
runtimeEnums: S.optionalWith(S.Boolean, { default: () => false }),
|
|
39
|
-
typeOnlyImports: S.optionalWith(S.Boolean, { default: () => true }),
|
|
23
|
+
/** Generate query functions (default: true) */
|
|
40
24
|
generateQueries: S.optionalWith(S.Boolean, { default: () => true }),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
generateFunctions: S.optionalWith(S.Boolean, { default: () => true }),
|
|
45
|
-
functionsFile: S.optionalWith(S.String, { default: () => "functions.ts" }),
|
|
46
|
-
exportName: S.optional(S.Any),
|
|
47
|
-
exportStyle: S.optionalWith(S.Literal("flat", "namespace"), { default: () => "flat" }),
|
|
48
|
-
explicitColumns: S.optionalWith(S.Boolean, { default: () => true }),
|
|
49
|
-
dbAsParameter: S.optionalWith(S.Boolean, { default: () => true }),
|
|
50
|
-
header: S.optional(S.String),
|
|
25
|
+
/** If true, db is passed as first parameter; if false, imported via dbImport */
|
|
26
|
+
dbAsParameter: S.optionalWith(S.Boolean, { default: () => false }),
|
|
27
|
+
/** Default limit for list queries (default: 50) */
|
|
51
28
|
defaultLimit: S.optionalWith(S.Number, { default: () => 50 }),
|
|
52
29
|
});
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Kysely Type Helpers (ported from kysely-codegen)
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Helper type definitions to be emitted in the types file.
|
|
35
|
+
*/
|
|
53
36
|
const GENERATED_TYPE_DEF = `T extends ColumnType<infer S, infer I, infer U>
|
|
54
37
|
? ColumnType<S, I | undefined, U>
|
|
55
38
|
: ColumnType<T, T | undefined, T>`;
|
|
@@ -59,13 +42,21 @@ const ARRAY_TYPE_DEF = `ArrayTypeImpl<T> extends (infer U)[]
|
|
|
59
42
|
const ARRAY_TYPE_IMPL_DEF = `T extends ColumnType<infer S, infer I, infer U>
|
|
60
43
|
? ColumnType<S[], I[], U[]>
|
|
61
44
|
: T[]`;
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// PostgreSQL Type Mappings
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/** Simple scalar types: PG type → TS type builder */
|
|
62
49
|
const SCALAR_TYPES = {
|
|
50
|
+
// Boolean
|
|
63
51
|
bool: ts.boolean,
|
|
52
|
+
boolean: ts.boolean,
|
|
53
|
+
// Integers → number
|
|
64
54
|
int2: ts.number,
|
|
65
55
|
int4: ts.number,
|
|
66
56
|
float4: ts.number,
|
|
67
57
|
float8: ts.number,
|
|
68
58
|
oid: ts.number,
|
|
59
|
+
// Text types → string
|
|
69
60
|
text: ts.string,
|
|
70
61
|
varchar: ts.string,
|
|
71
62
|
bpchar: ts.string,
|
|
@@ -74,35 +65,46 @@ const SCALAR_TYPES = {
|
|
|
74
65
|
bit: ts.string,
|
|
75
66
|
varbit: ts.string,
|
|
76
67
|
xml: ts.string,
|
|
68
|
+
citext: ts.string,
|
|
69
|
+
// UUID → string
|
|
77
70
|
uuid: ts.string,
|
|
71
|
+
// Network types → string
|
|
78
72
|
inet: ts.string,
|
|
79
73
|
cidr: ts.string,
|
|
80
74
|
macaddr: ts.string,
|
|
81
75
|
macaddr8: ts.string,
|
|
76
|
+
// Geometric types → string
|
|
82
77
|
line: ts.string,
|
|
83
78
|
lseg: ts.string,
|
|
84
79
|
box: ts.string,
|
|
85
80
|
path: ts.string,
|
|
86
81
|
polygon: ts.string,
|
|
82
|
+
// Time without date → string
|
|
87
83
|
time: ts.string,
|
|
88
84
|
timetz: ts.string,
|
|
85
|
+
// Full-text search → string
|
|
89
86
|
tsvector: ts.string,
|
|
90
87
|
tsquery: ts.string,
|
|
91
88
|
txid_snapshot: ts.string,
|
|
89
|
+
// Money → string
|
|
92
90
|
money: ts.string,
|
|
91
|
+
// Binary → Buffer
|
|
93
92
|
bytea: () => ts.ref("Buffer"),
|
|
94
93
|
};
|
|
95
94
|
const COMPLEX_TYPES = {
|
|
95
|
+
// int8/bigint: returns string, accepts string|number|bigint
|
|
96
96
|
int8: {
|
|
97
97
|
select: ts.string,
|
|
98
98
|
insert: () => ts.union(ts.string(), ts.number(), ts.bigint()),
|
|
99
99
|
update: () => ts.union(ts.string(), ts.number(), ts.bigint()),
|
|
100
100
|
},
|
|
101
|
+
// numeric/decimal: returns string, accepts number|string
|
|
101
102
|
numeric: {
|
|
102
103
|
select: ts.string,
|
|
103
104
|
insert: () => ts.union(ts.number(), ts.string()),
|
|
104
105
|
update: () => ts.union(ts.number(), ts.string()),
|
|
105
106
|
},
|
|
107
|
+
// Timestamps: returns Date, accepts Date|string
|
|
106
108
|
date: {
|
|
107
109
|
select: () => ts.ref("Date"),
|
|
108
110
|
insert: () => ts.union(ts.ref("Date"), ts.string()),
|
|
@@ -118,11 +120,13 @@ const COMPLEX_TYPES = {
|
|
|
118
120
|
insert: () => ts.union(ts.ref("Date"), ts.string()),
|
|
119
121
|
update: () => ts.union(ts.ref("Date"), ts.string()),
|
|
120
122
|
},
|
|
123
|
+
// Interval: string for now
|
|
121
124
|
interval: {
|
|
122
125
|
select: ts.string,
|
|
123
126
|
insert: () => ts.union(ts.string(), ts.number()),
|
|
124
127
|
update: () => ts.union(ts.string(), ts.number()),
|
|
125
128
|
},
|
|
129
|
+
// JSON: JsonValue
|
|
126
130
|
json: {
|
|
127
131
|
select: () => ts.ref("JsonValue"),
|
|
128
132
|
insert: () => ts.ref("JsonValue"),
|
|
@@ -133,6 +137,7 @@ const COMPLEX_TYPES = {
|
|
|
133
137
|
insert: () => ts.ref("JsonValue"),
|
|
134
138
|
update: () => ts.ref("JsonValue"),
|
|
135
139
|
},
|
|
140
|
+
// Point: object with x, y
|
|
136
141
|
point: {
|
|
137
142
|
select: () => ts.objectType([
|
|
138
143
|
{ name: "x", type: ts.number() },
|
|
@@ -147,73 +152,34 @@ const COMPLEX_TYPES = {
|
|
|
147
152
|
{ name: "y", type: ts.number() },
|
|
148
153
|
]),
|
|
149
154
|
},
|
|
150
|
-
circle: {
|
|
151
|
-
select: () => ts.objectType([
|
|
152
|
-
{ name: "x", type: ts.number() },
|
|
153
|
-
{ name: "y", type: ts.number() },
|
|
154
|
-
{ name: "radius", type: ts.number() },
|
|
155
|
-
]),
|
|
156
|
-
insert: () => ts.objectType([
|
|
157
|
-
{ name: "x", type: ts.number() },
|
|
158
|
-
{ name: "y", type: ts.number() },
|
|
159
|
-
{ name: "radius", type: ts.number() },
|
|
160
|
-
]),
|
|
161
|
-
update: () => ts.objectType([
|
|
162
|
-
{ name: "x", type: ts.number() },
|
|
163
|
-
{ name: "y", type: ts.number() },
|
|
164
|
-
{ name: "radius", type: ts.number() },
|
|
165
|
-
]),
|
|
166
|
-
},
|
|
167
155
|
};
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
schema: ctx.schemaName,
|
|
173
|
-
table: ctx.tableName,
|
|
174
|
-
column: field.columnName,
|
|
175
|
-
pgType: field.isArray && field.elementTypeName
|
|
176
|
-
? field.elementTypeName
|
|
177
|
-
: field.pgAttribute.getType()?.typname ?? "",
|
|
178
|
-
});
|
|
179
|
-
function resolveFieldTypeForKysely(field, ctx) {
|
|
156
|
+
/**
|
|
157
|
+
* Resolve a field to its Kysely type.
|
|
158
|
+
*/
|
|
159
|
+
function resolveFieldType(field, ctx) {
|
|
180
160
|
const pgType = field.pgAttribute.getType();
|
|
181
161
|
const typeName = pgType?.typname ?? "";
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
externalImport: pipe(importPath, Option.map(path => ({ name: typeName, from: path })), Option.getOrUndefined),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
if (isEnumType(field)) {
|
|
194
|
-
const enumName = getPgTypeName(field);
|
|
195
|
-
if (enumName) {
|
|
196
|
-
const enumDef = findEnumByPgName(ctx.enums, enumName);
|
|
197
|
-
if (Option.isSome(enumDef)) {
|
|
198
|
-
return {
|
|
199
|
-
selectType: ts.ref(enumDef.value.name),
|
|
200
|
-
needsColumnType: false,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
162
|
+
// Check if it's an enum
|
|
163
|
+
if (pgType?.typtype === "e") {
|
|
164
|
+
const enumDef = ctx.enums.find(e => e.pgType.typname === typeName);
|
|
165
|
+
if (enumDef) {
|
|
166
|
+
return {
|
|
167
|
+
selectType: ts.ref(enumDef.name),
|
|
168
|
+
needsColumnType: false,
|
|
169
|
+
};
|
|
203
170
|
}
|
|
204
171
|
}
|
|
172
|
+
// Check if it's a composite type
|
|
205
173
|
if (pgType?.typtype === "c") {
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
needsColumnType: false,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
174
|
+
const compositeDef = ctx.composites.find(c => c.pgType.typname === typeName);
|
|
175
|
+
if (compositeDef) {
|
|
176
|
+
return {
|
|
177
|
+
selectType: ts.ref(compositeDef.name),
|
|
178
|
+
needsColumnType: false,
|
|
179
|
+
};
|
|
215
180
|
}
|
|
216
181
|
}
|
|
182
|
+
// Check complex types (need ColumnType wrapper)
|
|
217
183
|
const complexType = COMPLEX_TYPES[typeName];
|
|
218
184
|
if (complexType) {
|
|
219
185
|
return {
|
|
@@ -221,9 +187,9 @@ function resolveFieldTypeForKysely(field, ctx) {
|
|
|
221
187
|
insertType: complexType.insert(),
|
|
222
188
|
updateType: complexType.update(),
|
|
223
189
|
needsColumnType: true,
|
|
224
|
-
externalImport: complexType.import,
|
|
225
190
|
};
|
|
226
191
|
}
|
|
192
|
+
// Check simple scalar types
|
|
227
193
|
const scalarBuilder = SCALAR_TYPES[typeName];
|
|
228
194
|
if (scalarBuilder) {
|
|
229
195
|
return {
|
|
@@ -231,32 +197,51 @@ function resolveFieldTypeForKysely(field, ctx) {
|
|
|
231
197
|
needsColumnType: false,
|
|
232
198
|
};
|
|
233
199
|
}
|
|
234
|
-
|
|
235
|
-
const extType = getExtensionTypeMapping(typeName, String(pgType.typnamespace), ctx.extensions);
|
|
236
|
-
if (Option.isSome(extType)) {
|
|
237
|
-
return {
|
|
238
|
-
selectType: ts.fromString(extType.value),
|
|
239
|
-
needsColumnType: false,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
}
|
|
200
|
+
// Default to string
|
|
243
201
|
return {
|
|
244
202
|
selectType: ts.string(),
|
|
245
203
|
needsColumnType: false,
|
|
246
204
|
};
|
|
247
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Determine if a field should be wrapped in Generated<T>.
|
|
208
|
+
*
|
|
209
|
+
* A field needs Generated<T> wrapper (making it optional in Insertable<T>) if:
|
|
210
|
+
* 1. The role cannot insert this field (permission-denied → treat as if generated)
|
|
211
|
+
* 2. The field has a database default and is an identity/generated column
|
|
212
|
+
* 3. The field has a default that will be used if not provided
|
|
213
|
+
*
|
|
214
|
+
* This ensures Kysely's Insertable<T> aligns with IR insert shapes which
|
|
215
|
+
* exclude fields the role cannot insert.
|
|
216
|
+
*/
|
|
217
|
+
function isGeneratedField(field) {
|
|
218
|
+
// If role can't insert this field, make it optional in Insertable<T>
|
|
219
|
+
// This matches IR behavior where such fields are excluded from insert shape
|
|
220
|
+
if (!field.permissions.canInsert)
|
|
221
|
+
return true;
|
|
222
|
+
// Fields with any kind of default are optional on insert
|
|
223
|
+
// (identity columns, generated columns, or any DEFAULT value)
|
|
224
|
+
if (field.hasDefault)
|
|
225
|
+
return true;
|
|
226
|
+
if (field.isIdentity)
|
|
227
|
+
return true;
|
|
228
|
+
if (field.isGenerated)
|
|
229
|
+
return true;
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Build the final field type with array/nullable/Generated wrappers.
|
|
234
|
+
*/
|
|
248
235
|
function buildFieldType(field, kyselyType, needsGenerated) {
|
|
249
236
|
let baseType;
|
|
237
|
+
// If complex type, wrap in ColumnType<S, I, U>
|
|
250
238
|
if (kyselyType.needsColumnType && kyselyType.insertType && kyselyType.updateType) {
|
|
251
|
-
baseType = ts.ref("ColumnType", [
|
|
252
|
-
kyselyType.selectType,
|
|
253
|
-
kyselyType.insertType,
|
|
254
|
-
kyselyType.updateType,
|
|
255
|
-
]);
|
|
239
|
+
baseType = ts.ref("ColumnType", [kyselyType.selectType, kyselyType.insertType, kyselyType.updateType]);
|
|
256
240
|
}
|
|
257
241
|
else {
|
|
258
242
|
baseType = kyselyType.selectType;
|
|
259
243
|
}
|
|
244
|
+
// Wrap in array if needed
|
|
260
245
|
if (field.isArray) {
|
|
261
246
|
if (kyselyType.needsColumnType) {
|
|
262
247
|
baseType = ts.ref("ArrayType", [baseType]);
|
|
@@ -265,363 +250,81 @@ function buildFieldType(field, kyselyType, needsGenerated) {
|
|
|
265
250
|
baseType = ts.array(baseType);
|
|
266
251
|
}
|
|
267
252
|
}
|
|
253
|
+
// Wrap in nullable if needed
|
|
268
254
|
if (field.nullable) {
|
|
269
|
-
baseType = ts.
|
|
255
|
+
baseType = ts.union(baseType, ts.null());
|
|
270
256
|
}
|
|
257
|
+
// Wrap in Generated<T> if field has default and is not insertable
|
|
271
258
|
if (needsGenerated) {
|
|
272
259
|
baseType = ts.ref("Generated", [baseType]);
|
|
273
260
|
}
|
|
274
261
|
return baseType;
|
|
275
262
|
}
|
|
276
|
-
function isGeneratedField(field) {
|
|
277
|
-
// Identity and generated columns are always "generated" regardless of hasDefault
|
|
278
|
-
if (field.isIdentity)
|
|
279
|
-
return true;
|
|
280
|
-
if (field.isGenerated)
|
|
281
|
-
return true;
|
|
282
|
-
if (!field.hasDefault)
|
|
283
|
-
return false;
|
|
284
|
-
if (!field.permissions.canInsert)
|
|
285
|
-
return true;
|
|
286
|
-
const pgType = field.pgAttribute.getType();
|
|
287
|
-
if (!pgType)
|
|
288
|
-
return false;
|
|
289
|
-
const typeOid = Number(pgType._id);
|
|
290
|
-
const isIntegerType = typeOid === PgTypeOid.Int2
|
|
291
|
-
|| typeOid === PgTypeOid.Int4
|
|
292
|
-
|| typeOid === PgTypeOid.Int8;
|
|
293
|
-
if (isIntegerType && field.hasDefault) {
|
|
294
|
-
const constraints = field.pgAttribute.getClass()?.getConstraints() ?? [];
|
|
295
|
-
const isPrimaryKey = constraints.some(c => c.contype === "p" && c.conkey?.includes(field.pgAttribute.attnum));
|
|
296
|
-
if (isPrimaryKey)
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
263
|
// ============================================================================
|
|
302
|
-
//
|
|
264
|
+
// Type Generation
|
|
303
265
|
// ============================================================================
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return resolved.enumDef ? resolved.enumDef.name : resolved.tsType;
|
|
320
|
-
};
|
|
321
|
-
const toPascalCase = (s) => inflect.pascalCase(s);
|
|
322
|
-
const id = (name) => b.identifier(name);
|
|
323
|
-
const call = (obj, method, args = []) => b.callExpression(b.memberExpression(toExpr(obj), id(method)), args.map(toExpr));
|
|
324
|
-
const selectFrom = (tableRef) => call(id("db"), "selectFrom", [str(tableRef)]);
|
|
325
|
-
const selectColumns = (base, entity, explicitColumns) => explicitColumns
|
|
326
|
-
? call(base, "select", [b.arrayExpression(entity.shapes.row.fields.map(f => str(f.columnName)))])
|
|
327
|
-
: call(base, "selectAll");
|
|
328
|
-
const insertInto = (tableRef) => call(id("db"), "insertInto", [str(tableRef)]);
|
|
329
|
-
const updateTable = (tableRef) => call(id("db"), "updateTable", [str(tableRef)]);
|
|
330
|
-
const deleteFrom = (tableRef) => call(id("db"), "deleteFrom", [str(tableRef)]);
|
|
331
|
-
const chain = (expr, method, args = []) => call(expr, method, args);
|
|
332
|
-
const arrowFn = (params, body) => {
|
|
333
|
-
const fn = b.arrowFunctionExpression(params.map(p => p), toExpr(body));
|
|
334
|
-
return fn;
|
|
335
|
-
};
|
|
336
|
-
const pgTypeNameToTs = (typeName) => {
|
|
337
|
-
const baseName = typeName.includes(".") ? typeName.split(".").pop() : typeName;
|
|
338
|
-
switch (baseName) {
|
|
339
|
-
case "bool":
|
|
340
|
-
case "boolean":
|
|
341
|
-
return "boolean";
|
|
342
|
-
case "int2":
|
|
343
|
-
case "smallint":
|
|
344
|
-
case "int4":
|
|
345
|
-
case "integer":
|
|
346
|
-
case "int":
|
|
347
|
-
case "oid":
|
|
348
|
-
case "float4":
|
|
349
|
-
case "real":
|
|
350
|
-
case "float8":
|
|
351
|
-
case "double precision":
|
|
352
|
-
return "number";
|
|
353
|
-
case "int8":
|
|
354
|
-
case "bigint":
|
|
355
|
-
case "numeric":
|
|
356
|
-
case "decimal":
|
|
357
|
-
case "money":
|
|
358
|
-
return "string";
|
|
359
|
-
case "text":
|
|
360
|
-
case "varchar":
|
|
361
|
-
case "character varying":
|
|
362
|
-
case "char":
|
|
363
|
-
case "character":
|
|
364
|
-
case "bpchar":
|
|
365
|
-
case "name":
|
|
366
|
-
case "xml":
|
|
367
|
-
case "bit":
|
|
368
|
-
case "varbit":
|
|
369
|
-
case "bit varying":
|
|
370
|
-
case "uuid":
|
|
371
|
-
case "inet":
|
|
372
|
-
case "cidr":
|
|
373
|
-
case "macaddr":
|
|
374
|
-
case "macaddr8":
|
|
375
|
-
case "time":
|
|
376
|
-
case "timetz":
|
|
377
|
-
case "time with time zone":
|
|
378
|
-
case "time without time zone":
|
|
379
|
-
case "interval":
|
|
380
|
-
return "string";
|
|
381
|
-
case "date":
|
|
382
|
-
case "timestamp":
|
|
383
|
-
case "timestamptz":
|
|
384
|
-
case "timestamp with time zone":
|
|
385
|
-
case "timestamp without time zone":
|
|
386
|
-
return "Date";
|
|
387
|
-
case "json":
|
|
388
|
-
case "jsonb":
|
|
389
|
-
case "jsonpath":
|
|
390
|
-
return "unknown";
|
|
391
|
-
case "bytea":
|
|
392
|
-
return "Buffer";
|
|
393
|
-
case "void":
|
|
394
|
-
return "void";
|
|
395
|
-
default:
|
|
396
|
-
return "unknown";
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
// ============================================================================
|
|
400
|
-
// Query Method Generators
|
|
401
|
-
// ============================================================================
|
|
402
|
-
const generateFindById = (ctx) => {
|
|
403
|
-
const { entity, executeQueries, defaultSchemas, entityName, exportName, explicitColumns, dbAsParameter } = ctx;
|
|
404
|
-
if (!entity.primaryKey || !entity.permissions.canSelect)
|
|
405
|
-
return undefined;
|
|
406
|
-
const pkColName = entity.primaryKey.columns[0];
|
|
407
|
-
const pkField = findRowField(entity, pkColName);
|
|
408
|
-
if (!pkField)
|
|
409
|
-
return undefined;
|
|
410
|
-
const tableRef = getTableRef(entity, defaultSchemas);
|
|
411
|
-
const fieldName = pkField.name;
|
|
412
|
-
const fieldType = getFieldTypeAst(pkField, ctx);
|
|
413
|
-
let query = chain(selectColumns(selectFrom(tableRef), entity, explicitColumns), "where", [str(pkColName), str("="), id(fieldName)]);
|
|
414
|
-
if (executeQueries) {
|
|
415
|
-
query = chain(query, "executeTakeFirst");
|
|
416
|
-
}
|
|
417
|
-
const optionsParam = param.destructured([{ name: fieldName, type: fieldType }]);
|
|
418
|
-
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
419
|
-
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
420
|
-
const fn = arrowFn(params, query);
|
|
421
|
-
const name = exportName(entityName, "FindById");
|
|
422
|
-
const rowType = entity.shapes.row.name;
|
|
423
|
-
const meta = {
|
|
424
|
-
name,
|
|
425
|
-
kind: "read",
|
|
426
|
-
params: [{
|
|
427
|
-
name: fieldName,
|
|
428
|
-
type: getFieldTypeString(pkField, ctx),
|
|
429
|
-
required: true,
|
|
430
|
-
columnName: pkColName,
|
|
431
|
-
source: "pk",
|
|
432
|
-
}],
|
|
433
|
-
returns: { type: rowType, nullable: true, isArray: false },
|
|
434
|
-
callSignature: { style: "named" },
|
|
435
|
-
};
|
|
436
|
-
return { name, fn, meta };
|
|
437
|
-
};
|
|
438
|
-
const generateListMany = (ctx) => {
|
|
439
|
-
const { entity, executeQueries, defaultSchemas, entityName, exportName, explicitColumns, dbAsParameter, defaultLimit } = ctx;
|
|
440
|
-
if (!entity.permissions.canSelect)
|
|
441
|
-
return undefined;
|
|
442
|
-
const tableRef = getTableRef(entity, defaultSchemas);
|
|
443
|
-
let query = chain(chain(selectColumns(selectFrom(tableRef), entity, explicitColumns), "limit", [id("limit")]), "offset", [id("offset")]);
|
|
444
|
-
if (executeQueries) {
|
|
445
|
-
query = chain(query, "execute");
|
|
446
|
-
}
|
|
447
|
-
const optionsParam = param.destructured([
|
|
448
|
-
{ name: "limit", type: ts.number(), optional: true, defaultValue: b.numericLiteral(defaultLimit) },
|
|
449
|
-
{ name: "offset", type: ts.number(), optional: true, defaultValue: b.numericLiteral(0) },
|
|
450
|
-
]);
|
|
451
|
-
const dbParam = param.typed("db", ts.ref("Kysely", [ts.ref("DB")]));
|
|
452
|
-
const params = dbAsParameter ? [optionsParam, dbParam] : [optionsParam];
|
|
453
|
-
const fn = arrowFn(params, query);
|
|
454
|
-
const name = exportName(entityName, "ListMany");
|
|
455
|
-
const rowType = entity.shapes.row.name;
|
|
456
|
-
const meta = {
|
|
457
|
-
name,
|
|
458
|
-
kind: "list",
|
|
459
|
-
params: [
|
|
460
|
-
{ name: "limit", type: "number", required: false, source: "pagination" },
|
|
461
|
-
{ name: "offset", type: "number", required: false, source: "pagination" },
|
|
462
|
-
],
|
|
463
|
-
returns: { type: rowType, nullable: false, isArray: true },
|
|
464
|
-
callSignature: { style: "named" },
|
|
465
|
-
};
|
|
466
|
-
return { name, fn, meta };
|
|
467
|
-
};
|
|
468
|
-
const generateCreate = (ctx) => {
|
|
469
|
-
const { entity, executeQueries, defaultSchemas, entityName, exportName, dbAsParameter } = ctx;
|
|
470
|
-
if (!entity.permissions.canInsert)
|
|
471
|
-
return undefined;
|
|
472
|
-
const tableRef = getTableRef(entity, defaultSchemas);
|
|
473
|
-
const tableTypeName = getTableTypeName(entity);
|
|
474
|
-
let query = chain(chain(insertInto(tableRef), "values", [id("data")]), "returningAll");
|
|
475
|
-
if (executeQueries) {
|
|
476
|
-
query = chain(query, "executeTakeFirstOrThrow");
|
|
266
|
+
/**
|
|
267
|
+
* Generate enum type alias: `export type Status = "active" | "inactive"`
|
|
268
|
+
*/
|
|
269
|
+
function generateEnumType(enumEntity) {
|
|
270
|
+
return exp.typeAlias(enumEntity.name, { capability: "types:kysely", entity: enumEntity.name }, ts.union(...enumEntity.values.map(v => ts.literal(v)))).node;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Generate composite type interface.
|
|
274
|
+
*/
|
|
275
|
+
function generateCompositeInterface(composite, ctx) {
|
|
276
|
+
const properties = [];
|
|
277
|
+
for (const field of composite.fields) {
|
|
278
|
+
const kyselyType = resolveFieldType(field, ctx);
|
|
279
|
+
const fieldType = buildFieldType(field, kyselyType, false);
|
|
280
|
+
properties.push({ name: field.name, type: fieldType });
|
|
477
281
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
type: insertType,
|
|
493
|
-
required: true,
|
|
494
|
-
source: "body",
|
|
495
|
-
}],
|
|
496
|
-
returns: { type: rowType, nullable: false, isArray: false },
|
|
497
|
-
callSignature: { style: "named", bodyStyle: "property" },
|
|
498
|
-
};
|
|
499
|
-
return { name, fn, meta };
|
|
500
|
-
};
|
|
501
|
-
const generateUpdate = (ctx) => {
|
|
502
|
-
const { entity, executeQueries, defaultSchemas, entityName, exportName, dbAsParameter } = ctx;
|
|
503
|
-
if (!entity.primaryKey || !entity.permissions.canUpdate)
|
|
504
|
-
return undefined;
|
|
505
|
-
const pkColName = entity.primaryKey.columns[0];
|
|
506
|
-
const pkField = findRowField(entity, pkColName);
|
|
507
|
-
if (!pkField)
|
|
508
|
-
return undefined;
|
|
509
|
-
const tableRef = getTableRef(entity, defaultSchemas);
|
|
510
|
-
const fieldName = pkField.name;
|
|
511
|
-
const fieldType = getFieldTypeAst(pkField, ctx);
|
|
512
|
-
const tableTypeName = getTableTypeName(entity);
|
|
513
|
-
let query = chain(chain(chain(updateTable(tableRef), "set", [id("data")]), "where", [
|
|
514
|
-
str(pkColName),
|
|
515
|
-
str("="),
|
|
516
|
-
id(fieldName),
|
|
517
|
-
]), "returningAll");
|
|
518
|
-
if (executeQueries) {
|
|
519
|
-
query = chain(query, "executeTakeFirstOrThrow");
|
|
282
|
+
return exp.interface(composite.name, { capability: "types:kysely", entity: composite.name }, properties).node;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Generate table interface with all column types.
|
|
286
|
+
*/
|
|
287
|
+
function generateTableInterface(entity, ctx) {
|
|
288
|
+
const properties = [];
|
|
289
|
+
for (const field of entity.shapes.row.fields) {
|
|
290
|
+
if (!field.permissions.canSelect)
|
|
291
|
+
continue;
|
|
292
|
+
const kyselyType = resolveFieldType(field, ctx);
|
|
293
|
+
const needsGenerated = isGeneratedField(field);
|
|
294
|
+
const fieldType = buildFieldType(field, kyselyType, needsGenerated);
|
|
295
|
+
properties.push({ name: field.name, type: fieldType });
|
|
520
296
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
params: [
|
|
535
|
-
{
|
|
536
|
-
name: fieldName,
|
|
537
|
-
type: getFieldTypeString(pkField, ctx),
|
|
538
|
-
required: true,
|
|
539
|
-
columnName: pkColName,
|
|
540
|
-
source: "pk",
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
name: "data",
|
|
544
|
-
type: updateType,
|
|
545
|
-
required: true,
|
|
546
|
-
source: "body",
|
|
547
|
-
},
|
|
548
|
-
],
|
|
549
|
-
returns: { type: rowType, nullable: false, isArray: false },
|
|
550
|
-
callSignature: { style: "named", bodyStyle: "property" },
|
|
551
|
-
};
|
|
552
|
-
return { name, fn, meta };
|
|
553
|
-
};
|
|
554
|
-
const generateDelete = (ctx) => {
|
|
555
|
-
const { entity, executeQueries, defaultSchemas, entityName, exportName, dbAsParameter } = ctx;
|
|
556
|
-
if (!entity.primaryKey || !entity.permissions.canDelete)
|
|
557
|
-
return undefined;
|
|
558
|
-
const pkColName = entity.primaryKey.columns[0];
|
|
559
|
-
const pkField = findRowField(entity, pkColName);
|
|
560
|
-
if (!pkField)
|
|
561
|
-
return undefined;
|
|
562
|
-
const tableRef = getTableRef(entity, defaultSchemas);
|
|
563
|
-
const fieldName = pkField.name;
|
|
564
|
-
const fieldType = getFieldTypeAst(pkField, ctx);
|
|
565
|
-
let query = chain(deleteFrom(tableRef), "where", [
|
|
566
|
-
str(pkColName),
|
|
567
|
-
str("="),
|
|
568
|
-
id(fieldName),
|
|
569
|
-
]);
|
|
570
|
-
if (executeQueries) {
|
|
571
|
-
query = chain(query, "execute");
|
|
297
|
+
return exp.interface(entity.name, { capability: "types:kysely", entity: entity.name }, properties).node;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Generate DB interface: `export interface DB { table_name: TableType }`
|
|
301
|
+
*/
|
|
302
|
+
function generateDBInterface(entities, defaultSchemas) {
|
|
303
|
+
const properties = [];
|
|
304
|
+
for (const entity of entities) {
|
|
305
|
+
if (!entity.permissions.canSelect)
|
|
306
|
+
continue;
|
|
307
|
+
// Use schema-qualified key if not in default schema
|
|
308
|
+
const key = defaultSchemas.includes(entity.schemaName) ? entity.pgName : `${entity.schemaName}.${entity.pgName}`;
|
|
309
|
+
properties.push({ name: key, type: ts.ref(entity.name) });
|
|
572
310
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
name,
|
|
580
|
-
kind: "delete",
|
|
581
|
-
params: [{
|
|
582
|
-
name: fieldName,
|
|
583
|
-
type: getFieldTypeString(pkField, ctx),
|
|
584
|
-
required: true,
|
|
585
|
-
columnName: pkColName,
|
|
586
|
-
source: "pk",
|
|
587
|
-
}],
|
|
588
|
-
returns: { type: "void", nullable: false, isArray: false },
|
|
589
|
-
callSignature: { style: "named" },
|
|
590
|
-
};
|
|
591
|
-
return { name, fn, meta };
|
|
592
|
-
};
|
|
593
|
-
const generateCrudMethods = (ctx) => [
|
|
594
|
-
generateFindById(ctx),
|
|
595
|
-
ctx.generateListMany ? generateListMany(ctx) : undefined,
|
|
596
|
-
generateCreate(ctx),
|
|
597
|
-
generateUpdate(ctx),
|
|
598
|
-
generateDelete(ctx),
|
|
599
|
-
].filter((p) => p != null);
|
|
600
|
-
const collectImports = (entities, composites, ctx) => {
|
|
601
|
-
const kyselyImports = new Set();
|
|
602
|
-
const externalImports = new Map();
|
|
603
|
-
let needsJsonTypes = false;
|
|
604
|
-
let needsArrayType = false;
|
|
311
|
+
// Sort by key for stable output
|
|
312
|
+
properties.sort((a, b) => a.name.localeCompare(b.name));
|
|
313
|
+
return exp.interface("DB", { capability: "types:kysely", entity: "DB" }, properties).node;
|
|
314
|
+
}
|
|
315
|
+
function collectTypeImports(entities, composites, ctx) {
|
|
316
|
+
let needsColumnType = false;
|
|
605
317
|
let needsGenerated = false;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
318
|
+
let needsArrayType = false;
|
|
319
|
+
let needsJsonTypes = false;
|
|
320
|
+
const processField = (field, checkGenerated) => {
|
|
321
|
+
const pgType = field.pgAttribute.getType();
|
|
322
|
+
const typeName = pgType?.typname ?? "";
|
|
323
|
+
const kyselyType = resolveFieldType(field, ctx);
|
|
612
324
|
if (kyselyType.needsColumnType) {
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
if (kyselyType.externalImport) {
|
|
616
|
-
const { name, from } = kyselyType.externalImport;
|
|
617
|
-
if (!externalImports.has(from)) {
|
|
618
|
-
externalImports.set(from, new Set());
|
|
619
|
-
}
|
|
620
|
-
externalImports.get(from).add(name);
|
|
325
|
+
needsColumnType = true;
|
|
621
326
|
}
|
|
622
|
-
|
|
623
|
-
const pgType = field.pgAttribute.getType();
|
|
624
|
-
if (pgType?.typname === "json" || pgType?.typname === "jsonb") {
|
|
327
|
+
if (typeName === "json" || typeName === "jsonb") {
|
|
625
328
|
needsJsonTypes = true;
|
|
626
329
|
}
|
|
627
330
|
if (field.isArray && kyselyType.needsColumnType) {
|
|
@@ -637,270 +340,706 @@ const collectImports = (entities, composites, ctx) => {
|
|
|
637
340
|
for (const field of entity.shapes.row.fields) {
|
|
638
341
|
if (!field.permissions.canSelect)
|
|
639
342
|
continue;
|
|
640
|
-
processField(field,
|
|
343
|
+
processField(field, true);
|
|
641
344
|
}
|
|
642
345
|
}
|
|
643
346
|
for (const composite of composites) {
|
|
644
347
|
for (const field of composite.fields) {
|
|
645
|
-
processField(field,
|
|
348
|
+
processField(field, false);
|
|
646
349
|
}
|
|
647
350
|
}
|
|
648
|
-
return {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
// Statement Generators (Types)
|
|
658
|
-
// ============================================================================
|
|
659
|
-
const generateEnumStatement = (enumEntity) => {
|
|
660
|
-
return exp.typeAlias(enumEntity.name, { capability: "types", entity: enumEntity.name }, ts.union(...enumEntity.values.map(v => ts.literal(v))));
|
|
661
|
-
};
|
|
662
|
-
const generateTableInterface = (entity, ctx) => {
|
|
663
|
-
const properties = [];
|
|
664
|
-
for (const field of entity.shapes.row.fields) {
|
|
665
|
-
if (!field.permissions.canSelect)
|
|
666
|
-
continue;
|
|
667
|
-
const kyselyType = resolveFieldTypeForKysely(field, ctx);
|
|
668
|
-
const needsGenerated = isGeneratedField(field);
|
|
669
|
-
const fieldType = buildFieldType(field, kyselyType, needsGenerated);
|
|
670
|
-
properties.push({
|
|
671
|
-
name: field.name,
|
|
672
|
-
type: fieldType,
|
|
673
|
-
});
|
|
351
|
+
return { needsColumnType, needsGenerated, needsArrayType, needsJsonTypes };
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Build the helper types header string.
|
|
355
|
+
*/
|
|
356
|
+
function buildTypesHeader(imports) {
|
|
357
|
+
const lines = [];
|
|
358
|
+
if (imports.needsGenerated) {
|
|
359
|
+
lines.push(`export type Generated<T> = ${GENERATED_TYPE_DEF};`);
|
|
674
360
|
}
|
|
675
|
-
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
const properties = [];
|
|
679
|
-
for (const field of composite.fields) {
|
|
680
|
-
const kyselyType = resolveFieldTypeForKysely(field, ctx);
|
|
681
|
-
const fieldType = buildFieldType(field, kyselyType, false);
|
|
682
|
-
properties.push({
|
|
683
|
-
name: field.name,
|
|
684
|
-
type: fieldType,
|
|
685
|
-
});
|
|
361
|
+
if (imports.needsArrayType) {
|
|
362
|
+
lines.push(`export type ArrayType<T> = ${ARRAY_TYPE_DEF};`);
|
|
363
|
+
lines.push(`export type ArrayTypeImpl<T> = ${ARRAY_TYPE_IMPL_DEF};`);
|
|
686
364
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (!entity.permissions.canSelect)
|
|
693
|
-
continue;
|
|
694
|
-
const key = defaultSchemas.includes(entity.schemaName)
|
|
695
|
-
? entity.pgName
|
|
696
|
-
: `${entity.schemaName}.${entity.pgName}`;
|
|
697
|
-
properties.push({
|
|
698
|
-
name: key,
|
|
699
|
-
type: ts.ref(entity.name),
|
|
700
|
-
});
|
|
365
|
+
if (imports.needsJsonTypes) {
|
|
366
|
+
lines.push(`export type JsonPrimitive = boolean | number | string | null;`);
|
|
367
|
+
lines.push(`export type JsonObject = { [x: string]: JsonValue | undefined };`);
|
|
368
|
+
lines.push(`export type JsonArray = JsonValue[];`);
|
|
369
|
+
lines.push(`export type JsonValue = JsonArray | JsonObject | JsonPrimitive;`);
|
|
701
370
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
};
|
|
371
|
+
return lines.join("\n\n");
|
|
372
|
+
}
|
|
705
373
|
// ============================================================================
|
|
706
|
-
//
|
|
374
|
+
// Query Generation Helpers
|
|
707
375
|
// ============================================================================
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
376
|
+
function buildColumnArray(fields) {
|
|
377
|
+
return conjure.arr(...fields.map(f => str(f.columnName))).build();
|
|
378
|
+
}
|
|
379
|
+
function buildQueryName(inflection, entityName, operation) {
|
|
380
|
+
return inflection.variableName(entityName, operation);
|
|
381
|
+
}
|
|
382
|
+
function buildFindByName(inflection, entityName, columnName) {
|
|
383
|
+
return inflection.variableName(entityName, `FindBy${inflection.pascalCase(columnName)}`);
|
|
384
|
+
}
|
|
385
|
+
function buildListByName(inflection, entityName, columnName) {
|
|
386
|
+
return inflection.variableName(entityName, `ListBy${inflection.pascalCase(columnName)}`);
|
|
387
|
+
}
|
|
388
|
+
function getPgType(field) {
|
|
389
|
+
const pgType = field.pgAttribute.getType();
|
|
390
|
+
return pgType?.typname ?? "unknown";
|
|
391
|
+
}
|
|
392
|
+
function pgTypeToTsType(pgType) {
|
|
393
|
+
const lower = pgType.toLowerCase();
|
|
394
|
+
if (["uuid", "text", "varchar", "char", "citext", "name"].includes(lower))
|
|
395
|
+
return "string";
|
|
396
|
+
if (["int2", "int4", "int8", "integer", "smallint", "bigint", "numeric", "decimal", "real", "float4", "float8"].includes(lower))
|
|
397
|
+
return "number";
|
|
398
|
+
if (["bool", "boolean"].includes(lower))
|
|
399
|
+
return "boolean";
|
|
400
|
+
if (["timestamp", "timestamptz", "date"].includes(lower))
|
|
401
|
+
return "Date";
|
|
402
|
+
if (["json", "jsonb"].includes(lower))
|
|
403
|
+
return "unknown";
|
|
404
|
+
return "string";
|
|
405
|
+
}
|
|
406
|
+
function buildPkParam(field) {
|
|
407
|
+
return {
|
|
408
|
+
name: field.name,
|
|
409
|
+
type: pgTypeToTsType(getPgType(field)),
|
|
410
|
+
required: true,
|
|
411
|
+
columnName: field.columnName,
|
|
412
|
+
source: "pk",
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function buildLookupParam(field) {
|
|
416
|
+
return {
|
|
417
|
+
name: field.name,
|
|
418
|
+
type: pgTypeToTsType(getPgType(field)),
|
|
419
|
+
required: true,
|
|
420
|
+
columnName: field.columnName,
|
|
421
|
+
source: "lookup",
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function buildBodyParam(entityName, shape) {
|
|
425
|
+
const wrapper = shape === "insert" ? "Insertable" : "Updateable";
|
|
426
|
+
return {
|
|
427
|
+
name: "data",
|
|
428
|
+
type: `${wrapper}<${entityName}>`,
|
|
429
|
+
wrapper,
|
|
430
|
+
entityType: entityName,
|
|
431
|
+
required: true,
|
|
432
|
+
source: "body",
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function buildPaginationParams(defaultLimit) {
|
|
436
|
+
return [
|
|
437
|
+
{ name: "limit", type: "number", required: false, defaultValue: defaultLimit, source: "pagination" },
|
|
438
|
+
{ name: "offset", type: "number", required: false, defaultValue: 0, source: "pagination" },
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
function buildReturnType(entityName, isArray, nullable) {
|
|
442
|
+
return {
|
|
443
|
+
type: entityName,
|
|
444
|
+
nullable,
|
|
445
|
+
isArray,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function isBodyParam(p) {
|
|
449
|
+
return "wrapper" in p;
|
|
450
|
+
}
|
|
451
|
+
function isPaginationParam(p) {
|
|
452
|
+
return "defaultValue" in p;
|
|
453
|
+
}
|
|
454
|
+
function buildParamType(p) {
|
|
455
|
+
if (isBodyParam(p)) {
|
|
456
|
+
return ts.ref(p.wrapper, [ts.ref(p.entityType)]);
|
|
457
|
+
}
|
|
458
|
+
return ts.ref(p.type);
|
|
459
|
+
}
|
|
460
|
+
function buildDestructuredParam(params) {
|
|
461
|
+
return param.destructured(params.map(p => ({
|
|
462
|
+
name: p.name,
|
|
463
|
+
type: buildParamType(p),
|
|
464
|
+
optional: "required" in p ? p.required === false : false,
|
|
465
|
+
defaultValue: isPaginationParam(p) ? conjure.num(p.defaultValue) : undefined,
|
|
466
|
+
})));
|
|
467
|
+
}
|
|
721
468
|
// ============================================================================
|
|
722
|
-
//
|
|
469
|
+
// Plugin Definition
|
|
723
470
|
// ============================================================================
|
|
471
|
+
/**
|
|
472
|
+
* Kysely plugin - generates Kysely-compatible types and query functions.
|
|
473
|
+
*
|
|
474
|
+
* Capabilities provided:
|
|
475
|
+
* - `types:kysely:DB` - the DB interface
|
|
476
|
+
* - `types:kysely:EntityName` - table/composite interfaces
|
|
477
|
+
* - `queries:kysely:EntityName:operation` - CRUD query functions
|
|
478
|
+
*/
|
|
724
479
|
export function kysely(config) {
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
|
|
480
|
+
// Parse schema-validated options
|
|
481
|
+
const schemaConfig = S.decodeSync(KyselyConfigSchema)(config ?? {});
|
|
482
|
+
// Debug logging
|
|
483
|
+
// Resolve file naming
|
|
484
|
+
// typesFile is always a single static path (all types in one file)
|
|
485
|
+
// queriesFile can be dynamic per-entity
|
|
730
486
|
const resolvedConfig = {
|
|
731
|
-
...
|
|
732
|
-
|
|
733
|
-
|
|
487
|
+
...schemaConfig,
|
|
488
|
+
typesFile: config?.typesFile ?? "db.ts",
|
|
489
|
+
queriesFile: normalizeFileNaming(config?.queriesFile, "queries.ts"),
|
|
490
|
+
dbImport: config?.dbImport,
|
|
734
491
|
};
|
|
735
|
-
return
|
|
492
|
+
return {
|
|
736
493
|
name: "kysely",
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
494
|
+
provides: ["queries"],
|
|
495
|
+
consumes: [],
|
|
496
|
+
fileDefaults: [
|
|
497
|
+
{
|
|
498
|
+
// All types go to a single file
|
|
499
|
+
pattern: "types:kysely:",
|
|
500
|
+
fileNaming: () => resolvedConfig.typesFile,
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
pattern: "queries:kysely:",
|
|
504
|
+
fileNaming: resolvedConfig.queriesFile,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
declare: Effect.gen(function* () {
|
|
508
|
+
const ir = yield* IR;
|
|
509
|
+
const inflection = yield* Inflection;
|
|
510
|
+
const declarations = [];
|
|
742
511
|
const enumEntities = getEnumEntities(ir);
|
|
743
512
|
const compositeEntities = getCompositeEntities(ir).filter(e => e.tags.omit !== true);
|
|
744
513
|
const tableEntities = getTableEntities(ir).filter(e => e.tags.omit !== true);
|
|
745
|
-
|
|
746
|
-
const dbTypeName = parsed.dbTypeName;
|
|
747
|
-
// ================================================================
|
|
748
|
-
// Generate Types (DB.ts)
|
|
749
|
-
// ================================================================
|
|
750
|
-
const fieldCtx = {
|
|
751
|
-
schemaName: "",
|
|
752
|
-
tableName: "",
|
|
753
|
-
enums: enumEntities,
|
|
754
|
-
composites: compositeEntities,
|
|
755
|
-
extensions: ir.extensions,
|
|
756
|
-
typeHints,
|
|
757
|
-
defaultSchemas,
|
|
758
|
-
};
|
|
759
|
-
const imports = collectImports(tableEntities, compositeEntities, fieldCtx);
|
|
760
|
-
const statements = [];
|
|
514
|
+
// Declare types
|
|
761
515
|
for (const enumEntity of enumEntities) {
|
|
762
516
|
if (enumEntity.tags.omit === true)
|
|
763
517
|
continue;
|
|
764
|
-
|
|
518
|
+
declarations.push({
|
|
519
|
+
name: enumEntity.name,
|
|
520
|
+
capability: `types:kysely:${enumEntity.name}`,
|
|
521
|
+
});
|
|
765
522
|
}
|
|
766
523
|
for (const composite of compositeEntities) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
tableName: composite.pgName,
|
|
771
|
-
}));
|
|
772
|
-
}
|
|
773
|
-
for (const entity of tableEntities) {
|
|
774
|
-
statements.push(generateTableInterface(entity, {
|
|
775
|
-
...fieldCtx,
|
|
776
|
-
schemaName: entity.schemaName,
|
|
777
|
-
tableName: entity.pgName,
|
|
778
|
-
}));
|
|
779
|
-
}
|
|
780
|
-
statements.push(generateDBInterface(tableEntities, defaultSchemas, dbTypeName));
|
|
781
|
-
const dbFilePath = `${parsed.outputDir}/${dbTypeName}.ts`;
|
|
782
|
-
const dbFile = ctx.file(dbFilePath);
|
|
783
|
-
if (imports.kyselyImports.size > 0) {
|
|
784
|
-
dbFile.import({
|
|
785
|
-
kind: "package",
|
|
786
|
-
types: [...imports.kyselyImports],
|
|
787
|
-
from: "kysely",
|
|
524
|
+
declarations.push({
|
|
525
|
+
name: composite.name,
|
|
526
|
+
capability: `types:kysely:${composite.name}`,
|
|
788
527
|
});
|
|
789
528
|
}
|
|
790
|
-
for (const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
types:
|
|
794
|
-
from,
|
|
529
|
+
for (const entity of tableEntities) {
|
|
530
|
+
declarations.push({
|
|
531
|
+
name: entity.name,
|
|
532
|
+
capability: `types:kysely:${entity.name}`,
|
|
795
533
|
});
|
|
796
534
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
if
|
|
802
|
-
|
|
803
|
-
|
|
535
|
+
declarations.push({
|
|
536
|
+
name: "DB",
|
|
537
|
+
capability: "types:kysely:DB",
|
|
538
|
+
});
|
|
539
|
+
// Declare queries if enabled
|
|
540
|
+
if (resolvedConfig.generateQueries) {
|
|
541
|
+
for (const entity of tableEntities) {
|
|
542
|
+
const entityName = entity.name;
|
|
543
|
+
let hasAnyMethods = false;
|
|
544
|
+
if (entity.permissions.canSelect && entity.primaryKey && entity.primaryKey.columns.length > 0) {
|
|
545
|
+
hasAnyMethods = true;
|
|
546
|
+
declarations.push({
|
|
547
|
+
name: buildQueryName(inflection, entityName, "FindById"),
|
|
548
|
+
capability: `queries:kysely:${entityName}:findById`,
|
|
549
|
+
dependsOn: [`types:kysely:${entityName}`],
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
// listByCursor for indexed timestamptz columns
|
|
553
|
+
const cursorCandidates = getCursorPaginationCandidates(entity);
|
|
554
|
+
for (const candidate of cursorCandidates) {
|
|
555
|
+
const listByName = buildListByName(inflection, entityName, candidate.cursorColumnName);
|
|
556
|
+
const pascalColumn = inflection.pascalCase(candidate.cursorColumnName);
|
|
557
|
+
hasAnyMethods = true;
|
|
558
|
+
declarations.push({
|
|
559
|
+
name: listByName,
|
|
560
|
+
capability: `queries:kysely:${entityName}:listBy${pascalColumn}`,
|
|
561
|
+
dependsOn: [`types:kysely:${entityName}`],
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
if (entity.kind === "table" && entity.permissions.canInsert && entity.shapes.insert) {
|
|
565
|
+
hasAnyMethods = true;
|
|
566
|
+
declarations.push({
|
|
567
|
+
name: buildQueryName(inflection, entityName, "Create"),
|
|
568
|
+
capability: `queries:kysely:${entityName}:create`,
|
|
569
|
+
dependsOn: [`types:kysely:${entityName}`],
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
if (entity.kind === "table" &&
|
|
573
|
+
entity.permissions.canUpdate &&
|
|
574
|
+
entity.shapes.update &&
|
|
575
|
+
entity.primaryKey &&
|
|
576
|
+
entity.primaryKey.columns.length > 0) {
|
|
577
|
+
hasAnyMethods = true;
|
|
578
|
+
declarations.push({
|
|
579
|
+
name: buildQueryName(inflection, entityName, "Update"),
|
|
580
|
+
capability: `queries:kysely:${entityName}:update`,
|
|
581
|
+
dependsOn: [`types:kysely:${entityName}`],
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (entity.kind === "table" &&
|
|
585
|
+
entity.permissions.canDelete &&
|
|
586
|
+
entity.primaryKey &&
|
|
587
|
+
entity.primaryKey.columns.length > 0) {
|
|
588
|
+
hasAnyMethods = true;
|
|
589
|
+
declarations.push({
|
|
590
|
+
name: buildQueryName(inflection, entityName, "Delete"),
|
|
591
|
+
capability: `queries:kysely:${entityName}:delete`,
|
|
592
|
+
dependsOn: [`types:kysely:${entityName}`],
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
// findBy queries for indexed columns
|
|
596
|
+
if (entity.permissions.canSelect) {
|
|
597
|
+
const pkColumns = new Set(entity.primaryKey?.columns ?? []);
|
|
598
|
+
const processedColumns = new Set();
|
|
599
|
+
for (const index of entity.indexes) {
|
|
600
|
+
if (index.isPartial || index.hasExpressions || index.columns.length !== 1)
|
|
601
|
+
continue;
|
|
602
|
+
if (index.method === "gin" || index.method === "gist")
|
|
603
|
+
continue;
|
|
604
|
+
const columnName = index.columns[0];
|
|
605
|
+
if (pkColumns.has(columnName))
|
|
606
|
+
continue;
|
|
607
|
+
if (processedColumns.has(columnName))
|
|
608
|
+
continue;
|
|
609
|
+
processedColumns.add(columnName);
|
|
610
|
+
const findByName = buildFindByName(inflection, entityName, columnName);
|
|
611
|
+
const pascalColumn = inflection.pascalCase(columnName);
|
|
612
|
+
hasAnyMethods = true;
|
|
613
|
+
declarations.push({
|
|
614
|
+
name: findByName,
|
|
615
|
+
capability: `queries:kysely:${entityName}:findBy${pascalColumn}`,
|
|
616
|
+
dependsOn: [`types:kysely:${entityName}`],
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (hasAnyMethods) {
|
|
621
|
+
declarations.push({
|
|
622
|
+
name: `${entityName}Queries`,
|
|
623
|
+
capability: `queries:kysely:${entityName}`,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
804
627
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
628
|
+
return declarations;
|
|
629
|
+
}),
|
|
630
|
+
render: Effect.gen(function* () {
|
|
631
|
+
const ir = yield* IR;
|
|
632
|
+
const inflection = yield* Inflection;
|
|
633
|
+
const symbols = [];
|
|
634
|
+
const enumEntities = getEnumEntities(ir);
|
|
635
|
+
const compositeEntities = getCompositeEntities(ir).filter(e => e.tags.omit !== true);
|
|
636
|
+
const tableEntities = getTableEntities(ir).filter(e => e.tags.omit !== true);
|
|
637
|
+
const defaultSchemas = ir.schemas;
|
|
638
|
+
const typeCtx = {
|
|
639
|
+
enums: enumEntities,
|
|
640
|
+
composites: compositeEntities,
|
|
641
|
+
};
|
|
642
|
+
// Collect imports for types
|
|
643
|
+
const typeImports = collectTypeImports(tableEntities, compositeEntities, typeCtx);
|
|
644
|
+
// Build kysely imports for types file
|
|
645
|
+
const kyselyTypeImports = [];
|
|
646
|
+
if (typeImports.needsColumnType) {
|
|
647
|
+
kyselyTypeImports.push("ColumnType");
|
|
810
648
|
}
|
|
811
|
-
|
|
812
|
-
|
|
649
|
+
const typesHeader = buildTypesHeader(typeImports);
|
|
650
|
+
const typesExternalImports = kyselyTypeImports.length > 0 ? [{ from: "kysely", types: kyselyTypeImports }] : [];
|
|
651
|
+
// Generate enum types
|
|
652
|
+
for (const enumEntity of enumEntities) {
|
|
653
|
+
if (enumEntity.tags.omit === true)
|
|
654
|
+
continue;
|
|
655
|
+
symbols.push({
|
|
656
|
+
name: enumEntity.name,
|
|
657
|
+
capability: `types:kysely:${enumEntity.name}`,
|
|
658
|
+
node: generateEnumType(enumEntity),
|
|
659
|
+
exports: "named",
|
|
660
|
+
externalImports: typesExternalImports,
|
|
661
|
+
fileHeader: typesHeader,
|
|
662
|
+
});
|
|
813
663
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
664
|
+
// Generate composite interfaces
|
|
665
|
+
for (const composite of compositeEntities) {
|
|
666
|
+
symbols.push({
|
|
667
|
+
name: composite.name,
|
|
668
|
+
capability: `types:kysely:${composite.name}`,
|
|
669
|
+
node: generateCompositeInterface(composite, typeCtx),
|
|
670
|
+
exports: "named",
|
|
671
|
+
externalImports: typesExternalImports,
|
|
672
|
+
fileHeader: typesHeader,
|
|
673
|
+
});
|
|
820
674
|
}
|
|
821
|
-
|
|
822
|
-
const exportStyle = resolvedConfig.exportStyle;
|
|
675
|
+
// Generate table interfaces
|
|
823
676
|
for (const entity of tableEntities) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
677
|
+
symbols.push({
|
|
678
|
+
name: entity.name,
|
|
679
|
+
capability: `types:kysely:${entity.name}`,
|
|
680
|
+
node: generateTableInterface(entity, typeCtx),
|
|
681
|
+
exports: "named",
|
|
682
|
+
externalImports: typesExternalImports,
|
|
683
|
+
fileHeader: typesHeader,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
// Generate DB interface
|
|
687
|
+
symbols.push({
|
|
688
|
+
name: "DB",
|
|
689
|
+
capability: "types:kysely:DB",
|
|
690
|
+
node: generateDBInterface(tableEntities, defaultSchemas),
|
|
691
|
+
exports: "named",
|
|
692
|
+
externalImports: typesExternalImports,
|
|
693
|
+
fileHeader: typesHeader,
|
|
694
|
+
});
|
|
695
|
+
// Generate queries if enabled
|
|
696
|
+
if (resolvedConfig.generateQueries) {
|
|
697
|
+
// User module imports for db instance (only if not using dbAsParameter)
|
|
698
|
+
const queryUserImports = !resolvedConfig.dbAsParameter && resolvedConfig.dbImport
|
|
699
|
+
? [resolvedConfig.dbImport]
|
|
700
|
+
: undefined;
|
|
701
|
+
for (const entity of tableEntities) {
|
|
702
|
+
const entityName = entity.name;
|
|
703
|
+
const tableName = ir.schemas.includes(entity.schemaName)
|
|
704
|
+
? entity.pgName
|
|
705
|
+
: `${entity.schemaName}.${entity.pgName}`;
|
|
706
|
+
const entityMethods = [];
|
|
707
|
+
// findById
|
|
708
|
+
if (entity.permissions.canSelect && entity.primaryKey && entity.primaryKey.columns.length > 0) {
|
|
709
|
+
const pkColumn = entity.primaryKey.columns[0];
|
|
710
|
+
const pkField = entity.shapes.row.fields.find(f => f.columnName === pkColumn);
|
|
711
|
+
const pkParam = buildPkParam(pkField);
|
|
712
|
+
const method = {
|
|
713
|
+
name: buildQueryName(inflection, entityName, "FindById"),
|
|
714
|
+
kind: "read",
|
|
715
|
+
params: [pkParam],
|
|
716
|
+
returns: buildReturnType(entityName, false, true),
|
|
717
|
+
callSignature: { style: "named" },
|
|
718
|
+
};
|
|
719
|
+
entityMethods.push(method);
|
|
720
|
+
const queryExpr = chain(b.identifier("db"))
|
|
721
|
+
.method("selectFrom", [str(tableName)])
|
|
722
|
+
.method("select", [buildColumnArray(entity.shapes.row.fields)])
|
|
723
|
+
.method("where", [
|
|
724
|
+
str(pkColumn),
|
|
725
|
+
str("="),
|
|
726
|
+
b.identifier(pkField.name),
|
|
727
|
+
])
|
|
728
|
+
.build();
|
|
729
|
+
const destructuredParam = buildDestructuredParam([pkParam]);
|
|
730
|
+
let fnBuilder = fn();
|
|
731
|
+
if (resolvedConfig.dbAsParameter) {
|
|
732
|
+
fnBuilder = fnBuilder.param("db", ts.ref("Kysely"));
|
|
733
|
+
}
|
|
734
|
+
const fnExpr = fnBuilder.rawParam(destructuredParam).arrow().body(stmt.return(queryExpr)).build();
|
|
735
|
+
symbols.push({
|
|
736
|
+
name: method.name,
|
|
737
|
+
capability: `queries:kysely:${entityName}:findById`,
|
|
738
|
+
node: exp.const(method.name, { capability: "", entity: entityName }, fnExpr).node,
|
|
739
|
+
exports: "named",
|
|
740
|
+
externalImports: resolvedConfig.dbAsParameter ? [{ from: "kysely", names: ["Kysely"] }] : [],
|
|
741
|
+
userImports: queryUserImports,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
// listByCursor for indexed timestamptz columns
|
|
745
|
+
const cursorCandidates = getCursorPaginationCandidates(entity);
|
|
746
|
+
for (const candidate of cursorCandidates) {
|
|
747
|
+
const pascalColumn = inflection.pascalCase(candidate.cursorColumnName);
|
|
748
|
+
const pkField = entity.shapes.row.fields.find(f => f.name === candidate.pkColumn);
|
|
749
|
+
if (!pkField)
|
|
750
|
+
continue;
|
|
751
|
+
const cursorType = ts.objectType([
|
|
752
|
+
{ name: candidate.cursorColumn, type: ts.ref("Date") },
|
|
753
|
+
{ name: candidate.pkColumn, type: ts.ref(pgTypeToTsType(getPgType(pkField))) },
|
|
754
|
+
]);
|
|
755
|
+
const method = {
|
|
756
|
+
name: buildListByName(inflection, entityName, candidate.cursorColumnName),
|
|
757
|
+
kind: "list",
|
|
758
|
+
params: [],
|
|
759
|
+
returns: buildReturnType(entityName, true, false),
|
|
760
|
+
callSignature: { style: "named" },
|
|
761
|
+
};
|
|
762
|
+
entityMethods.push(method);
|
|
763
|
+
const cursorParam = param.destructured([
|
|
764
|
+
{ name: "cursor", type: ts.union(cursorType, ts.undefined()), optional: true },
|
|
765
|
+
{ name: "limit", type: ts.number(), optional: true, defaultValue: conjure.num(resolvedConfig.defaultLimit) },
|
|
766
|
+
]);
|
|
767
|
+
const cursorComparisonOp = candidate.desc ? "<" : ">";
|
|
768
|
+
const orderDirection = candidate.desc ? "desc" : "asc";
|
|
769
|
+
const cursorCondition = b.callExpression(b.identifier("eb"), [
|
|
770
|
+
str(candidate.cursorColumnName),
|
|
771
|
+
str(cursorComparisonOp),
|
|
772
|
+
b.memberExpression(b.identifier("cursor"), b.identifier(candidate.cursorColumn)),
|
|
773
|
+
]);
|
|
774
|
+
const pkCondition = b.callExpression(b.identifier("eb"), [
|
|
775
|
+
str(candidate.pkColumnName),
|
|
776
|
+
str(cursorComparisonOp),
|
|
777
|
+
b.memberExpression(b.identifier("cursor"), b.identifier(candidate.pkColumn)),
|
|
778
|
+
]);
|
|
779
|
+
const equalityCondition = b.callExpression(b.identifier("eb"), [
|
|
780
|
+
str(candidate.cursorColumnName),
|
|
781
|
+
str("="),
|
|
782
|
+
b.memberExpression(b.identifier("cursor"), b.identifier(candidate.cursorColumn)),
|
|
783
|
+
]);
|
|
784
|
+
const andClause = chain(b.identifier("eb"))
|
|
785
|
+
.method("and", [
|
|
786
|
+
arrExpr(equalityCondition, pkCondition),
|
|
787
|
+
])
|
|
788
|
+
.build();
|
|
789
|
+
const whereClause = chain(b.identifier("eb"))
|
|
790
|
+
.method("or", [
|
|
791
|
+
arrExpr(cursorCondition, andClause),
|
|
792
|
+
])
|
|
793
|
+
.build();
|
|
794
|
+
const queryExpr = chain(b.identifier("db"))
|
|
795
|
+
.method("selectFrom", [str(tableName)])
|
|
796
|
+
.method("select", [buildColumnArray(entity.shapes.row.fields)])
|
|
797
|
+
.method("$if", [
|
|
798
|
+
b.binaryExpression("!==", b.identifier("cursor"), b.identifier("undefined")),
|
|
799
|
+
fn()
|
|
800
|
+
.param("qb")
|
|
801
|
+
.arrow()
|
|
802
|
+
.body(stmt.return(chain(b.identifier("qb"))
|
|
803
|
+
.method("where", [b.arrowFunctionExpression([b.identifier("eb")], cast.toExpr(whereClause))])
|
|
804
|
+
.build()))
|
|
805
|
+
.build(),
|
|
806
|
+
])
|
|
807
|
+
.method("orderBy", [str(candidate.cursorColumnName), str(orderDirection)])
|
|
808
|
+
.method("orderBy", [str(candidate.pkColumnName), str(orderDirection)])
|
|
809
|
+
.method("limit", [b.identifier("limit")])
|
|
810
|
+
.build();
|
|
811
|
+
let fnBuilder = fn();
|
|
812
|
+
if (resolvedConfig.dbAsParameter) {
|
|
813
|
+
fnBuilder = fnBuilder.param("db", ts.ref("Kysely"));
|
|
814
|
+
}
|
|
815
|
+
const fnExpr = fnBuilder.rawParam(cursorParam).arrow().body(stmt.return(queryExpr)).build();
|
|
816
|
+
symbols.push({
|
|
817
|
+
name: method.name,
|
|
818
|
+
capability: `queries:kysely:${entityName}:listBy${pascalColumn}`,
|
|
819
|
+
node: exp.const(method.name, { capability: "", entity: entityName }, fnExpr).node,
|
|
820
|
+
exports: "named",
|
|
821
|
+
externalImports: resolvedConfig.dbAsParameter ? [{ from: "kysely", names: ["Kysely"] }] : [],
|
|
822
|
+
userImports: queryUserImports,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
// create
|
|
826
|
+
if (entity.kind === "table" && entity.permissions.canInsert && entity.shapes.insert) {
|
|
827
|
+
const bodyParam = buildBodyParam(entityName, "insert");
|
|
828
|
+
const method = {
|
|
829
|
+
name: buildQueryName(inflection, entityName, "Create"),
|
|
830
|
+
kind: "create",
|
|
831
|
+
params: [bodyParam],
|
|
832
|
+
returns: buildReturnType(entityName, false, false),
|
|
833
|
+
callSignature: { style: "named", bodyStyle: "spread" },
|
|
834
|
+
};
|
|
835
|
+
entityMethods.push(method);
|
|
836
|
+
const queryExpr = chain(b.identifier("db"))
|
|
837
|
+
.method("insertInto", [str(tableName)])
|
|
838
|
+
.method("values", [b.identifier("data")])
|
|
839
|
+
.method("returningAll", [])
|
|
840
|
+
.build();
|
|
841
|
+
// Simple typed parameter: (data: Insertable<Entity>)
|
|
842
|
+
let fnBuilder = fn();
|
|
843
|
+
if (resolvedConfig.dbAsParameter) {
|
|
844
|
+
fnBuilder = fnBuilder.param("db", ts.ref("Kysely"));
|
|
845
|
+
}
|
|
846
|
+
const fnExpr = fnBuilder
|
|
847
|
+
.param("data", ts.ref("Insertable", [ts.ref(entityName)]))
|
|
848
|
+
.arrow()
|
|
849
|
+
.body(stmt.return(queryExpr))
|
|
850
|
+
.build();
|
|
851
|
+
symbols.push({
|
|
852
|
+
name: method.name,
|
|
853
|
+
capability: `queries:kysely:${entityName}:create`,
|
|
854
|
+
node: exp.const(method.name, { capability: "", entity: entityName }, fnExpr).node,
|
|
855
|
+
exports: "named",
|
|
856
|
+
externalImports: [
|
|
857
|
+
{
|
|
858
|
+
from: "kysely",
|
|
859
|
+
names: resolvedConfig.dbAsParameter ? ["Kysely"] : [],
|
|
860
|
+
types: ["Insertable"],
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
from: resolvedConfig.typesFile,
|
|
864
|
+
types: [entityName],
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
userImports: queryUserImports,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
// update
|
|
871
|
+
if (entity.kind === "table" &&
|
|
872
|
+
entity.permissions.canUpdate &&
|
|
873
|
+
entity.shapes.update &&
|
|
874
|
+
entity.primaryKey &&
|
|
875
|
+
entity.primaryKey.columns.length > 0) {
|
|
876
|
+
const pkColumn = entity.primaryKey.columns[0];
|
|
877
|
+
const pkField = entity.shapes.row.fields.find(f => f.columnName === pkColumn);
|
|
878
|
+
const pkParam = buildPkParam(pkField);
|
|
879
|
+
const bodyParam = buildBodyParam(entityName, "update");
|
|
880
|
+
const method = {
|
|
881
|
+
name: buildQueryName(inflection, entityName, "Update"),
|
|
882
|
+
kind: "update",
|
|
883
|
+
params: [pkParam, bodyParam],
|
|
884
|
+
returns: buildReturnType(entityName, false, true),
|
|
885
|
+
callSignature: { style: "named", bodyStyle: "spread" },
|
|
886
|
+
};
|
|
887
|
+
entityMethods.push(method);
|
|
888
|
+
const queryExpr = chain(b.identifier("db"))
|
|
889
|
+
.method("updateTable", [str(tableName)])
|
|
890
|
+
.method("set", [b.identifier("data")])
|
|
891
|
+
.method("where", [
|
|
892
|
+
str(pkColumn),
|
|
893
|
+
str("="),
|
|
894
|
+
b.identifier(pkField.name),
|
|
895
|
+
])
|
|
896
|
+
.method("returningAll", [])
|
|
897
|
+
.build();
|
|
898
|
+
// Use param.withRest for flat destructuring: ({ id, ...data }: { id: string } & Omit<Updateable<Entity>, 'id'>)
|
|
899
|
+
// Using Omit ensures `data` doesn't include the PK field
|
|
900
|
+
const destructuredParam = param.withRest([{ name: pkField.name, type: ts.ref(pkParam.type) }], "data", ts.ref("Omit", [
|
|
901
|
+
ts.ref("Updateable", [ts.ref(entityName)]),
|
|
902
|
+
ts.literal(pkField.name),
|
|
903
|
+
]));
|
|
904
|
+
let fnBuilder = fn();
|
|
905
|
+
if (resolvedConfig.dbAsParameter) {
|
|
906
|
+
fnBuilder = fnBuilder.param("db", ts.ref("Kysely"));
|
|
907
|
+
}
|
|
908
|
+
const fnExpr = fnBuilder.rawParam(destructuredParam).arrow().body(stmt.return(queryExpr)).build();
|
|
909
|
+
symbols.push({
|
|
910
|
+
name: method.name,
|
|
911
|
+
capability: `queries:kysely:${entityName}:update`,
|
|
912
|
+
node: exp.const(method.name, { capability: "", entity: entityName }, fnExpr).node,
|
|
913
|
+
exports: "named",
|
|
914
|
+
externalImports: [
|
|
915
|
+
{
|
|
916
|
+
from: "kysely",
|
|
917
|
+
names: resolvedConfig.dbAsParameter ? ["Kysely"] : [],
|
|
918
|
+
types: ["Updateable"],
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
from: resolvedConfig.typesFile,
|
|
922
|
+
types: [entityName],
|
|
923
|
+
},
|
|
924
|
+
],
|
|
925
|
+
userImports: queryUserImports,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
// delete
|
|
929
|
+
if (entity.kind === "table" &&
|
|
930
|
+
entity.permissions.canDelete &&
|
|
931
|
+
entity.primaryKey &&
|
|
932
|
+
entity.primaryKey.columns.length > 0) {
|
|
933
|
+
const pkColumn = entity.primaryKey.columns[0];
|
|
934
|
+
const pkField = entity.shapes.row.fields.find(f => f.columnName === pkColumn);
|
|
935
|
+
const pkParam = buildPkParam(pkField);
|
|
936
|
+
const method = {
|
|
937
|
+
name: buildQueryName(inflection, entityName, "Delete"),
|
|
938
|
+
kind: "delete",
|
|
939
|
+
params: [pkParam],
|
|
940
|
+
returns: buildReturnType(entityName, false, false),
|
|
941
|
+
callSignature: { style: "named" },
|
|
942
|
+
};
|
|
943
|
+
entityMethods.push(method);
|
|
944
|
+
const queryExpr = chain(b.identifier("db"))
|
|
945
|
+
.method("deleteFrom", [str(tableName)])
|
|
946
|
+
.method("where", [
|
|
947
|
+
str(pkColumn),
|
|
948
|
+
str("="),
|
|
949
|
+
b.identifier(pkField.name),
|
|
950
|
+
])
|
|
951
|
+
.build();
|
|
952
|
+
const destructuredParam = buildDestructuredParam([pkParam]);
|
|
953
|
+
let fnBuilder = fn();
|
|
954
|
+
if (resolvedConfig.dbAsParameter) {
|
|
955
|
+
fnBuilder = fnBuilder.param("db", ts.ref("Kysely"));
|
|
956
|
+
}
|
|
957
|
+
const fnExpr = fnBuilder.rawParam(destructuredParam).arrow().body(stmt.return(queryExpr)).build();
|
|
958
|
+
symbols.push({
|
|
959
|
+
name: method.name,
|
|
960
|
+
capability: `queries:kysely:${entityName}:delete`,
|
|
961
|
+
node: exp.const(method.name, { capability: "", entity: entityName }, fnExpr).node,
|
|
962
|
+
exports: "named",
|
|
963
|
+
externalImports: resolvedConfig.dbAsParameter ? [{ from: "kysely", names: ["Kysely"] }] : [],
|
|
964
|
+
userImports: queryUserImports,
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
// findBy queries for indexed columns
|
|
968
|
+
if (entity.permissions.canSelect) {
|
|
969
|
+
const pkColumns = new Set(entity.primaryKey?.columns ?? []);
|
|
970
|
+
const processedColumns = new Set();
|
|
971
|
+
for (const index of entity.indexes) {
|
|
972
|
+
if (index.isPartial || index.hasExpressions || index.columns.length !== 1)
|
|
973
|
+
continue;
|
|
974
|
+
if (index.method === "gin" || index.method === "gist")
|
|
975
|
+
continue;
|
|
976
|
+
const columnName = index.columns[0];
|
|
977
|
+
if (pkColumns.has(columnName))
|
|
978
|
+
continue;
|
|
979
|
+
if (processedColumns.has(columnName))
|
|
980
|
+
continue;
|
|
981
|
+
processedColumns.add(columnName);
|
|
982
|
+
const field = entity.shapes.row.fields.find(f => f.columnName === columnName);
|
|
983
|
+
if (!field)
|
|
984
|
+
continue;
|
|
985
|
+
const pascalColumn = inflection.pascalCase(columnName);
|
|
986
|
+
const isUnique = index.isUnique;
|
|
987
|
+
const lookupParam = buildLookupParam(field);
|
|
988
|
+
const method = {
|
|
989
|
+
name: buildFindByName(inflection, entityName, columnName),
|
|
990
|
+
kind: "lookup",
|
|
991
|
+
params: [lookupParam],
|
|
992
|
+
returns: buildReturnType(entityName, !isUnique, isUnique),
|
|
993
|
+
lookupField: field.name,
|
|
994
|
+
isUniqueLookup: isUnique,
|
|
995
|
+
callSignature: { style: "named" },
|
|
996
|
+
};
|
|
997
|
+
entityMethods.push(method);
|
|
998
|
+
const queryExpr = chain(b.identifier("db"))
|
|
999
|
+
.method("selectFrom", [str(tableName)])
|
|
1000
|
+
.method("select", [buildColumnArray(entity.shapes.row.fields)])
|
|
1001
|
+
.method("where", [
|
|
1002
|
+
str(columnName),
|
|
1003
|
+
str("="),
|
|
1004
|
+
b.identifier(field.name),
|
|
1005
|
+
])
|
|
1006
|
+
.build();
|
|
1007
|
+
const destructuredParam = buildDestructuredParam([lookupParam]);
|
|
1008
|
+
let fnBuilder = fn();
|
|
1009
|
+
if (resolvedConfig.dbAsParameter) {
|
|
1010
|
+
fnBuilder = fnBuilder.param("db", ts.ref("Kysely"));
|
|
1011
|
+
}
|
|
1012
|
+
const fnExpr = fnBuilder.rawParam(destructuredParam).arrow().body(stmt.return(queryExpr)).build();
|
|
1013
|
+
symbols.push({
|
|
1014
|
+
name: method.name,
|
|
1015
|
+
capability: `queries:kysely:${entityName}:findBy${pascalColumn}`,
|
|
1016
|
+
node: exp.const(method.name, { capability: "", entity: entityName }, fnExpr).node,
|
|
1017
|
+
exports: "named",
|
|
1018
|
+
externalImports: resolvedConfig.dbAsParameter ? [{ from: "kysely", names: ["Kysely"] }] : [],
|
|
1019
|
+
userImports: queryUserImports,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
// Entity queries metadata (no importPath - emit phase handles file resolution)
|
|
1024
|
+
const pkField = entity.primaryKey?.columns[0]
|
|
1025
|
+
? entity.shapes.row.fields.find(f => f.columnName === entity.primaryKey.columns[0])
|
|
1026
|
+
: undefined;
|
|
1027
|
+
const entityExtension = {
|
|
1028
|
+
methods: entityMethods,
|
|
1029
|
+
pkType: pkField ? pgTypeToTsType(getPgType(pkField)) : undefined,
|
|
1030
|
+
hasCompositePk: (entity.primaryKey?.columns.length ?? 0) > 1,
|
|
1031
|
+
};
|
|
1032
|
+
symbols.push({
|
|
1033
|
+
name: `${entityName}Queries`,
|
|
1034
|
+
capability: `queries:kysely:${entityName}`,
|
|
1035
|
+
node: b.stringLiteral(""),
|
|
1036
|
+
metadata: entityExtension,
|
|
1037
|
+
exports: false,
|
|
865
1038
|
});
|
|
866
1039
|
}
|
|
867
|
-
// Only import DB if dbAsParameter is true
|
|
868
|
-
const dbImportTypes = parsed.dbAsParameter ? [dbTypeName, entityName] : [entityName];
|
|
869
|
-
file.import({
|
|
870
|
-
kind: "relative",
|
|
871
|
-
types: dbImportTypes,
|
|
872
|
-
from: resolvedConfig.dbTypesPath,
|
|
873
|
-
});
|
|
874
|
-
const stmts = toStatements(methods, exportStyle, entityName);
|
|
875
|
-
file.ast(conjure.program(...stmts)).emit();
|
|
876
|
-
const pkType = entity.primaryKey
|
|
877
|
-
? getFieldTypeString(findRowField(entity, entity.primaryKey.columns[0]), genCtx)
|
|
878
|
-
: "unknown";
|
|
879
|
-
ctx.symbols.registerEntityMethods({
|
|
880
|
-
entity: entityName,
|
|
881
|
-
importPath: filePath,
|
|
882
|
-
pkType,
|
|
883
|
-
hasCompositePk: (entity.primaryKey?.columns.length ?? 0) > 1,
|
|
884
|
-
methods: methods.map(m => ({
|
|
885
|
-
name: m.meta.name,
|
|
886
|
-
file: filePath,
|
|
887
|
-
entity: entityName,
|
|
888
|
-
kind: m.meta.kind,
|
|
889
|
-
params: m.meta.params.map(p => ({
|
|
890
|
-
name: p.name,
|
|
891
|
-
type: p.type,
|
|
892
|
-
required: p.required,
|
|
893
|
-
columnName: p.columnName,
|
|
894
|
-
source: p.source,
|
|
895
|
-
})),
|
|
896
|
-
returns: m.meta.returns,
|
|
897
|
-
lookupField: m.meta.lookupField,
|
|
898
|
-
isUniqueLookup: m.meta.isUniqueLookup,
|
|
899
|
-
callSignature: m.meta.callSignature,
|
|
900
|
-
})),
|
|
901
|
-
}, "kysely");
|
|
902
1040
|
}
|
|
903
|
-
|
|
904
|
-
|
|
1041
|
+
return symbols;
|
|
1042
|
+
}),
|
|
1043
|
+
};
|
|
905
1044
|
}
|
|
906
1045
|
//# sourceMappingURL=kysely.js.map
|