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.
Files changed (57) hide show
  1. package/dist/index.cjs +2968 -2983
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +784 -251
  4. package/dist/index.d.ts +784 -251
  5. package/dist/index.js +2913 -2975
  6. package/dist/index.js.map +1 -1
  7. package/package.json +6 -3
  8. package/src/codegen/typescript.ts +29 -40
  9. package/src/core/ast/expression-builders.ts +34 -53
  10. package/src/core/ast/expression-nodes.ts +51 -72
  11. package/src/core/ast/expression-visitor.ts +219 -252
  12. package/src/core/ast/expression.ts +20 -21
  13. package/src/core/ddl/introspect/utils.ts +45 -45
  14. package/src/core/dialect/abstract.ts +55 -81
  15. package/src/core/execution/db-executor.ts +4 -5
  16. package/src/core/execution/executors/mysql-executor.ts +7 -9
  17. package/src/decorators/bootstrap.ts +29 -26
  18. package/src/dto/apply-filter.ts +279 -0
  19. package/src/dto/dto-types.ts +229 -0
  20. package/src/dto/filter-types.ts +193 -0
  21. package/src/dto/index.ts +97 -0
  22. package/src/dto/openapi/generators/base.ts +29 -0
  23. package/src/dto/openapi/generators/column.ts +34 -0
  24. package/src/dto/openapi/generators/dto.ts +94 -0
  25. package/src/dto/openapi/generators/filter.ts +74 -0
  26. package/src/dto/openapi/generators/nested-dto.ts +532 -0
  27. package/src/dto/openapi/generators/pagination.ts +111 -0
  28. package/src/dto/openapi/generators/relation-filter.ts +210 -0
  29. package/src/dto/openapi/index.ts +17 -0
  30. package/src/dto/openapi/type-mappings.ts +191 -0
  31. package/src/dto/openapi/types.ts +90 -0
  32. package/src/dto/openapi/utilities.ts +45 -0
  33. package/src/dto/pagination-utils.ts +150 -0
  34. package/src/dto/transform.ts +197 -0
  35. package/src/index.ts +5 -3
  36. package/src/orm/entity-context.ts +9 -9
  37. package/src/orm/entity.ts +74 -74
  38. package/src/orm/orm-session.ts +159 -159
  39. package/src/orm/relation-change-processor.ts +3 -3
  40. package/src/orm/runtime-types.ts +5 -5
  41. package/src/orm/unit-of-work.ts +13 -25
  42. package/src/query-builder/query-ast-service.ts +287 -300
  43. package/src/query-builder/relation-filter-utils.ts +159 -160
  44. package/src/query-builder/select.ts +137 -192
  45. package/src/schema/column-types.ts +4 -4
  46. package/src/schema/types.ts +5 -1
  47. package/src/core/ast/ast-validation.ts +0 -19
  48. package/src/core/ast/param-proxy.ts +0 -47
  49. package/src/core/ast/query-visitor.ts +0 -273
  50. package/src/openapi/index.ts +0 -4
  51. package/src/openapi/query-parameters.ts +0 -207
  52. package/src/openapi/schema-extractor-input.ts +0 -193
  53. package/src/openapi/schema-extractor-output.ts +0 -427
  54. package/src/openapi/schema-extractor-utils.ts +0 -110
  55. package/src/openapi/schema-extractor.ts +0 -120
  56. package/src/openapi/schema-types.ts +0 -187
  57. package/src/openapi/type-mappers.ts +0 -227
@@ -0,0 +1,197 @@
1
+ /**
2
+ * DTO transformation utilities for working with DTO instances.
3
+ *
4
+ * These helpers make it easier to convert between DTO types, apply defaults,
5
+ * and handle auto-generated fields without manual object construction.
6
+ */
7
+
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ // Transform utilities
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+
12
+ /**
13
+ * Transforms a CreateDto or UpdateDto into a ResponseDto by merging with auto-generated fields.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const newUser: UserResponse = toResponse(body, {
18
+ * id: newId,
19
+ * createdAt: new Date().toISOString()
20
+ * });
21
+ *
22
+ * // Or use the curried form for reuse
23
+ * const createUserResponse = toResponseBuilder<UserCreateDto, UserResponse>({
24
+ * id: () => nextId++,
25
+ * createdAt: () => new Date().toISOString()
26
+ * });
27
+ * const user = createUserResponse(body);
28
+ * ```
29
+ */
30
+ export function toResponse<TInput, TOutput>(
31
+ input: TInput,
32
+ autoFields: Partial<TOutput>
33
+ ): TOutput {
34
+ return {
35
+ ...input,
36
+ ...autoFields
37
+ } as unknown as TOutput;
38
+ }
39
+
40
+ /**
41
+ * Creates a toResponse function with pre-configured auto-fields.
42
+ * Useful for reusing same transformation logic across multiple endpoints.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * const userResponseBuilder = toResponseBuilder<CreateUserDto, UserResponse>({
47
+ * id: () => generateId(),
48
+ * createdAt: () => new Date().toISOString(),
49
+ * active: true
50
+ * });
51
+ *
52
+ * app.post('/users', (req, res) => {
53
+ * const user = userResponseBuilder(req.body);
54
+ * res.status(201).json(user);
55
+ * });
56
+ * ```
57
+ */
58
+ export function toResponseBuilder<TInput, TOutput>(
59
+ autoFields: Partial<TOutput> | (() => Partial<TOutput>)
60
+ ): (input: TInput) => TOutput {
61
+ return (input: TInput) => {
62
+ const fields: Partial<TOutput> =
63
+ typeof autoFields === 'function' ? (autoFields as () => Partial<TOutput>)() : autoFields;
64
+ return {
65
+ ...input,
66
+ ...fields
67
+ } as unknown as TOutput;
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Merges default values into a DTO. Returns a complete object with all defaults applied.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const user = withDefaults(body, {
77
+ * active: true,
78
+ * createdAt: new Date().toISOString()
79
+ * });
80
+ * ```
81
+ */
82
+ export function withDefaults<T>(dto: Partial<T>, defaults: T): T {
83
+ return {
84
+ ...defaults,
85
+ ...dto
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Creates a withDefaults function with pre-configured defaults.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const userWithDefaults = withDefaultsBuilder<CreateUserDto>({
95
+ * active: true
96
+ * });
97
+ *
98
+ * app.post('/users', (req, res) => {
99
+ * const userInput = userWithDefaults(req.body);
100
+ * // ...
101
+ * });
102
+ * ```
103
+ */
104
+ export function withDefaultsBuilder<T>(
105
+ defaults: T | (() => T)
106
+ ): (dto: Partial<T>) => T {
107
+ return (dto: Partial<T>) => {
108
+ const resolvedDefaults: T =
109
+ typeof defaults === 'function' ? (defaults as () => T)() : defaults;
110
+ return {
111
+ ...resolvedDefaults,
112
+ ...dto
113
+ };
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Excludes specified fields from an object. Useful for removing sensitive fields
119
+ * from database results before sending to the client.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const userFromDb = await db.select().from(users).where(...).first();
124
+ * const userResponse = exclude(userFromDb, 'passwordHash', 'apiKey');
125
+ * ```
126
+ */
127
+ export function exclude<T extends object, K extends keyof T>(
128
+ obj: T,
129
+ ...keys: K[]
130
+ ): Omit<T, K> {
131
+ const result = { ...obj };
132
+ for (const key of keys) {
133
+ delete result[key];
134
+ }
135
+ return result as Omit<T, K>;
136
+ }
137
+
138
+ /**
139
+ * Picks only specified fields from an object. Useful for creating DTOs from
140
+ * larger database entities.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * const userFromDb = await db.select().from(users).where(...).first();
145
+ * const userResponse = pick(userFromDb, 'id', 'name', 'email');
146
+ * ```
147
+ */
148
+ export function pick<T extends object, K extends keyof T>(
149
+ obj: T,
150
+ ...keys: K[]
151
+ ): Pick<T, K> {
152
+ const result = {} as Pick<T, K>;
153
+ for (const key of keys) {
154
+ result[key] = obj[key];
155
+ }
156
+ return result;
157
+ }
158
+
159
+ /**
160
+ * Maps field names from one DTO to another. Useful when your DTO has different
161
+ * field names than your entity (e.g., camelCase vs snake_case).
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * type ApiUser = { firstName: string; lastName: string };
166
+ * type DbUser = { first_name: string; last_name: string };
167
+ *
168
+ * const dbUser = mapFields(apiUser, {
169
+ * firstName: 'first_name',
170
+ * lastName: 'last_name'
171
+ * });
172
+ * ```
173
+ */
174
+ type MappedFields<T, M extends Partial<Record<keyof T, string>>> = {
175
+ [K in keyof M as M[K] extends string ? M[K] : never]: K extends keyof T ? T[K] : never;
176
+ };
177
+
178
+ export function mapFields<T extends object, M extends Partial<Record<keyof T, string>>>(
179
+ obj: T,
180
+ fieldMap: M
181
+ ): Omit<T, keyof M> & MappedFields<T, M> {
182
+ const result: Record<string, unknown> = {};
183
+ const keys = Object.keys(fieldMap) as (keyof T)[];
184
+ for (const sourceKey of keys) {
185
+ const targetKey = fieldMap[sourceKey];
186
+ if (targetKey) {
187
+ result[targetKey] = obj[sourceKey];
188
+ }
189
+ }
190
+ // Copy any unmapped fields
191
+ for (const [key, value] of Object.entries(obj)) {
192
+ if (!(key in fieldMap)) {
193
+ result[key] = value;
194
+ }
195
+ }
196
+ return result as Omit<T, keyof M> & MappedFields<T, M>;
197
+ }
package/src/index.ts CHANGED
@@ -50,9 +50,8 @@ export * from './orm/runtime-types.js';
50
50
  export * from './orm/query-logger.js';
51
51
  export * from './orm/interceptor-pipeline.js';
52
52
  export * from './orm/jsonify.js';
53
- export * from './orm/save-graph-types.js';
54
- export * from './decorators/index.js';
55
- export * from './openapi/index.js';
53
+ export * from './orm/save-graph-types.js';
54
+ export * from './decorators/index.js';
56
55
 
57
56
  // NEW: execution abstraction + helpers
58
57
  export * from './core/execution/db-executor.js';
@@ -65,3 +64,6 @@ export * from './core/execution/executors/mssql-executor.js';
65
64
 
66
65
  // NEW: first-class pooling integration
67
66
  export * from './orm/pooled-executor-factory.js';
67
+
68
+ // DTO module for REST API integration
69
+ export * from './dto/index.js';
@@ -2,9 +2,9 @@ import { Dialect } from '../core/dialect/abstract.js';
2
2
  import type { DbExecutor } from '../core/execution/db-executor.js';
3
3
  import { TableDef } from '../schema/table.js';
4
4
  import { RelationDef } from '../schema/relation.js';
5
- import { RelationChange, RelationKey, TrackedEntity } from './runtime-types.js';
6
-
7
- export type PrimaryKey = string | number;
5
+ import { RelationChange, RelationKey, TrackedEntity } from './runtime-types.js';
6
+
7
+ export type PrimaryKey = string | number;
8
8
 
9
9
  /**
10
10
  * Interface for entity context providing entity tracking and management.
@@ -21,7 +21,7 @@ export interface EntityContext {
21
21
  * @param pk - The primary key value
22
22
  * @returns The entity or undefined
23
23
  */
24
- getEntity(table: TableDef, pk: PrimaryKey): object | undefined;
24
+ getEntity(table: TableDef, pk: PrimaryKey): object | undefined;
25
25
 
26
26
  /**
27
27
  * Sets an entity in the context.
@@ -29,7 +29,7 @@ export interface EntityContext {
29
29
  * @param pk - The primary key value
30
30
  * @param entity - The entity to set
31
31
  */
32
- setEntity(table: TableDef, pk: PrimaryKey, entity: object): void;
32
+ setEntity(table: TableDef, pk: PrimaryKey, entity: object): void;
33
33
 
34
34
  /**
35
35
  * Tracks a new entity.
@@ -37,7 +37,7 @@ export interface EntityContext {
37
37
  * @param entity - The new entity
38
38
  * @param pk - Optional primary key
39
39
  */
40
- trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void;
40
+ trackNew(table: TableDef, entity: object, pk?: PrimaryKey): void;
41
41
 
42
42
  /**
43
43
  * Tracks a managed entity.
@@ -45,19 +45,19 @@ export interface EntityContext {
45
45
  * @param pk - The primary key
46
46
  * @param entity - The managed entity
47
47
  */
48
- trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void;
48
+ trackManaged(table: TableDef, pk: PrimaryKey, entity: object): void;
49
49
 
50
50
  /**
51
51
  * Marks an entity as dirty.
52
52
  * @param entity - The entity to mark
53
53
  */
54
- markDirty(entity: object): void;
54
+ markDirty(entity: object): void;
55
55
 
56
56
  /**
57
57
  * Marks an entity as removed.
58
58
  * @param entity - The entity to mark
59
59
  */
60
- markRemoved(entity: object): void;
60
+ markRemoved(entity: object): void;
61
61
 
62
62
  /**
63
63
  * Gets all tracked entities for a table.
package/src/orm/entity.ts CHANGED
@@ -7,20 +7,20 @@ import { RelationIncludeOptions } from '../query-builder/relation-types.js';
7
7
  import { populateHydrationCache } from './entity-hydration.js';
8
8
  import { getRelationWrapper, RelationEntityFactory } from './entity-relations.js';
9
9
 
10
- export { relationLoaderCache } from './entity-relation-cache.js';
11
-
12
- const isRelationWrapperLoaded = (value: unknown): boolean => {
13
- if (!value || typeof value !== 'object') return false;
14
- return Boolean((value as { loaded?: boolean }).loaded);
15
- };
16
-
17
- type JsonSource<TTable extends TableDef> = EntityInstance<TTable> & Record<string, unknown>;
18
-
19
- /**
20
- * Creates an entity proxy with lazy loading capabilities.
21
- * @template TTable - The table type
22
- * @template TLazy - The lazy relation keys
23
- * @param ctx - The entity context
10
+ export { relationLoaderCache } from './entity-relation-cache.js';
11
+
12
+ const isRelationWrapperLoaded = (value: unknown): boolean => {
13
+ if (!value || typeof value !== 'object') return false;
14
+ return Boolean((value as { loaded?: boolean }).loaded);
15
+ };
16
+
17
+ type JsonSource<TTable extends TableDef> = EntityInstance<TTable> & Record<string, unknown>;
18
+
19
+ /**
20
+ * Creates an entity proxy with lazy loading capabilities.
21
+ * @template TTable - The table type
22
+ * @template TLazy - The lazy relation keys
23
+ * @param ctx - The entity context
24
24
  * @param table - The table definition
25
25
  * @param row - The database row
26
26
  * @param lazyRelations - Optional lazy relations
@@ -35,39 +35,39 @@ export const createEntityProxy = <
35
35
  row: Record<string, unknown>,
36
36
  lazyRelations: TLazy[] = [] as TLazy[],
37
37
  lazyRelationOptions: Map<string, RelationIncludeOptions> = new Map()
38
- ): EntityInstance<TTable> => {
39
- const target: Record<string, unknown> = { ...row };
40
- const meta: EntityMeta<TTable> = {
41
- ctx,
42
- table,
38
+ ): EntityInstance<TTable> => {
39
+ const target: Record<string, unknown> = { ...row };
40
+ const meta: EntityMeta<TTable> = {
41
+ ctx,
42
+ table,
43
43
  lazyRelations: [...lazyRelations],
44
44
  lazyRelationOptions: new Map(lazyRelationOptions),
45
45
  relationCache: new Map(),
46
- relationHydration: new Map(),
47
- relationWrappers: new Map()
48
- };
49
- const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
50
- createEntityFromRow(meta.ctx, relationTable, relationRow);
51
-
52
- const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
53
- const json: Record<string, unknown> = {};
54
- for (const key of Object.keys(target)) {
55
- json[key] = self[key];
56
- }
57
- for (const [relationName, wrapper] of meta.relationWrappers) {
58
- if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
59
- if (isRelationWrapperLoaded(wrapper)) {
60
- json[relationName] = wrapper;
61
- }
62
- }
63
- return json;
64
- };
65
-
66
- Object.defineProperty(target, ENTITY_META, {
67
- value: meta,
68
- enumerable: false,
69
- writable: false
70
- });
46
+ relationHydration: new Map(),
47
+ relationWrappers: new Map()
48
+ };
49
+ const createRelationEntity: RelationEntityFactory = (relationTable, relationRow) =>
50
+ createEntityFromRow(meta.ctx, relationTable, relationRow);
51
+
52
+ const buildJson = (self: JsonSource<TTable>): Record<string, unknown> => {
53
+ const json: Record<string, unknown> = {};
54
+ for (const key of Object.keys(target)) {
55
+ json[key] = self[key];
56
+ }
57
+ for (const [relationName, wrapper] of meta.relationWrappers) {
58
+ if (Object.prototype.hasOwnProperty.call(json, relationName)) continue;
59
+ if (isRelationWrapperLoaded(wrapper)) {
60
+ json[relationName] = wrapper;
61
+ }
62
+ }
63
+ return json;
64
+ };
65
+
66
+ Object.defineProperty(target, ENTITY_META, {
67
+ value: meta,
68
+ enumerable: false,
69
+ writable: false
70
+ });
71
71
 
72
72
  const handler: ProxyHandler<object> = {
73
73
  get(targetObj, prop, receiver) {
@@ -75,28 +75,28 @@ export const createEntityProxy = <
75
75
  return meta;
76
76
  }
77
77
 
78
- if (prop === '$load') {
79
- return async (relationName: RelationKey<TTable>) => {
80
- const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
81
- if (wrapper && typeof wrapper.load === 'function') {
82
- return wrapper.load();
83
- }
84
- return undefined;
85
- };
86
- }
87
-
88
- if (prop === 'toJSON') {
89
- if (prop in targetObj) {
90
- return Reflect.get(targetObj, prop, receiver);
91
- }
92
- return () => buildJson(receiver as JsonSource<TTable>);
93
- }
94
-
95
- if (typeof prop === 'string' && table.relations[prop]) {
96
- return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
97
- }
98
-
99
- return Reflect.get(targetObj, prop, receiver);
78
+ if (prop === '$load') {
79
+ return async (relationName: RelationKey<TTable>) => {
80
+ const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
81
+ if (wrapper && typeof wrapper.load === 'function') {
82
+ return wrapper.load();
83
+ }
84
+ return undefined;
85
+ };
86
+ }
87
+
88
+ if (prop === 'toJSON') {
89
+ if (prop in targetObj) {
90
+ return Reflect.get(targetObj, prop, receiver);
91
+ }
92
+ return () => buildJson(receiver as JsonSource<TTable>);
93
+ }
94
+
95
+ if (typeof prop === 'string' && table.relations[prop]) {
96
+ return getRelationWrapper(meta, prop as RelationKey<TTable>, receiver, createRelationEntity);
97
+ }
98
+
99
+ return Reflect.get(targetObj, prop, receiver);
100
100
  },
101
101
 
102
102
  set(targetObj, prop, value, receiver) {
@@ -104,14 +104,14 @@ export const createEntityProxy = <
104
104
  if (typeof prop === 'string' && table.columns[prop]) {
105
105
  ctx.markDirty(receiver);
106
106
  }
107
- return result;
108
- }
109
- };
110
-
111
- const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
112
- populateHydrationCache(proxy, row, meta);
113
- return proxy;
114
- };
107
+ return result;
108
+ }
109
+ };
110
+
111
+ const proxy = new Proxy(target, handler) as EntityInstance<TTable>;
112
+ populateHydrationCache(proxy, row, meta);
113
+ return proxy;
114
+ };
115
115
 
116
116
  /**
117
117
  * Creates an entity instance from a database row.