metal-orm 1.0.91 → 1.0.93

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.
@@ -8,8 +8,8 @@ import {
8
8
  BelongsToManyRelation,
9
9
  RelationKinds
10
10
  } from '../../../schema/relation.js';
11
- import type { OpenApiSchema } from '../types.js';
12
- import { columnToOpenApiSchema } from './column.js';
11
+ import type { OpenApiSchema, OpenApiDialect } from '../types.js';
12
+ import { columnToFilterSchema } from './filter.js';
13
13
  import { getColumnMap } from './base.js';
14
14
 
15
15
  export function relationFilterToOpenApiSchema(
@@ -17,14 +17,26 @@ export function relationFilterToOpenApiSchema(
17
17
  options?: {
18
18
  exclude?: string[];
19
19
  include?: string[];
20
- }
20
+ },
21
+ dialect: OpenApiDialect = 'openapi-3.1',
22
+ depth: number = 0
21
23
  ): OpenApiSchema {
22
24
  if (relation.type === RelationKinds.BelongsTo || relation.type === RelationKinds.HasOne) {
23
- return singleRelationFilterToOpenApiSchema((relation as BelongsToRelation | HasOneRelation).target, options);
25
+ return singleRelationFilterToOpenApiSchema(
26
+ (relation as BelongsToRelation | HasOneRelation).target,
27
+ options,
28
+ dialect,
29
+ depth
30
+ );
24
31
  }
25
32
 
26
33
  if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
27
- return manyRelationFilterToOpenApiSchema((relation as HasManyRelation | BelongsToManyRelation).target, options);
34
+ return manyRelationFilterToOpenApiSchema(
35
+ (relation as HasManyRelation | BelongsToManyRelation).target,
36
+ options,
37
+ dialect,
38
+ depth
39
+ );
28
40
  }
29
41
 
30
42
  return { type: 'object', properties: {} };
@@ -32,7 +44,9 @@ export function relationFilterToOpenApiSchema(
32
44
 
33
45
  function singleRelationFilterToOpenApiSchema(
34
46
  target: TableDef | EntityConstructor,
35
- options?: { exclude?: string[]; include?: string[] }
47
+ options?: { exclude?: string[]; include?: string[] },
48
+ dialect: OpenApiDialect = 'openapi-3.1',
49
+ depth: number = 0
36
50
  ): OpenApiSchema {
37
51
  const columns = getColumnMap(target);
38
52
  const properties: Record<string, OpenApiSchema> = {};
@@ -47,13 +61,22 @@ function singleRelationFilterToOpenApiSchema(
47
61
  continue;
48
62
  }
49
63
 
50
- properties[key] = columnToOpenApiSchema(col);
64
+ properties[key] = columnToFilterSchema(col, dialect);
51
65
 
52
66
  if (col.notNull || col.primary) {
53
67
  required.push(key);
54
68
  }
55
69
  }
56
70
 
71
+ if (depth > 0) {
72
+ const tableDef = target as TableDef;
73
+ if (tableDef.relations) {
74
+ for (const [relationName, relation] of Object.entries(tableDef.relations)) {
75
+ properties[relationName] = relationFilterToOpenApiSchema(relation, undefined, dialect, depth - 1);
76
+ }
77
+ }
78
+ }
79
+
57
80
  return {
58
81
  type: 'object',
59
82
  properties,
@@ -63,25 +86,31 @@ function singleRelationFilterToOpenApiSchema(
63
86
 
64
87
  function manyRelationFilterToOpenApiSchema(
65
88
  target: TableDef | EntityConstructor,
66
- options?: { exclude?: string[]; include?: string[] }
89
+ options?: { exclude?: string[]; include?: string[] },
90
+ dialect: OpenApiDialect = 'openapi-3.1',
91
+ depth: number = 0
67
92
  ): OpenApiSchema {
93
+ const nestedSchema: OpenApiSchema = depth > 0
94
+ ? nestedWhereInputToOpenApiSchema(target, depth, dialect)
95
+ : {
96
+ type: 'object',
97
+ properties: generateNestedProperties(target, options, dialect),
98
+ };
99
+
68
100
  return {
69
101
  type: 'object',
70
102
  properties: {
71
103
  some: {
72
- type: 'object',
104
+ ...nestedSchema,
73
105
  description: 'Filter related records that match all conditions',
74
- properties: generateNestedProperties(target, options),
75
106
  },
76
107
  every: {
77
- type: 'object',
108
+ ...nestedSchema,
78
109
  description: 'Filter related records where all match conditions',
79
- properties: generateNestedProperties(target, options),
80
110
  },
81
111
  none: {
82
- type: 'object',
112
+ ...nestedSchema,
83
113
  description: 'Filter where no related records match',
84
- properties: generateNestedProperties(target, options),
85
114
  },
86
115
  isEmpty: {
87
116
  type: 'boolean',
@@ -97,7 +126,8 @@ function manyRelationFilterToOpenApiSchema(
97
126
 
98
127
  function generateNestedProperties(
99
128
  target: TableDef | EntityConstructor,
100
- options?: { exclude?: string[]; include?: string[] }
129
+ options?: { exclude?: string[]; include?: string[] },
130
+ dialect: OpenApiDialect = 'openapi-3.1'
101
131
  ): Record<string, OpenApiSchema> {
102
132
  const columns = getColumnMap(target);
103
133
  const properties: Record<string, OpenApiSchema> = {};
@@ -111,7 +141,7 @@ function generateNestedProperties(
111
141
  continue;
112
142
  }
113
143
 
114
- properties[key] = columnToOpenApiSchema(col);
144
+ properties[key] = columnToFilterSchema(col, dialect);
115
145
  }
116
146
 
117
147
  return properties;
@@ -125,7 +155,8 @@ export function whereInputWithRelationsToOpenApiSchema<T extends TableDef | Enti
125
155
  relationExclude?: string[];
126
156
  relationInclude?: string[];
127
157
  maxDepth?: number;
128
- }
158
+ },
159
+ dialect: OpenApiDialect = 'openapi-3.1'
129
160
  ): OpenApiSchema {
130
161
  const columns = getColumnMap(target);
131
162
  const properties: Record<string, OpenApiSchema> = {};
@@ -140,7 +171,7 @@ export function whereInputWithRelationsToOpenApiSchema<T extends TableDef | Enti
140
171
  continue;
141
172
  }
142
173
 
143
- properties[key] = columnToOpenApiSchema(col);
174
+ properties[key] = columnToFilterSchema(col, dialect);
144
175
  }
145
176
 
146
177
  const tableDef = target as TableDef;
@@ -157,7 +188,7 @@ export function whereInputWithRelationsToOpenApiSchema<T extends TableDef | Enti
157
188
  properties[relationName] = relationFilterToOpenApiSchema(relation, {
158
189
  exclude: options?.columnExclude,
159
190
  include: options?.columnInclude,
160
- });
191
+ }, dialect);
161
192
  }
162
193
  }
163
194
 
@@ -169,7 +200,8 @@ export function whereInputWithRelationsToOpenApiSchema<T extends TableDef | Enti
169
200
 
170
201
  export function nestedWhereInputToOpenApiSchema<T extends TableDef | EntityConstructor>(
171
202
  target: T,
172
- depth: number = 2
203
+ depth: number = 2,
204
+ dialect: OpenApiDialect = 'openapi-3.1'
173
205
  ): OpenApiSchema {
174
206
  if (depth <= 0) {
175
207
  return { type: 'object', properties: {} };
@@ -179,27 +211,13 @@ export function nestedWhereInputToOpenApiSchema<T extends TableDef | EntityConst
179
211
  const properties: Record<string, OpenApiSchema> = {};
180
212
 
181
213
  for (const [key, col] of Object.entries(columns)) {
182
- properties[key] = columnToOpenApiSchema(col);
214
+ properties[key] = columnToFilterSchema(col, dialect);
183
215
  }
184
216
 
185
217
  const tableDef = target as TableDef;
186
218
  if (tableDef.relations) {
187
219
  for (const [relationName, relation] of Object.entries(tableDef.relations)) {
188
- properties[relationName] = relationFilterToOpenApiSchema(relation);
189
-
190
- if (depth > 1) {
191
- if (relation.type === RelationKinds.BelongsTo || relation.type === RelationKinds.HasOne) {
192
- properties[relationName] = singleRelationFilterToOpenApiSchema((relation as BelongsToRelation | HasOneRelation).target);
193
- } else if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
194
- properties[relationName] = {
195
- type: 'object',
196
- properties: {
197
- some: nestedWhereInputToOpenApiSchema((relation as HasManyRelation | BelongsToManyRelation).target, depth - 1),
198
- every: nestedWhereInputToOpenApiSchema((relation as HasManyRelation | BelongsToManyRelation).target, depth - 1),
199
- },
200
- };
201
- }
202
- }
220
+ properties[relationName] = relationFilterToOpenApiSchema(relation, undefined, dialect, depth - 1);
203
221
  }
204
222
  }
205
223
 
@@ -25,7 +25,7 @@ export interface OpenApiSchema {
25
25
  oneOf?: OpenApiSchema[];
26
26
  }
27
27
 
28
- export interface OpenApiParameter {
28
+ export interface OpenApiParameterObject {
29
29
  name: string;
30
30
  in: 'query' | 'path' | 'header' | 'cookie';
31
31
  required?: boolean;
@@ -33,10 +33,19 @@ export interface OpenApiParameter {
33
33
  description?: string;
34
34
  }
35
35
 
36
+ export interface OpenApiResponseObject {
37
+ description: string;
38
+ content?: {
39
+ 'application/json': {
40
+ schema: OpenApiSchema;
41
+ };
42
+ };
43
+ }
44
+
36
45
  export interface OpenApiOperation {
37
46
  summary?: string;
38
47
  description?: string;
39
- parameters?: OpenApiParameter[];
48
+ parameters?: OpenApiParameterObject[];
40
49
  requestBody?: {
41
50
  description?: string;
42
51
  required?: boolean;
@@ -46,14 +55,7 @@ export interface OpenApiOperation {
46
55
  };
47
56
  };
48
57
  };
49
- responses?: Record<string, {
50
- description: string;
51
- content?: {
52
- 'application/json': {
53
- schema: OpenApiSchema;
54
- };
55
- };
56
- }>;
58
+ responses?: Record<string, OpenApiResponseObject>;
57
59
  }
58
60
 
59
61
  export interface OpenApiDocumentInfo {
@@ -77,8 +79,8 @@ export interface PaginationParams {
77
79
 
78
80
  export interface OpenApiComponent {
79
81
  schemas?: Record<string, OpenApiSchema>;
80
- parameters?: Record<string, OpenApiSchema>;
81
- responses?: Record<string, OpenApiSchema>;
82
+ parameters?: Record<string, OpenApiParameterObject>;
83
+ responses?: Record<string, OpenApiResponseObject>;
82
84
  securitySchemes?: Record<string, unknown>;
83
85
  }
84
86
 
@@ -88,3 +90,12 @@ export interface OpenApiDocument {
88
90
  paths: Record<string, Record<string, OpenApiOperation>>;
89
91
  components?: OpenApiComponent;
90
92
  }
93
+
94
+ export type OpenApiDialect = 'openapi-3.0' | 'openapi-3.1';
95
+
96
+ export interface OpenApiDocumentOptions {
97
+ dialect?: OpenApiDialect;
98
+ allowScalarEquals?: boolean;
99
+ }
100
+
101
+ export type OpenApiParameter = OpenApiParameterObject;
@@ -1,4 +1,4 @@
1
- import type { OpenApiSchema, OpenApiOperation, OpenApiDocumentInfo, ApiRouteDefinition, OpenApiDocument } from './types.js';
1
+ import type { OpenApiSchema, OpenApiOperation, OpenApiDocumentInfo, ApiRouteDefinition, OpenApiDocument, OpenApiDialect, OpenApiType } from './types.js';
2
2
 
3
3
  export function schemaToJson(schema: OpenApiSchema): string {
4
4
  return JSON.stringify(schema, null, 2);
@@ -20,10 +20,55 @@ export function mergeSchemas(base: OpenApiSchema, override: Partial<OpenApiSchem
20
20
  };
21
21
  }
22
22
 
23
+ export function applyNullability(
24
+ schema: OpenApiSchema,
25
+ isNullable: boolean,
26
+ dialect: OpenApiDialect
27
+ ): OpenApiSchema {
28
+ if (!isNullable) {
29
+ const { nullable: _, ...clean } = schema;
30
+ return clean;
31
+ }
32
+
33
+ if (dialect === 'openapi-3.0') {
34
+ return { ...schema, nullable: true };
35
+ }
36
+
37
+ const type = schema.type;
38
+ if (Array.isArray(type)) {
39
+ if (!type.includes('null')) {
40
+ const { nullable: _, ...clean } = schema;
41
+ return { ...clean, type: [...type, 'null'] as OpenApiType[] };
42
+ }
43
+ } else if (type) {
44
+ const { nullable: _, ...clean } = schema;
45
+ return { ...clean, type: [type, 'null'] as OpenApiType[] };
46
+ } else {
47
+ const { nullable: _, ...clean } = schema;
48
+ return { ...clean, type: ['null'] as OpenApiType[] };
49
+ }
50
+
51
+ const { nullable: _, ...clean } = schema;
52
+ return clean;
53
+ }
54
+
55
+ export function isNullableColumn(col: { notNull?: boolean }): boolean {
56
+ return !col.notNull;
57
+ }
58
+
59
+ export function getOpenApiVersionForDialect(dialect: OpenApiDialect): string {
60
+ return dialect === 'openapi-3.0' ? '3.0.3' : '3.1.0';
61
+ }
62
+
23
63
  export function generateOpenApiDocument(
24
64
  info: OpenApiDocumentInfo,
25
- routes: ApiRouteDefinition[]
65
+ routes: ApiRouteDefinition[],
66
+ options?: {
67
+ dialect?: OpenApiDialect;
68
+ allowScalarEquals?: boolean;
69
+ }
26
70
  ): OpenApiDocument {
71
+ const dialect: OpenApiDialect = options?.dialect ?? 'openapi-3.1';
27
72
  const paths: Record<string, Record<string, OpenApiOperation>> = {};
28
73
 
29
74
  for (const route of routes) {
@@ -34,7 +79,7 @@ export function generateOpenApiDocument(
34
79
  }
35
80
 
36
81
  return {
37
- openapi: '3.1.0',
82
+ openapi: getOpenApiVersionForDialect(dialect),
38
83
  info: {
39
84
  title: info.title,
40
85
  version: info.version,
@@ -21,11 +21,11 @@ export type EntityOrTableTarget = EntityConstructor | TableDef;
21
21
  export type EntityOrTableTargetResolver<T extends EntityOrTableTarget = EntityOrTableTarget> =
22
22
  T | (() => T);
23
23
 
24
- /**
25
- * Simplified column definition structure used during metadata registration.
26
- * @template T - Concrete column definition type being extended
27
- */
28
- export type ColumnDefLike<T extends ColumnDef = ColumnDef> = Omit<T, 'name' | 'table'>;
24
+ /**
25
+ * Simplified column definition structure used during metadata registration.
26
+ * @template T - Concrete column definition type being extended
27
+ */
28
+ export type ColumnDefLike<T extends ColumnDef = ColumnDef> = Omit<T, 'table'>;
29
29
 
30
30
  /**
31
31
  * Transforms simplified column metadata into full ColumnDef objects during table building.
@@ -250,15 +250,15 @@ export const buildTableDef = <TColumns extends Record<string, ColumnDefLike>>(me
250
250
  return meta.table;
251
251
  }
252
252
 
253
- // Build columns using a simpler approach that avoids type assertion
254
- const columns: Record<string, ColumnDef> = {};
255
- for (const [key, def] of Object.entries(meta.columns)) {
256
- columns[key] = {
257
- ...def,
258
- name: key,
259
- table: meta.tableName
260
- } as ColumnDef;
261
- }
253
+ // Build columns using a simpler approach that avoids type assertion
254
+ const columns: Record<string, ColumnDef> = {};
255
+ for (const [key, def] of Object.entries(meta.columns)) {
256
+ columns[key] = {
257
+ ...def,
258
+ name: def.name ?? key,
259
+ table: meta.tableName
260
+ } as ColumnDef;
261
+ }
262
262
 
263
263
  const table = defineTable(meta.tableName, columns as MaterializeColumns<TColumns>, {}, meta.hooks);
264
264
  meta.table = table;