@vibeorm/generator 1.3.0 → 2.0.0-alpha.1

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.
@@ -1,130 +0,0 @@
1
- import type { Model, Schema, Field, ScalarField, EnumField, RelationField } from "@vibeorm/parser";
2
- import { fileHeader, toCamelCase } from "../utils.ts";
3
-
4
- /**
5
- * Generates the Payload types for each model.
6
- * These split each model into { scalars, objects, composites }
7
- * which is the foundation for select/include type narrowing.
8
- */
9
- export function generateModels(params: { schema: Schema }): string {
10
- const { schema } = params;
11
- const parts: string[] = [fileHeader()];
12
-
13
- // Import enums if any
14
- if (schema.enums.length > 0) {
15
- const enumImports = schema.enums.map((e) => e.name).join(", ");
16
- parts.push(`import type { ${enumImports} } from "./enums.ts";\n`);
17
- }
18
-
19
- // Generate JsonValue type.
20
- //
21
- // Arrays and object index signatures are `readonly` so that `readonly T[]` /
22
- // `ReadonlyArray<T>` (common in typed catalogues, factories, seed scripts) can
23
- // be assigned into Json columns without forcing a spread/cast at every call
24
- // site. Postgres doesn't care about JS array mutability — this is a purely
25
- // type-level relaxation that's strictly more permissive in argument position.
26
- parts.push(`export type JsonValue = string | number | boolean | null | readonly JsonValue[] | { readonly [key: string]: JsonValue };\n`);
27
-
28
- // InputJsonValue — looser than JsonValue, used in CreateInput/UpdateInput
29
- // for Json fields (Bug 8). Accepts:
30
- // - all JsonValue shapes (readonly arrays / readonly object index sigs)
31
- // - any object exposing toJSON() (Date, Decimal wrappers, BigInt wrappers, …)
32
- //
33
- // The `toJSON` escape hatch is what lets users feed seed/factory data typed
34
- // as `Record<string, unknown>` via a single intentional cast, instead of
35
- // rewriting every call site. Postgres jsonb doesn't care about the JS shape —
36
- // the adapter JSON.stringifies whatever it gets. Output types keep the strict
37
- // `JsonValue`.
38
- parts.push(`export type InputJsonObject = { readonly [key: string]: InputJsonValue | null | undefined };
39
- export type InputJsonArray = ReadonlyArray<InputJsonValue | null>;
40
- export type InputJsonValue =
41
- | string
42
- | number
43
- | boolean
44
- | InputJsonObject
45
- | InputJsonArray
46
- | { toJSON(): unknown };
47
- `);
48
-
49
- // Generate the base OperationPayload interface
50
- parts.push(`export type OperationPayload = {
51
- name: string;
52
- scalars: Record<string, unknown>;
53
- objects: Record<string, unknown>;
54
- composites: Record<string, unknown>;
55
- };\n`);
56
-
57
- // Generate payload type for each model
58
- for (const model of schema.models) {
59
- parts.push(generatePayloadType({ model, schema }));
60
- }
61
-
62
- // Generate plain model types (default selection = scalars only)
63
- for (const model of schema.models) {
64
- parts.push(generatePlainModelType({ model, schema }));
65
- }
66
-
67
- return parts.join("\n");
68
- }
69
-
70
- function generatePayloadType(params: { model: Model; schema: Schema }): string {
71
- const { model, schema } = params;
72
-
73
- const scalarFields = model.fields.filter(
74
- (f): f is ScalarField | EnumField => f.kind === "scalar" || f.kind === "enum"
75
- );
76
- const relationFields = model.fields.filter(
77
- (f): f is RelationField => f.kind === "relation"
78
- );
79
-
80
- // Scalars block
81
- const scalarsEntries = scalarFields.map((f) => {
82
- const tsType = f.kind === "scalar" ? f.tsType : f.enumName;
83
- const nullable = f.isRequired ? "" : " | null";
84
- const listPrefix = f.isList ? "" : "";
85
- const listSuffix = f.isList ? "[]" : "";
86
- return ` ${f.name}: ${listPrefix}${tsType}${nullable}${listSuffix};`;
87
- });
88
-
89
- // Objects block
90
- const objectsEntries = relationFields.map((f) => {
91
- const payloadType = `$${f.relatedModel}Payload`;
92
- if (f.isList) {
93
- return ` ${f.name}: ${payloadType}[];`;
94
- }
95
- const nullable = f.isRequired ? "" : " | null";
96
- return ` ${f.name}: ${payloadType}${nullable};`;
97
- });
98
-
99
- return `export type $${model.name}Payload = {
100
- name: "${model.name}";
101
- scalars: {
102
- ${scalarsEntries.join("\n")}
103
- };
104
- objects: {
105
- ${objectsEntries.join("\n")}
106
- };
107
- composites: {};
108
- };
109
- `;
110
- }
111
-
112
- function generatePlainModelType(params: { model: Model; schema: Schema }): string {
113
- const { model } = params;
114
-
115
- const scalarFields = model.fields.filter(
116
- (f): f is ScalarField | EnumField => f.kind === "scalar" || f.kind === "enum"
117
- );
118
-
119
- const entries = scalarFields.map((f) => {
120
- const tsType = f.kind === "scalar" ? f.tsType : f.enumName;
121
- const nullable = f.isRequired ? "" : " | null";
122
- const listSuffix = f.isList ? "[]" : "";
123
- return ` ${f.name}: ${tsType}${nullable}${listSuffix};`;
124
- });
125
-
126
- return `export type ${model.name} = {
127
- ${entries.join("\n")}
128
- };
129
- `;
130
- }
@@ -1,182 +0,0 @@
1
- import { fileHeader } from "../utils.ts";
2
- import type { Schema } from "@vibeorm/parser";
3
-
4
- /**
5
- * Generates the core type resolution engine.
6
- *
7
- * This produces the conditional types that narrow return types
8
- * based on the select/include args passed to operations.
9
- *
10
- * The pattern:
11
- * - No select/include → DefaultSelection (scalars only)
12
- * - select: { field: true } → only selected fields
13
- * - include: { relation: true } → scalars + included relations
14
- * - Nested select/include in relations → recursive narrowing
15
- */
16
- export function generateResult(params: { schema: Schema }): string {
17
- const { schema } = params;
18
- const parts: string[] = [fileHeader()];
19
-
20
- // Import payload types
21
- const payloadImports = schema.models
22
- .map((m) => `$${m.name}Payload`)
23
- .join(", ");
24
- parts.push(
25
- `import type { ${payloadImports}, OperationPayload } from "./models.ts";\n`
26
- );
27
-
28
- // ─── Utility Types ────────────────────────────────────────────────
29
-
30
- parts.push(`
31
- // ─── Core Utility Types ───────────────────────────────────────────
32
-
33
- /** Detect if T is exactly \`any\` */
34
- type IsAny<T> = 0 extends 1 & T ? true : false;
35
-
36
- /** Flatten intersection types for cleaner hover tooltips */
37
- type Compute<T> = T extends Function ? T : { [K in keyof T]: T[K] } & unknown;
38
-
39
- /**
40
- * Enforces strict type safety by recursively rejecting excess properties.
41
- *
42
- * TypeScript's structural subtyping allows objects to have extra properties
43
- * when passed through generics (\`<T extends Args>\`). This utility type
44
- * maps any key in A that is NOT in W to \`never\`, causing a compile-time
45
- * error when unknown keys (e.g. typos in where/select/include) are used.
46
- *
47
- * Primitives, arrays, functions, and Dates are passed through unchanged
48
- * to preserve literal types for return type narrowing via GetFindResult.
49
- */
50
- export type Exact<A, W> = W extends unknown
51
- ? A extends W
52
- ? A extends Record<string, unknown>
53
- ? { [K in keyof A]: K extends keyof W ? Exact<A[K], W[K]> : never }
54
- : A
55
- : W
56
- : never;
57
-
58
- /** Extract scalar fields from a payload (default selection) */
59
- type DefaultSelection<P extends OperationPayload> = P["scalars"];
60
-
61
- /**
62
- * Check if a key exists in the payload's objects or composites
63
- * and extract the value type.
64
- */
65
- type SelectableObject<P extends OperationPayload, K extends PropertyKey> =
66
- K extends keyof P["objects"] ? P["objects"][K] : never;
67
-
68
- // ─── GetFindResult ────────────────────────────────────────────────
69
-
70
- /**
71
- * The core type that resolves the result type based on select/include args.
72
- *
73
- * - If no args or args is any → DefaultSelection (scalars only)
74
- * - If select is provided → only selected fields (scalars + expanded relations)
75
- * - If include is provided → all scalars + included relations
76
- */
77
- export type GetFindResult<P extends OperationPayload, A> =
78
- // If Args is any (no args passed), return default
79
- IsAny<A> extends true
80
- ? DefaultSelection<P>
81
- : A extends { select: infer S extends Record<string, unknown> }
82
- ? // ── SELECT branch ──
83
- Compute<{
84
- [K in keyof S as S[K] extends false | undefined | null
85
- ? never
86
- : K]: K extends keyof P["scalars"]
87
- ? // Scalar field selected
88
- P["scalars"][K]
89
- : K extends keyof P["objects"]
90
- ? // Relation field selected
91
- P["objects"][K] extends OperationPayload[]
92
- ? // Array relation
93
- S[K] extends Record<string, unknown>
94
- ? GetFindResult<P["objects"][K][number], S[K]>[]
95
- : DefaultSelection<P["objects"][K][number]>[]
96
- : P["objects"][K] extends OperationPayload
97
- ? // Required single relation (checked before nullable to avoid false match)
98
- S[K] extends Record<string, unknown>
99
- ? GetFindResult<P["objects"][K], S[K]>
100
- : DefaultSelection<P["objects"][K]>
101
- : P["objects"][K] extends OperationPayload | null
102
- ? // Nullable single relation
103
- S[K] extends Record<string, unknown>
104
- ? GetFindResult<NonNullable<P["objects"][K]> & OperationPayload, S[K]> | null
105
- : DefaultSelection<NonNullable<P["objects"][K]> & OperationPayload> | null
106
- : never
107
- : never;
108
- }>
109
- : A extends { include: infer I extends Record<string, unknown> }
110
- ? // ── INCLUDE branch ──
111
- // All scalars + included relations
112
- Compute<
113
- DefaultSelection<P> & {
114
- [K in keyof I as I[K] extends false | undefined | null
115
- ? never
116
- : K]: K extends keyof P["objects"]
117
- ? P["objects"][K] extends OperationPayload[]
118
- ? // Array relation
119
- I[K] extends Record<string, unknown>
120
- ? GetFindResult<P["objects"][K][number], I[K]>[]
121
- : DefaultSelection<P["objects"][K][number]>[]
122
- : P["objects"][K] extends OperationPayload
123
- ? // Required single relation (checked before nullable to avoid false match)
124
- I[K] extends Record<string, unknown>
125
- ? GetFindResult<P["objects"][K], I[K]>
126
- : DefaultSelection<P["objects"][K]>
127
- : P["objects"][K] extends OperationPayload | null
128
- ? // Nullable single relation
129
- I[K] extends Record<string, unknown>
130
- ? GetFindResult<NonNullable<P["objects"][K]> & OperationPayload, I[K]> | null
131
- : DefaultSelection<NonNullable<P["objects"][K]> & OperationPayload> | null
132
- : never
133
- : never;
134
- }
135
- >
136
- : // ── No select/include ──
137
- DefaultSelection<P>;
138
-
139
- // ─── Operation Result Types ───────────────────────────────────────
140
-
141
- /**
142
- * Maps an operation name to the correct result cardinality.
143
- */
144
- export type GetResult<
145
- P extends OperationPayload,
146
- A,
147
- Op extends string,
148
- > = {
149
- findMany: GetFindResult<P, A>[];
150
- findFirst: GetFindResult<P, A> | null;
151
- findUnique: GetFindResult<P, A> | null;
152
- findUniqueOrThrow: GetFindResult<P, A>;
153
- findFirstOrThrow: GetFindResult<P, A>;
154
- create: GetFindResult<P, A>;
155
- createMany: { count: number };
156
- createManyAndReturn: GetFindResult<P, A>[];
157
- update: GetFindResult<P, A>;
158
- upsert: GetFindResult<P, A>;
159
- delete: GetFindResult<P, A>;
160
- count: number;
161
- deleteMany: { count: number };
162
- updateMany: { count: number };
163
- }[Op & keyof {
164
- findMany: unknown;
165
- findFirst: unknown;
166
- findUnique: unknown;
167
- findUniqueOrThrow: unknown;
168
- findFirstOrThrow: unknown;
169
- create: unknown;
170
- createMany: unknown;
171
- createManyAndReturn: unknown;
172
- update: unknown;
173
- upsert: unknown;
174
- delete: unknown;
175
- count: unknown;
176
- deleteMany: unknown;
177
- updateMany: unknown;
178
- }];
179
- `);
180
-
181
- return parts.join("\n");
182
- }