metal-orm 1.0.89 → 1.0.91
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/dist/index.cjs +2968 -2983
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +784 -251
- package/dist/index.d.ts +784 -251
- package/dist/index.js +2913 -2975
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/src/codegen/typescript.ts +29 -40
- package/src/core/ast/expression-builders.ts +34 -53
- package/src/core/ast/expression-nodes.ts +51 -72
- package/src/core/ast/expression-visitor.ts +219 -252
- package/src/core/ast/expression.ts +20 -21
- package/src/core/ddl/introspect/utils.ts +45 -45
- package/src/core/dialect/abstract.ts +55 -81
- package/src/core/execution/db-executor.ts +4 -5
- package/src/core/execution/executors/mysql-executor.ts +7 -9
- package/src/decorators/bootstrap.ts +29 -26
- package/src/dto/apply-filter.ts +279 -0
- package/src/dto/dto-types.ts +229 -0
- package/src/dto/filter-types.ts +193 -0
- package/src/dto/index.ts +97 -0
- package/src/dto/openapi/generators/base.ts +29 -0
- package/src/dto/openapi/generators/column.ts +34 -0
- package/src/dto/openapi/generators/dto.ts +94 -0
- package/src/dto/openapi/generators/filter.ts +74 -0
- package/src/dto/openapi/generators/nested-dto.ts +532 -0
- package/src/dto/openapi/generators/pagination.ts +111 -0
- package/src/dto/openapi/generators/relation-filter.ts +210 -0
- package/src/dto/openapi/index.ts +17 -0
- package/src/dto/openapi/type-mappings.ts +191 -0
- package/src/dto/openapi/types.ts +90 -0
- package/src/dto/openapi/utilities.ts +45 -0
- package/src/dto/pagination-utils.ts +150 -0
- package/src/dto/transform.ts +197 -0
- package/src/index.ts +5 -3
- package/src/orm/entity-context.ts +9 -9
- package/src/orm/entity.ts +74 -74
- package/src/orm/orm-session.ts +159 -159
- package/src/orm/relation-change-processor.ts +3 -3
- package/src/orm/runtime-types.ts +5 -5
- package/src/orm/unit-of-work.ts +13 -25
- package/src/query-builder/query-ast-service.ts +287 -300
- package/src/query-builder/relation-filter-utils.ts +159 -160
- package/src/query-builder/select.ts +137 -192
- package/src/schema/column-types.ts +4 -4
- package/src/schema/types.ts +5 -1
- package/src/core/ast/ast-validation.ts +0 -19
- package/src/core/ast/param-proxy.ts +0 -47
- package/src/core/ast/query-visitor.ts +0 -273
- package/src/openapi/index.ts +0 -4
- package/src/openapi/query-parameters.ts +0 -207
- package/src/openapi/schema-extractor-input.ts +0 -193
- package/src/openapi/schema-extractor-output.ts +0 -427
- package/src/openapi/schema-extractor-utils.ts +0 -110
- package/src/openapi/schema-extractor.ts +0 -120
- package/src/openapi/schema-types.ts +0 -187
- package/src/openapi/type-mappers.ts +0 -227
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTO (Data Transfer Object) type utilities for metal-orm.
|
|
3
|
+
* Derives API types from TableDef/Entity metadata.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TableDef } from '../schema/table.js';
|
|
7
|
+
import type { ColumnDef } from '../schema/column-types.js';
|
|
8
|
+
import type { ColumnToTs, InferRow } from '../schema/types.js';
|
|
9
|
+
import type { EntityConstructor } from '../orm/entity-metadata.js';
|
|
10
|
+
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
// Entity support: Extract row type from entity constructor
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a type is a TableDef.
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
type IsTableDef<T> = T extends { name: string; columns: any } ? true : false;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extracts the row type from either a TableDef or EntityConstructor.
|
|
23
|
+
*/
|
|
24
|
+
type ExtractRow<T> = IsTableDef<T> extends true
|
|
25
|
+
? InferRow<T extends TableDef<infer C> ? TableDef<C> : never>
|
|
26
|
+
: T extends EntityConstructor<infer E>
|
|
27
|
+
? E
|
|
28
|
+
: never;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Utility to flatten intersection types for better IDE display.
|
|
32
|
+
*/
|
|
33
|
+
export type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Response DTO - excludes specified columns from the entity.
|
|
37
|
+
* Use this to hide sensitive fields like passwordHash, apiKey, etc.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // With TableDef
|
|
42
|
+
* type UserResponse = Dto<typeof usersTable, 'passwordHash'>;
|
|
43
|
+
*
|
|
44
|
+
* // With Entity class
|
|
45
|
+
* type UserResponse = Dto<User, 'passwordHash'>;
|
|
46
|
+
*
|
|
47
|
+
* // Exclude multiple fields
|
|
48
|
+
* type UserPublic = Dto<User, 'passwordHash' | 'email'>;
|
|
49
|
+
*
|
|
50
|
+
* // Include all fields (no exclusions)
|
|
51
|
+
* type UserFull = Dto<User>;
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export type Dto<
|
|
55
|
+
T extends TableDef | EntityConstructor,
|
|
56
|
+
TExclude extends keyof ExtractRow<T> = never
|
|
57
|
+
> = Simplify<Omit<ExtractRow<T>, TExclude>>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Compose a DTO with relations.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* type UserWithPosts = WithRelations<UserResponse, {
|
|
65
|
+
* posts: PostResponse[]
|
|
66
|
+
* }>;
|
|
67
|
+
*
|
|
68
|
+
* type PostWithAuthor = WithRelations<PostResponse, {
|
|
69
|
+
* author: Dto<User, 'passwordHash' | 'email'>
|
|
70
|
+
* }>;
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export type WithRelations<TBase, TRelations> = Simplify<TBase & TRelations>;
|
|
74
|
+
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
// Column classification helpers for TableDef (for Create/Update DTOs)
|
|
77
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
type ColumnMap<T extends TableDef> = T['columns'];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Checks if a column has a default value.
|
|
83
|
+
*/
|
|
84
|
+
type HasDefault<TCol extends ColumnDef> =
|
|
85
|
+
TCol['default'] extends undefined ? false : true;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Checks if a column is auto-generated (autoIncrement or generated always/byDefault).
|
|
89
|
+
*/
|
|
90
|
+
type IsAutoGenerated<TCol extends ColumnDef> =
|
|
91
|
+
TCol['autoIncrement'] extends true ? true :
|
|
92
|
+
TCol['generated'] extends 'always' | 'byDefault' ? true :
|
|
93
|
+
false;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checks if a column is insertable (not auto-generated).
|
|
97
|
+
*/
|
|
98
|
+
type IsInsertable<TCol extends ColumnDef> =
|
|
99
|
+
IsAutoGenerated<TCol> extends true ? false : true;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks if a column is required for insert (notNull, no default, not auto-generated).
|
|
103
|
+
*/
|
|
104
|
+
type IsRequiredInsert<TCol extends ColumnDef> =
|
|
105
|
+
TCol['notNull'] extends true
|
|
106
|
+
? IsAutoGenerated<TCol> extends true ? false :
|
|
107
|
+
HasDefault<TCol> extends true ? false :
|
|
108
|
+
true
|
|
109
|
+
: false;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Keys that are required for insert (notNull, no default, not auto-generated).
|
|
113
|
+
*/
|
|
114
|
+
type RequiredInsertKeys<T extends TableDef> = {
|
|
115
|
+
[K in keyof ColumnMap<T>]: ColumnMap<T>[K] extends ColumnDef
|
|
116
|
+
? IsInsertable<ColumnMap<T>[K]> extends true
|
|
117
|
+
? IsRequiredInsert<ColumnMap<T>[K]> extends true ? K : never
|
|
118
|
+
: never
|
|
119
|
+
: never;
|
|
120
|
+
}[keyof ColumnMap<T>];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Keys that are optional for insert (nullable, has default, or optional).
|
|
124
|
+
*/
|
|
125
|
+
type OptionalInsertKeys<T extends TableDef> = {
|
|
126
|
+
[K in keyof ColumnMap<T>]: ColumnMap<T>[K] extends ColumnDef
|
|
127
|
+
? IsInsertable<ColumnMap<T>[K]> extends true
|
|
128
|
+
? IsRequiredInsert<ColumnMap<T>[K]> extends false ? K : never
|
|
129
|
+
: never
|
|
130
|
+
: never;
|
|
131
|
+
}[keyof ColumnMap<T>];
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create DTO - includes only insertable columns with proper optionality.
|
|
135
|
+
* Auto-generated columns (autoIncrement, generated) are excluded.
|
|
136
|
+
* Columns with defaults or nullable are optional.
|
|
137
|
+
*
|
|
138
|
+
* Works with both TableDef and EntityConstructor:
|
|
139
|
+
* - For TableDef: Uses column metadata to determine required/optional fields
|
|
140
|
+
* - For EntityConstructor: All fields are optional (simpler type inference)
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* // With TableDef - auto-excludes id (autoIncrement), createdAt (has default)
|
|
145
|
+
* type CreateUserDto = CreateDto<typeof usersTable>;
|
|
146
|
+
* // → { name: string; email: string; bio?: string }
|
|
147
|
+
*
|
|
148
|
+
* // With Entity class - simpler inference, all fields optional
|
|
149
|
+
* type CreateUserDto = CreateDto<User>;
|
|
150
|
+
*
|
|
151
|
+
* // Exclude additional fields (e.g., authorId comes from context)
|
|
152
|
+
* type CreatePostDto = CreateDto<typeof Post, 'authorId'>;
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export type CreateDto<
|
|
156
|
+
T extends TableDef | EntityConstructor,
|
|
157
|
+
TExclude extends keyof ExtractRow<T> = never
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
159
|
+
> = T extends TableDef<any>
|
|
160
|
+
? Simplify<
|
|
161
|
+
{ [K in Exclude<RequiredInsertKeys<T>, TExclude>]: ColumnToTs<ColumnMap<T>[K]> } &
|
|
162
|
+
{ [K in Exclude<OptionalInsertKeys<T>, TExclude>]?: ColumnToTs<ColumnMap<T>[K]> }
|
|
163
|
+
>
|
|
164
|
+
: Simplify<{
|
|
165
|
+
[K in Exclude<keyof ExtractRow<T>, TExclude>]?: ExtractRow<T>[K];
|
|
166
|
+
}>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Update DTO - all columns are optional (partial update).
|
|
170
|
+
* Excludes specified columns (e.g., id, createdAt).
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* // With TableDef
|
|
175
|
+
* type UpdateUserDto = UpdateDto<typeof User>;
|
|
176
|
+
* // → { name?: string; email?: string; bio?: string; ... }
|
|
177
|
+
*
|
|
178
|
+
* // With Entity class
|
|
179
|
+
* type UpdateUserDto = UpdateDto<User>;
|
|
180
|
+
*
|
|
181
|
+
* // Exclude fields that shouldn't be updated
|
|
182
|
+
* type UpdateUserDto = UpdateDto<typeof User, 'id' | 'createdAt'>;
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export type UpdateDto<
|
|
186
|
+
T extends TableDef | EntityConstructor,
|
|
187
|
+
TExclude extends keyof ExtractRow<T> = never
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
|
+
> = T extends TableDef<any>
|
|
190
|
+
? Simplify<{
|
|
191
|
+
[K in Exclude<keyof ColumnMap<T>, TExclude>]?: ColumnToTs<ColumnMap<T>[K]>;
|
|
192
|
+
}>
|
|
193
|
+
: Simplify<{
|
|
194
|
+
[K in Exclude<keyof ExtractRow<T>, TExclude>]?: ExtractRow<T>[K];
|
|
195
|
+
}>;
|
|
196
|
+
|
|
197
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
198
|
+
// Enhanced Pagination DTO types
|
|
199
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Enhanced paginated response with computed navigation metadata.
|
|
203
|
+
* Extends the basic PaginatedResult with additional convenience fields.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* type UsersPagedResponse = PagedResponse<UserResponse>;
|
|
208
|
+
*
|
|
209
|
+
* // Response:
|
|
210
|
+
* // {
|
|
211
|
+
* // items: UserResponse[];
|
|
212
|
+
* // totalItems: number;
|
|
213
|
+
* // page: number;
|
|
214
|
+
* // pageSize: number;
|
|
215
|
+
* // totalPages: number;
|
|
216
|
+
* // hasNextPage: boolean;
|
|
217
|
+
* // hasPrevPage: boolean;
|
|
218
|
+
* // }
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export type PagedResponse<T> = {
|
|
222
|
+
items: T[];
|
|
223
|
+
totalItems: number;
|
|
224
|
+
page: number;
|
|
225
|
+
pageSize: number;
|
|
226
|
+
totalPages: number;
|
|
227
|
+
hasNextPage: boolean;
|
|
228
|
+
hasPrevPage: boolean;
|
|
229
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter types for building type-safe query filters from JSON input.
|
|
3
|
+
* Designed for REST API query parameters.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TableDef } from '../schema/table.js';
|
|
7
|
+
import type { ColumnDef } from '../schema/column-types.js';
|
|
8
|
+
import type { EntityConstructor } from '../orm/entity-metadata.js';
|
|
9
|
+
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// Entity support: Extract columns from entity constructor
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if a type is a TableDef.
|
|
16
|
+
*/
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
type IsTableDef<T> = T extends { name: string; columns: any } ? true : false;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maps TypeScript types to SQL column types for EntityConstructor columns.
|
|
22
|
+
*/
|
|
23
|
+
type TsTypeToColumnType<T> =
|
|
24
|
+
T extends string ? 'VARCHAR' :
|
|
25
|
+
T extends number ? 'INT' :
|
|
26
|
+
T extends boolean ? 'BOOLEAN' :
|
|
27
|
+
T extends Date ? 'TIMESTAMP' :
|
|
28
|
+
'VARCHAR';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the column map from either a TableDef or EntityConstructor.
|
|
32
|
+
* For EntityConstructor, infers column type from TypeScript property type.
|
|
33
|
+
*/
|
|
34
|
+
type ExtractColumns<T> = IsTableDef<T> extends true
|
|
35
|
+
? T extends TableDef<infer C> ? C : never
|
|
36
|
+
: T extends EntityConstructor<infer E>
|
|
37
|
+
? {
|
|
38
|
+
[K in keyof E]: ColumnDef<TsTypeToColumnType<E[K]>, E[K]>;
|
|
39
|
+
}
|
|
40
|
+
: never;
|
|
41
|
+
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
// Scalar filter types per data type
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Filter operators for string columns.
|
|
48
|
+
*/
|
|
49
|
+
export interface StringFilter {
|
|
50
|
+
equals?: string;
|
|
51
|
+
not?: string;
|
|
52
|
+
in?: string[];
|
|
53
|
+
notIn?: string[];
|
|
54
|
+
contains?: string;
|
|
55
|
+
startsWith?: string;
|
|
56
|
+
endsWith?: string;
|
|
57
|
+
/** Case-insensitive matching (for contains, startsWith, endsWith) */
|
|
58
|
+
mode?: 'default' | 'insensitive';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Filter operators for numeric columns (number, bigint).
|
|
63
|
+
*/
|
|
64
|
+
export interface NumberFilter {
|
|
65
|
+
equals?: number;
|
|
66
|
+
not?: number;
|
|
67
|
+
in?: number[];
|
|
68
|
+
notIn?: number[];
|
|
69
|
+
lt?: number;
|
|
70
|
+
lte?: number;
|
|
71
|
+
gt?: number;
|
|
72
|
+
gte?: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Filter operators for boolean columns.
|
|
77
|
+
*/
|
|
78
|
+
export interface BooleanFilter {
|
|
79
|
+
equals?: boolean;
|
|
80
|
+
not?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Filter operators for date/datetime columns.
|
|
85
|
+
* Accepts ISO date strings.
|
|
86
|
+
*/
|
|
87
|
+
export interface DateFilter {
|
|
88
|
+
equals?: string;
|
|
89
|
+
not?: string;
|
|
90
|
+
in?: string[];
|
|
91
|
+
notIn?: string[];
|
|
92
|
+
lt?: string;
|
|
93
|
+
lte?: string;
|
|
94
|
+
gt?: string;
|
|
95
|
+
gte?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
// Type mapping: ColumnDef → appropriate filter type
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
type NormalizedType<T extends ColumnDef> = Lowercase<T['type'] & string>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Maps a column definition to its appropriate filter type.
|
|
106
|
+
*/
|
|
107
|
+
export type FieldFilter<TCol extends ColumnDef> =
|
|
108
|
+
NormalizedType<TCol> extends 'int' | 'integer' | 'bigint' | 'decimal' | 'float' | 'double'
|
|
109
|
+
? NumberFilter
|
|
110
|
+
: NormalizedType<TCol> extends 'boolean'
|
|
111
|
+
? BooleanFilter
|
|
112
|
+
: NormalizedType<TCol> extends 'date' | 'datetime' | 'timestamp' | 'timestamptz'
|
|
113
|
+
? DateFilter
|
|
114
|
+
: StringFilter; // default to string for varchar, text, uuid, etc.
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
// SimpleWhereInput: field-only filters (no AND/OR/NOT logic)
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Full where input with all columns filterable.
|
|
122
|
+
* All conditions are implicitly AND-ed.
|
|
123
|
+
* Works with both TableDef and EntityConstructor.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* // With TableDef
|
|
128
|
+
* type UserFilter = WhereInput<typeof usersTable>;
|
|
129
|
+
*
|
|
130
|
+
* // With Entity class
|
|
131
|
+
* type UserFilter = WhereInput<User>;
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export type WhereInput<T extends TableDef | EntityConstructor> = {
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
136
|
+
[K in keyof ExtractColumns<T>]?: ExtractColumns<T>[K] extends ColumnDef<any, any>
|
|
137
|
+
? FieldFilter<ExtractColumns<T>[K]>
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
+
: FieldFilter<ColumnDef<any, any>>;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Restricted where input - only specified columns are filterable.
|
|
144
|
+
* Use this to limit which fields can be filtered via API.
|
|
145
|
+
*
|
|
146
|
+
* Works with both TableDef and EntityConstructor.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* // With TableDef - only allow filtering by name and email
|
|
151
|
+
* type UserFilter = SimpleWhereInput<typeof usersTable, 'name' | 'email'>;
|
|
152
|
+
*
|
|
153
|
+
* // With Entity class
|
|
154
|
+
* type UserFilter = SimpleWhereInput<User, 'name' | 'email'>;
|
|
155
|
+
*
|
|
156
|
+
* // Request: { "name": { "contains": "john" }, "email": { "endsWith": "@gmail.com" } }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export type SimpleWhereInput<
|
|
160
|
+
T extends TableDef | EntityConstructor,
|
|
161
|
+
K extends keyof ExtractColumns<T>
|
|
162
|
+
> = {
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
[P in K]?: ExtractColumns<T>[P] extends ColumnDef<any, any>
|
|
165
|
+
? FieldFilter<ExtractColumns<T>[P]>
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
: FieldFilter<ColumnDef<any, any>>;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
171
|
+
// Filter operator value types (for runtime processing)
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* All possible filter operators.
|
|
176
|
+
*/
|
|
177
|
+
export type FilterOperator =
|
|
178
|
+
| 'equals'
|
|
179
|
+
| 'not'
|
|
180
|
+
| 'in'
|
|
181
|
+
| 'notIn'
|
|
182
|
+
| 'lt'
|
|
183
|
+
| 'lte'
|
|
184
|
+
| 'gt'
|
|
185
|
+
| 'gte'
|
|
186
|
+
| 'contains'
|
|
187
|
+
| 'startsWith'
|
|
188
|
+
| 'endsWith';
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generic filter value that covers all operator types.
|
|
192
|
+
*/
|
|
193
|
+
export type FilterValue = StringFilter | NumberFilter | BooleanFilter | DateFilter;
|
package/src/dto/index.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTO (Data Transfer Object) module for metal-orm.
|
|
3
|
+
*
|
|
4
|
+
* Provides type utilities and runtime helpers for building REST APIs
|
|
5
|
+
* with automatic DTO generation from entity/table definitions.
|
|
6
|
+
*
|
|
7
|
+
* Supports both TableDef and Entity classes as type sources.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Dto, WithRelations, CreateDto, UpdateDto, SimpleWhereInput, applyFilter, PagedResponse, toPagedResponse } from 'metal-orm/dto';
|
|
12
|
+
*
|
|
13
|
+
* // Using Entity classes
|
|
14
|
+
* type UserResponse = Dto<User, 'passwordHash'>;
|
|
15
|
+
* type PostResponse = Dto<Post, 'authorId'>;
|
|
16
|
+
* type UserWithPosts = WithRelations<UserResponse, { posts: PostResponse[] }>;
|
|
17
|
+
* type CreateUserDto = CreateDto<User>;
|
|
18
|
+
* type UpdateUserDto = UpdateDto<User>;
|
|
19
|
+
* type UserFilter = SimpleWhereInput<User, 'name' | 'email'>;
|
|
20
|
+
*
|
|
21
|
+
* // Enhanced pagination
|
|
22
|
+
* type UsersPagedResponse = PagedResponse<UserResponse>;
|
|
23
|
+
*
|
|
24
|
+
* // Using TableDef directly
|
|
25
|
+
* type UserResponse = Dto<typeof UserTable, 'passwordHash'>;
|
|
26
|
+
* type PostResponse = Dto<typeof PostTable, 'authorId'>;
|
|
27
|
+
* type UserWithPosts = WithRelations<UserResponse, { posts: PostResponse[] }>;
|
|
28
|
+
* type CreateUserDto = CreateDto<typeof UserTable>;
|
|
29
|
+
* type UpdateUserDto = UpdateDto<typeof UserTable>;
|
|
30
|
+
* type UserFilter = SimpleWhereInput<typeof UserTable, 'name' | 'email'>;
|
|
31
|
+
*
|
|
32
|
+
* // Apply filter in controller
|
|
33
|
+
* let query = selectFromEntity(User);
|
|
34
|
+
* query = applyFilter(query, User, where);
|
|
35
|
+
*
|
|
36
|
+
* // Apply enhanced pagination
|
|
37
|
+
* const basic = await qb.executePaged(session, { page: 2, pageSize: 20 });
|
|
38
|
+
* const response = toPagedResponse(basic);
|
|
39
|
+
* // → { items: [...], totalItems: 150, page: 2, pageSize: 20,
|
|
40
|
+
* // totalPages: 8, hasNextPage: true, hasPrevPage: true }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @packageDocumentation
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
// DTO type utilities
|
|
47
|
+
export type {
|
|
48
|
+
Simplify,
|
|
49
|
+
Dto,
|
|
50
|
+
WithRelations,
|
|
51
|
+
CreateDto,
|
|
52
|
+
UpdateDto,
|
|
53
|
+
PagedResponse
|
|
54
|
+
} from './dto-types.js';
|
|
55
|
+
|
|
56
|
+
// Filter types
|
|
57
|
+
export type {
|
|
58
|
+
StringFilter,
|
|
59
|
+
NumberFilter,
|
|
60
|
+
BooleanFilter,
|
|
61
|
+
DateFilter,
|
|
62
|
+
FieldFilter,
|
|
63
|
+
WhereInput,
|
|
64
|
+
SimpleWhereInput,
|
|
65
|
+
FilterOperator,
|
|
66
|
+
FilterValue
|
|
67
|
+
} from './filter-types.js';
|
|
68
|
+
|
|
69
|
+
// Runtime filter application
|
|
70
|
+
export {
|
|
71
|
+
applyFilter,
|
|
72
|
+
buildFilterExpression
|
|
73
|
+
} from './apply-filter.js';
|
|
74
|
+
|
|
75
|
+
// Transformation utilities
|
|
76
|
+
export {
|
|
77
|
+
toResponse,
|
|
78
|
+
toResponseBuilder,
|
|
79
|
+
withDefaults,
|
|
80
|
+
withDefaultsBuilder,
|
|
81
|
+
exclude,
|
|
82
|
+
pick,
|
|
83
|
+
mapFields
|
|
84
|
+
} from './transform.js';
|
|
85
|
+
|
|
86
|
+
// Pagination utilities
|
|
87
|
+
export {
|
|
88
|
+
toPagedResponse,
|
|
89
|
+
toPagedResponseBuilder,
|
|
90
|
+
calculateTotalPages,
|
|
91
|
+
hasNextPage as hasNextPageMeta,
|
|
92
|
+
hasPrevPage as hasPrevPageMeta,
|
|
93
|
+
computePaginationMetadata
|
|
94
|
+
} from './pagination-utils.js';
|
|
95
|
+
|
|
96
|
+
// OpenAPI 3.1 Schema generation
|
|
97
|
+
export * from './openapi/index.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TableDef } from '../../../schema/table.js';
|
|
2
|
+
import type { ColumnDef } from '../../../schema/column-types.js';
|
|
3
|
+
import type { EntityConstructor } from '../../../orm/entity-metadata.js';
|
|
4
|
+
import { getEntityMetadata } from '../../../orm/entity-metadata.js';
|
|
5
|
+
|
|
6
|
+
export function isTableDef(target: TableDef | EntityConstructor): target is TableDef {
|
|
7
|
+
return 'columns' in target && 'name' in target;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getColumnMap(target: TableDef | EntityConstructor): Record<string, ColumnDef> {
|
|
11
|
+
if (isTableDef(target)) {
|
|
12
|
+
return target.columns;
|
|
13
|
+
}
|
|
14
|
+
const meta = getEntityMetadata(target);
|
|
15
|
+
if (meta && meta.columns) {
|
|
16
|
+
const columns: Record<string, ColumnDef> = {};
|
|
17
|
+
for (const [key, def] of Object.entries(meta.columns)) {
|
|
18
|
+
columns[key] = {
|
|
19
|
+
...def as object,
|
|
20
|
+
name: key,
|
|
21
|
+
table: meta.tableName
|
|
22
|
+
} as ColumnDef;
|
|
23
|
+
}
|
|
24
|
+
return columns;
|
|
25
|
+
}
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TargetType<T extends TableDef | EntityConstructor> = T;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ColumnDef } from '../../../schema/column-types.js';
|
|
2
|
+
import type { OpenApiSchema } from '../types.js';
|
|
3
|
+
import { columnTypeToOpenApiType, columnTypeToOpenApiFormat } from '../type-mappings.js';
|
|
4
|
+
|
|
5
|
+
export function columnToOpenApiSchema(col: ColumnDef): OpenApiSchema {
|
|
6
|
+
const schema: OpenApiSchema = {
|
|
7
|
+
type: columnTypeToOpenApiType(col),
|
|
8
|
+
format: columnTypeToOpenApiFormat(col),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
if (!col.notNull) {
|
|
12
|
+
schema.nullable = true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (col.comment) {
|
|
16
|
+
schema.description = col.comment;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (col.type.toUpperCase() === 'ENUM' && col.args && Array.isArray(col.args) && col.args.every(v => typeof v === 'string')) {
|
|
20
|
+
schema.enum = col.args as string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const args = col.args;
|
|
24
|
+
if (args && args.length > 0) {
|
|
25
|
+
if (col.type.toUpperCase() === 'VARCHAR' || col.type.toUpperCase() === 'CHAR') {
|
|
26
|
+
const length = args[0] as number;
|
|
27
|
+
if (length) {
|
|
28
|
+
schema.example = 'a'.repeat(length);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return schema;
|
|
34
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { TableDef } from '../../../schema/table.js';
|
|
2
|
+
import type { EntityConstructor } from '../../../orm/entity-metadata.js';
|
|
3
|
+
import type { OpenApiSchema } from '../types.js';
|
|
4
|
+
import { columnToOpenApiSchema } from './column.js';
|
|
5
|
+
import { getColumnMap } from './base.js';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
export function dtoToOpenApiSchema<T extends TableDef | EntityConstructor, TExclude extends keyof any>(
|
|
9
|
+
target: T,
|
|
10
|
+
exclude?: TExclude[]
|
|
11
|
+
): OpenApiSchema {
|
|
12
|
+
const columns = getColumnMap(target);
|
|
13
|
+
const properties: Record<string, OpenApiSchema> = {};
|
|
14
|
+
const required: string[] = [];
|
|
15
|
+
|
|
16
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
17
|
+
if (exclude?.includes(key as TExclude)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
properties[key] = columnToOpenApiSchema(col);
|
|
22
|
+
|
|
23
|
+
if (col.notNull || col.primary) {
|
|
24
|
+
required.push(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties,
|
|
31
|
+
...(required.length > 0 && { required }),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
export function createDtoToOpenApiSchema<T extends TableDef | EntityConstructor, TExclude extends keyof any>(
|
|
37
|
+
target: T,
|
|
38
|
+
exclude?: TExclude[]
|
|
39
|
+
): OpenApiSchema {
|
|
40
|
+
const columns = getColumnMap(target);
|
|
41
|
+
const properties: Record<string, OpenApiSchema> = {};
|
|
42
|
+
const required: string[] = [];
|
|
43
|
+
|
|
44
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
45
|
+
if (exclude?.includes(key as TExclude)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (col.autoIncrement || col.generated) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
properties[key] = columnToOpenApiSchema(col);
|
|
54
|
+
|
|
55
|
+
if (col.notNull && !col.default) {
|
|
56
|
+
required.push(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties,
|
|
63
|
+
...(required.length > 0 && { required }),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
export function updateDtoToOpenApiSchema<T extends TableDef | EntityConstructor, TExclude extends keyof any>(
|
|
69
|
+
target: T,
|
|
70
|
+
exclude?: TExclude[]
|
|
71
|
+
): OpenApiSchema {
|
|
72
|
+
const columns = getColumnMap(target);
|
|
73
|
+
const properties: Record<string, OpenApiSchema> = {};
|
|
74
|
+
|
|
75
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
76
|
+
if (exclude?.includes(key as TExclude)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (col.autoIncrement || col.generated) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
properties[key] = {
|
|
85
|
+
...columnToOpenApiSchema(col),
|
|
86
|
+
...(!col.notNull ? { nullable: true } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties,
|
|
93
|
+
};
|
|
94
|
+
}
|