metal-orm 1.0.88 → 1.0.90
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 +3818 -3783
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +765 -236
- package/dist/index.d.ts +765 -236
- package/dist/index.js +3763 -3775
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- 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/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 +11 -8
- package/src/dto/apply-filter.ts +281 -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 +83 -0
- package/src/dto/openapi/utilities.ts +45 -0
- package/src/dto/pagination-utils.ts +150 -0
- package/src/dto/transform.ts +193 -0
- package/src/index.ts +67 -65
- 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/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 -139
- 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 -111
- package/src/openapi/schema-types.ts +0 -176
- package/src/openapi/type-mappers.ts +0 -227
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CommonTableExpressionNode,
|
|
3
|
-
DerivedTableNode,
|
|
4
|
-
FunctionTableNode,
|
|
5
|
-
OrderByNode,
|
|
6
|
-
OrderingTerm,
|
|
7
|
-
SelectQueryNode,
|
|
8
|
-
SetOperationNode,
|
|
9
|
-
TableSourceNode
|
|
10
|
-
} from './query.js';
|
|
11
|
-
import type { JoinNode } from './join.js';
|
|
12
|
-
import type {
|
|
13
|
-
ExpressionNode,
|
|
14
|
-
OperandNode,
|
|
15
|
-
ParamNode
|
|
16
|
-
} from './expression-nodes.js';
|
|
17
|
-
import { isOperandNode } from './expression-nodes.js';
|
|
18
|
-
import {
|
|
19
|
-
hasOperandDispatcher,
|
|
20
|
-
type ExpressionVisitor,
|
|
21
|
-
type OperandVisitor,
|
|
22
|
-
visitExpression,
|
|
23
|
-
visitOperand
|
|
24
|
-
} from './expression-visitor.js';
|
|
25
|
-
|
|
26
|
-
export interface SelectQueryVisitor {
|
|
27
|
-
visitSelectQuery?(node: SelectQueryNode): void;
|
|
28
|
-
visitTableSource?(node: TableSourceNode): void;
|
|
29
|
-
visitDerivedTable?(node: DerivedTableNode): void;
|
|
30
|
-
visitFunctionTable?(node: FunctionTableNode): void;
|
|
31
|
-
visitJoin?(node: JoinNode): void;
|
|
32
|
-
visitCte?(node: CommonTableExpressionNode): void;
|
|
33
|
-
visitSetOperation?(node: SetOperationNode): void;
|
|
34
|
-
visitOrderBy?(node: OrderByNode): void;
|
|
35
|
-
visitExpression?(node: ExpressionNode): void;
|
|
36
|
-
visitOperand?(node: OperandNode): void;
|
|
37
|
-
visitParam?(node: ParamNode): void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const getNodeType = (value: unknown): string | undefined => {
|
|
41
|
-
if (typeof value !== 'object' || value === null) return undefined;
|
|
42
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, 'type');
|
|
43
|
-
if (descriptor && typeof descriptor.value === 'string') {
|
|
44
|
-
return descriptor.value;
|
|
45
|
-
}
|
|
46
|
-
if ('type' in value) {
|
|
47
|
-
const type = (value as { type?: unknown }).type;
|
|
48
|
-
return typeof type === 'string' ? type : undefined;
|
|
49
|
-
}
|
|
50
|
-
return undefined;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const visitSelectQuery = (ast: SelectQueryNode, visitor: SelectQueryVisitor): void => {
|
|
54
|
-
const visitExpressionNode = (node: ExpressionNode): void => {
|
|
55
|
-
visitExpression(node, expressionVisitor);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const visitOperandNode = (node: OperandNode): void => {
|
|
59
|
-
visitOperand(node, operandVisitor);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const visitOrderingTerm = (term: OrderingTerm): void => {
|
|
63
|
-
if (!term || typeof term !== 'object') return;
|
|
64
|
-
if (isOperandNode(term)) {
|
|
65
|
-
visitOperandNode(term);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const type = getNodeType(term);
|
|
69
|
-
if (type && hasOperandDispatcher(type)) {
|
|
70
|
-
visitOperandNode(term as unknown as OperandNode);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (type) {
|
|
74
|
-
visitExpressionNode(term as ExpressionNode);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const visitOrderByNode = (node: OrderByNode): void => {
|
|
79
|
-
visitor.visitOrderBy?.(node);
|
|
80
|
-
visitOrderingTerm(node.term);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const visitTableSource = (source: TableSourceNode): void => {
|
|
84
|
-
visitor.visitTableSource?.(source);
|
|
85
|
-
if (source.type === 'DerivedTable') {
|
|
86
|
-
visitor.visitDerivedTable?.(source);
|
|
87
|
-
visitSelectQuery(source.query, visitor);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (source.type === 'FunctionTable') {
|
|
91
|
-
visitor.visitFunctionTable?.(source);
|
|
92
|
-
source.args?.forEach(arg => visitOperandNode(arg));
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const expressionVisitor: ExpressionVisitor<void> = {
|
|
97
|
-
visitBinaryExpression: (node) => {
|
|
98
|
-
visitor.visitExpression?.(node);
|
|
99
|
-
visitOperandNode(node.left);
|
|
100
|
-
visitOperandNode(node.right);
|
|
101
|
-
if (node.escape) {
|
|
102
|
-
visitOperandNode(node.escape);
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
visitLogicalExpression: (node) => {
|
|
106
|
-
visitor.visitExpression?.(node);
|
|
107
|
-
node.operands.forEach(operand => visitExpressionNode(operand));
|
|
108
|
-
},
|
|
109
|
-
visitNullExpression: (node) => {
|
|
110
|
-
visitor.visitExpression?.(node);
|
|
111
|
-
visitOperandNode(node.left);
|
|
112
|
-
},
|
|
113
|
-
visitInExpression: (node) => {
|
|
114
|
-
visitor.visitExpression?.(node);
|
|
115
|
-
visitOperandNode(node.left);
|
|
116
|
-
if (Array.isArray(node.right)) {
|
|
117
|
-
node.right.forEach(operand => visitOperandNode(operand));
|
|
118
|
-
} else {
|
|
119
|
-
visitOperandNode(node.right);
|
|
120
|
-
}
|
|
121
|
-
},
|
|
122
|
-
visitExistsExpression: (node) => {
|
|
123
|
-
visitor.visitExpression?.(node);
|
|
124
|
-
visitSelectQuery(node.subquery, visitor);
|
|
125
|
-
},
|
|
126
|
-
visitBetweenExpression: (node) => {
|
|
127
|
-
visitor.visitExpression?.(node);
|
|
128
|
-
visitOperandNode(node.left);
|
|
129
|
-
visitOperandNode(node.lower);
|
|
130
|
-
visitOperandNode(node.upper);
|
|
131
|
-
},
|
|
132
|
-
visitArithmeticExpression: (node) => {
|
|
133
|
-
visitor.visitExpression?.(node);
|
|
134
|
-
visitOperandNode(node.left);
|
|
135
|
-
visitOperandNode(node.right);
|
|
136
|
-
},
|
|
137
|
-
visitBitwiseExpression: (node) => {
|
|
138
|
-
visitor.visitExpression?.(node);
|
|
139
|
-
visitOperandNode(node.left);
|
|
140
|
-
visitOperandNode(node.right);
|
|
141
|
-
},
|
|
142
|
-
visitOperand: (node) => {
|
|
143
|
-
visitOperandNode(node);
|
|
144
|
-
},
|
|
145
|
-
visitSelectQuery: (node) => {
|
|
146
|
-
visitSelectQuery(node, visitor);
|
|
147
|
-
},
|
|
148
|
-
otherwise: (node) => {
|
|
149
|
-
visitor.visitExpression?.(node);
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const operandVisitor: OperandVisitor<void> = {
|
|
154
|
-
visitColumn: (node) => {
|
|
155
|
-
visitor.visitOperand?.(node);
|
|
156
|
-
},
|
|
157
|
-
visitLiteral: (node) => {
|
|
158
|
-
visitor.visitOperand?.(node);
|
|
159
|
-
},
|
|
160
|
-
visitParam: (node) => {
|
|
161
|
-
visitor.visitOperand?.(node);
|
|
162
|
-
visitor.visitParam?.(node);
|
|
163
|
-
},
|
|
164
|
-
visitFunction: (node) => {
|
|
165
|
-
visitor.visitOperand?.(node);
|
|
166
|
-
node.args?.forEach(arg => visitOperandNode(arg));
|
|
167
|
-
node.orderBy?.forEach(order => visitOrderByNode(order));
|
|
168
|
-
if (node.separator) {
|
|
169
|
-
visitOperandNode(node.separator);
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
visitJsonPath: (node) => {
|
|
173
|
-
visitor.visitOperand?.(node);
|
|
174
|
-
visitOperandNode(node.column);
|
|
175
|
-
},
|
|
176
|
-
visitScalarSubquery: (node) => {
|
|
177
|
-
visitor.visitOperand?.(node);
|
|
178
|
-
visitSelectQuery(node.query, visitor);
|
|
179
|
-
},
|
|
180
|
-
visitCaseExpression: (node) => {
|
|
181
|
-
visitor.visitOperand?.(node);
|
|
182
|
-
node.conditions.forEach(cond => {
|
|
183
|
-
visitExpressionNode(cond.when);
|
|
184
|
-
visitOperandNode(cond.then);
|
|
185
|
-
});
|
|
186
|
-
if (node.else) {
|
|
187
|
-
visitOperandNode(node.else);
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
visitCast: (node) => {
|
|
191
|
-
visitor.visitOperand?.(node);
|
|
192
|
-
visitOperandNode(node.expression);
|
|
193
|
-
},
|
|
194
|
-
visitWindowFunction: (node) => {
|
|
195
|
-
visitor.visitOperand?.(node);
|
|
196
|
-
node.args?.forEach(arg => visitOperandNode(arg));
|
|
197
|
-
node.partitionBy?.forEach(term => visitOperandNode(term));
|
|
198
|
-
node.orderBy?.forEach(order => visitOrderByNode(order));
|
|
199
|
-
},
|
|
200
|
-
visitArithmeticExpression: (node) => {
|
|
201
|
-
visitor.visitOperand?.(node);
|
|
202
|
-
visitOperandNode(node.left);
|
|
203
|
-
visitOperandNode(node.right);
|
|
204
|
-
},
|
|
205
|
-
visitBitwiseExpression: (node) => {
|
|
206
|
-
visitor.visitOperand?.(node);
|
|
207
|
-
visitOperandNode(node.left);
|
|
208
|
-
visitOperandNode(node.right);
|
|
209
|
-
},
|
|
210
|
-
visitExpression: (node) => {
|
|
211
|
-
visitExpressionNode(node);
|
|
212
|
-
},
|
|
213
|
-
visitSelectQuery: (node) => {
|
|
214
|
-
visitSelectQuery(node, visitor);
|
|
215
|
-
},
|
|
216
|
-
visitCollate: (node) => {
|
|
217
|
-
visitor.visitOperand?.(node);
|
|
218
|
-
visitOperandNode(node.expression);
|
|
219
|
-
},
|
|
220
|
-
visitAliasRef: (node) => {
|
|
221
|
-
visitor.visitOperand?.(node);
|
|
222
|
-
},
|
|
223
|
-
otherwise: (node) => {
|
|
224
|
-
visitor.visitOperand?.(node);
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
visitor.visitSelectQuery?.(ast);
|
|
229
|
-
|
|
230
|
-
if (ast.ctes) {
|
|
231
|
-
for (const cte of ast.ctes) {
|
|
232
|
-
visitor.visitCte?.(cte);
|
|
233
|
-
visitSelectQuery(cte.query, visitor);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
visitTableSource(ast.from);
|
|
238
|
-
|
|
239
|
-
ast.columns?.forEach(col => {
|
|
240
|
-
visitOperandNode(col as OperandNode);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
ast.joins?.forEach(join => {
|
|
244
|
-
visitor.visitJoin?.(join);
|
|
245
|
-
visitTableSource(join.table);
|
|
246
|
-
visitExpressionNode(join.condition);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
if (ast.where) {
|
|
250
|
-
visitExpressionNode(ast.where);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
ast.groupBy?.forEach(term => {
|
|
254
|
-
visitOrderingTerm(term);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
if (ast.having) {
|
|
258
|
-
visitExpressionNode(ast.having);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
ast.orderBy?.forEach(order => {
|
|
262
|
-
visitOrderByNode(order);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
ast.distinct?.forEach(col => {
|
|
266
|
-
visitOperandNode(col);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
ast.setOps?.forEach(op => {
|
|
270
|
-
visitor.visitSetOperation?.(op);
|
|
271
|
-
visitSelectQuery(op.query, visitor);
|
|
272
|
-
});
|
|
273
|
-
};
|
package/src/openapi/index.ts
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import type { TableDef } from '../schema/table.js';
|
|
2
|
-
import {
|
|
3
|
-
isOperandNode,
|
|
4
|
-
type ExpressionNode,
|
|
5
|
-
type OperandNode,
|
|
6
|
-
type ColumnNode,
|
|
7
|
-
type FunctionNode,
|
|
8
|
-
type JsonPathNode,
|
|
9
|
-
type CaseExpressionNode,
|
|
10
|
-
type CastExpressionNode,
|
|
11
|
-
type WindowFunctionNode,
|
|
12
|
-
type ArithmeticExpressionNode,
|
|
13
|
-
type BitwiseExpressionNode,
|
|
14
|
-
type CollateExpressionNode
|
|
15
|
-
} from '../core/ast/expression.js';
|
|
16
|
-
import type { TableSourceNode, OrderByNode, OrderingTerm } from '../core/ast/query.js';
|
|
17
|
-
import type { ColumnSchemaOptions, JsonSchemaProperty, OpenApiParameter } from './schema-types.js';
|
|
18
|
-
import { mapColumnType } from './type-mappers.js';
|
|
19
|
-
|
|
20
|
-
const FILTER_PARAM_NAME = 'filter';
|
|
21
|
-
|
|
22
|
-
const buildRootTableNames = (table: TableDef, from?: TableSourceNode): Set<string> => {
|
|
23
|
-
const names = new Set<string>([table.name]);
|
|
24
|
-
if (from?.type === 'Table') {
|
|
25
|
-
names.add(from.name);
|
|
26
|
-
if (from.alias) names.add(from.alias);
|
|
27
|
-
}
|
|
28
|
-
return names;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const collectFilterColumns = (
|
|
32
|
-
expr: ExpressionNode,
|
|
33
|
-
table: TableDef,
|
|
34
|
-
rootTables: Set<string>
|
|
35
|
-
): Set<string> => {
|
|
36
|
-
const columns = new Set<string>();
|
|
37
|
-
|
|
38
|
-
const recordColumn = (node: ColumnNode): void => {
|
|
39
|
-
if (!rootTables.has(node.table)) return;
|
|
40
|
-
if (node.name in table.columns) {
|
|
41
|
-
columns.add(node.name);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const visitOrderingTerm = (term: OrderingTerm): void => {
|
|
46
|
-
if (!term || typeof term !== 'object') return;
|
|
47
|
-
if (isOperandNode(term)) {
|
|
48
|
-
visitOperand(term as OperandNode);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if ('type' in term) {
|
|
52
|
-
visitExpression(term as ExpressionNode);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const visitOrderBy = (orderBy: OrderByNode[] | undefined): void => {
|
|
57
|
-
if (!orderBy) return;
|
|
58
|
-
orderBy.forEach(node => visitOrderingTerm(node.term));
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const visitOperand = (node: OperandNode): void => {
|
|
62
|
-
switch (node.type) {
|
|
63
|
-
case 'Column':
|
|
64
|
-
recordColumn(node as ColumnNode);
|
|
65
|
-
return;
|
|
66
|
-
case 'Function': {
|
|
67
|
-
const fn = node as FunctionNode;
|
|
68
|
-
fn.args?.forEach(visitOperand);
|
|
69
|
-
visitOrderBy(fn.orderBy);
|
|
70
|
-
if (fn.separator) visitOperand(fn.separator);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
case 'JsonPath': {
|
|
74
|
-
const jp = node as JsonPathNode;
|
|
75
|
-
recordColumn(jp.column);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
case 'ScalarSubquery':
|
|
79
|
-
return;
|
|
80
|
-
case 'CaseExpression': {
|
|
81
|
-
const cs = node as CaseExpressionNode;
|
|
82
|
-
cs.conditions.forEach(condition => {
|
|
83
|
-
visitExpression(condition.when);
|
|
84
|
-
visitOperand(condition.then);
|
|
85
|
-
});
|
|
86
|
-
if (cs.else) visitOperand(cs.else);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
case 'Cast': {
|
|
90
|
-
const cast = node as CastExpressionNode;
|
|
91
|
-
visitOperand(cast.expression);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
case 'WindowFunction': {
|
|
95
|
-
const windowFn = node as WindowFunctionNode;
|
|
96
|
-
windowFn.args?.forEach(visitOperand);
|
|
97
|
-
windowFn.partitionBy?.forEach(recordColumn);
|
|
98
|
-
visitOrderBy(windowFn.orderBy);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
case 'ArithmeticExpression': {
|
|
102
|
-
const arith = node as ArithmeticExpressionNode;
|
|
103
|
-
visitOperand(arith.left);
|
|
104
|
-
visitOperand(arith.right);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
case 'BitwiseExpression': {
|
|
108
|
-
const bitwise = node as BitwiseExpressionNode;
|
|
109
|
-
visitOperand(bitwise.left);
|
|
110
|
-
visitOperand(bitwise.right);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
case 'Collate': {
|
|
114
|
-
const collate = node as CollateExpressionNode;
|
|
115
|
-
visitOperand(collate.expression);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
case 'AliasRef':
|
|
119
|
-
case 'Literal':
|
|
120
|
-
case 'Param':
|
|
121
|
-
return;
|
|
122
|
-
default:
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const visitExpression = (node: ExpressionNode): void => {
|
|
128
|
-
switch (node.type) {
|
|
129
|
-
case 'BinaryExpression':
|
|
130
|
-
visitOperand(node.left);
|
|
131
|
-
visitOperand(node.right);
|
|
132
|
-
if (node.escape) visitOperand(node.escape);
|
|
133
|
-
return;
|
|
134
|
-
case 'LogicalExpression':
|
|
135
|
-
node.operands.forEach(visitExpression);
|
|
136
|
-
return;
|
|
137
|
-
case 'NullExpression':
|
|
138
|
-
visitOperand(node.left);
|
|
139
|
-
return;
|
|
140
|
-
case 'InExpression':
|
|
141
|
-
visitOperand(node.left);
|
|
142
|
-
if (Array.isArray(node.right)) {
|
|
143
|
-
node.right.forEach(visitOperand);
|
|
144
|
-
}
|
|
145
|
-
return;
|
|
146
|
-
case 'ExistsExpression':
|
|
147
|
-
return;
|
|
148
|
-
case 'BetweenExpression':
|
|
149
|
-
visitOperand(node.left);
|
|
150
|
-
visitOperand(node.lower);
|
|
151
|
-
visitOperand(node.upper);
|
|
152
|
-
return;
|
|
153
|
-
case 'ArithmeticExpression':
|
|
154
|
-
visitOperand(node.left);
|
|
155
|
-
visitOperand(node.right);
|
|
156
|
-
return;
|
|
157
|
-
case 'BitwiseExpression':
|
|
158
|
-
visitOperand(node.left);
|
|
159
|
-
visitOperand(node.right);
|
|
160
|
-
return;
|
|
161
|
-
default:
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
visitExpression(expr);
|
|
167
|
-
return columns;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export const buildFilterParameters = (
|
|
171
|
-
table: TableDef,
|
|
172
|
-
where: ExpressionNode | undefined,
|
|
173
|
-
from: TableSourceNode | undefined,
|
|
174
|
-
options: ColumnSchemaOptions = {}
|
|
175
|
-
): OpenApiParameter[] => {
|
|
176
|
-
if (!where) return [];
|
|
177
|
-
|
|
178
|
-
const rootTables = buildRootTableNames(table, from);
|
|
179
|
-
const columnNames = collectFilterColumns(where, table, rootTables);
|
|
180
|
-
|
|
181
|
-
let schema: JsonSchemaProperty;
|
|
182
|
-
if (columnNames.size) {
|
|
183
|
-
const properties: Record<string, JsonSchemaProperty> = {};
|
|
184
|
-
for (const name of columnNames) {
|
|
185
|
-
const column = table.columns[name];
|
|
186
|
-
if (!column) continue;
|
|
187
|
-
properties[name] = mapColumnType(column, options);
|
|
188
|
-
}
|
|
189
|
-
schema = {
|
|
190
|
-
type: 'object',
|
|
191
|
-
properties
|
|
192
|
-
};
|
|
193
|
-
} else {
|
|
194
|
-
schema = {
|
|
195
|
-
type: 'object',
|
|
196
|
-
additionalProperties: true
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return [{
|
|
201
|
-
name: FILTER_PARAM_NAME,
|
|
202
|
-
in: 'query',
|
|
203
|
-
style: 'deepObject',
|
|
204
|
-
explode: true,
|
|
205
|
-
schema
|
|
206
|
-
}];
|
|
207
|
-
};
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import type { TableDef } from '../schema/table.js';
|
|
2
|
-
import type { RelationDef } from '../schema/relation.js';
|
|
3
|
-
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
OpenApiSchema,
|
|
7
|
-
SchemaExtractionContext,
|
|
8
|
-
InputSchemaOptions,
|
|
9
|
-
JsonSchemaProperty,
|
|
10
|
-
JsonSchemaType
|
|
11
|
-
} from './schema-types.js';
|
|
12
|
-
import { mapColumnType, mapRelationType } from './type-mappers.js';
|
|
13
|
-
import { buildCircularReferenceSchema } from './schema-extractor-utils.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Input schema extraction (write payloads)
|
|
17
|
-
*/
|
|
18
|
-
export const extractInputSchema = (
|
|
19
|
-
table: TableDef,
|
|
20
|
-
context: SchemaExtractionContext,
|
|
21
|
-
options: InputSchemaOptions
|
|
22
|
-
): OpenApiSchema => {
|
|
23
|
-
const cacheKey = `${table.name}:${options.mode ?? 'create'}`;
|
|
24
|
-
|
|
25
|
-
if (context.schemaCache.has(cacheKey)) {
|
|
26
|
-
return context.schemaCache.get(cacheKey)!;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (context.visitedTables.has(cacheKey) && context.depth > 0) {
|
|
30
|
-
return buildCircularReferenceSchema(table.name, 'input');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
context.visitedTables.add(cacheKey);
|
|
34
|
-
|
|
35
|
-
const properties: Record<string, JsonSchemaProperty> = {};
|
|
36
|
-
const required: string[] = [];
|
|
37
|
-
const primaryKey = findPrimaryKey(table);
|
|
38
|
-
|
|
39
|
-
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
40
|
-
const isPrimary = columnName === primaryKey || column.primary;
|
|
41
|
-
if (options.excludePrimaryKey && isPrimary) continue;
|
|
42
|
-
if (options.omitReadOnly && isReadOnlyColumn(column)) continue;
|
|
43
|
-
|
|
44
|
-
properties[columnName] = mapColumnType(column, options);
|
|
45
|
-
|
|
46
|
-
if (options.mode === 'create' && isRequiredForCreate(column)) {
|
|
47
|
-
required.push(columnName);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (options.mode === 'update' && options.requirePrimaryKey && isPrimary) {
|
|
51
|
-
required.push(columnName);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (options.includeRelations && context.depth < context.maxDepth) {
|
|
56
|
-
for (const [relationName, relation] of Object.entries(table.relations)) {
|
|
57
|
-
properties[relationName] = extractInputRelationSchema(
|
|
58
|
-
relation,
|
|
59
|
-
{ ...context, depth: context.depth + 1 },
|
|
60
|
-
options
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const schema: OpenApiSchema = {
|
|
66
|
-
type: 'object',
|
|
67
|
-
properties,
|
|
68
|
-
required
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
context.schemaCache.set(cacheKey, schema);
|
|
72
|
-
return schema;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const isReadOnlyColumn = (column: { autoIncrement?: boolean; generated?: string }): boolean =>
|
|
76
|
-
Boolean(column.autoIncrement || column.generated === 'always');
|
|
77
|
-
|
|
78
|
-
const isRequiredForCreate = (column: { notNull?: boolean; primary?: boolean; default?: unknown; autoIncrement?: boolean; generated?: string }): boolean => {
|
|
79
|
-
if (isReadOnlyColumn(column)) return false;
|
|
80
|
-
if (column.default !== undefined) return false;
|
|
81
|
-
return Boolean(column.notNull || column.primary);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const buildPrimaryKeySchema = (
|
|
85
|
-
table: TableDef,
|
|
86
|
-
options: InputSchemaOptions
|
|
87
|
-
): JsonSchemaProperty => {
|
|
88
|
-
const primaryKey = findPrimaryKey(table);
|
|
89
|
-
const column = table.columns[primaryKey];
|
|
90
|
-
if (!column) {
|
|
91
|
-
return {
|
|
92
|
-
anyOf: [
|
|
93
|
-
{ type: 'string' as JsonSchemaType },
|
|
94
|
-
{ type: 'number' as JsonSchemaType },
|
|
95
|
-
{ type: 'integer' as JsonSchemaType }
|
|
96
|
-
]
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return mapColumnType(column, options);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const extractInputRelationSchema = (
|
|
104
|
-
relation: RelationDef,
|
|
105
|
-
context: SchemaExtractionContext,
|
|
106
|
-
options: InputSchemaOptions
|
|
107
|
-
): JsonSchemaProperty => {
|
|
108
|
-
const { type: relationType, isNullable } = mapRelationType(relation.type);
|
|
109
|
-
const relationMode = options.relationMode ?? 'mixed';
|
|
110
|
-
const allowIds = relationMode !== 'objects';
|
|
111
|
-
const allowObjects = relationMode !== 'ids';
|
|
112
|
-
|
|
113
|
-
const variants: JsonSchemaProperty[] = [];
|
|
114
|
-
|
|
115
|
-
if (allowIds) {
|
|
116
|
-
variants.push(buildPrimaryKeySchema(relation.target, options));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (allowObjects) {
|
|
120
|
-
const targetSchema = extractInputSchema(relation.target, context, options);
|
|
121
|
-
variants.push(targetSchema as JsonSchemaProperty);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const itemSchema: JsonSchemaProperty =
|
|
125
|
-
variants.length === 1 ? variants[0] : { anyOf: variants };
|
|
126
|
-
|
|
127
|
-
if (relationType === 'array') {
|
|
128
|
-
return {
|
|
129
|
-
type: 'array',
|
|
130
|
-
items: itemSchema,
|
|
131
|
-
nullable: isNullable
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
...itemSchema,
|
|
137
|
-
nullable: isNullable
|
|
138
|
-
};
|
|
139
|
-
};
|