metal-orm 1.0.88 → 1.0.89
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 +57 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +57 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/openapi/schema-extractor-input.ts +55 -1
- package/src/openapi/schema-extractor.ts +15 -6
- package/src/openapi/schema-types.ts +12 -1
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { TableDef } from '../schema/table.js';
|
|
2
2
|
import type { RelationDef } from '../schema/relation.js';
|
|
3
|
+
import { RelationKinds } from '../schema/relation.js';
|
|
3
4
|
import { findPrimaryKey } from '../query-builder/hydration-planner.js';
|
|
4
5
|
|
|
5
6
|
import type {
|
|
6
7
|
OpenApiSchema,
|
|
7
8
|
SchemaExtractionContext,
|
|
8
9
|
InputSchemaOptions,
|
|
10
|
+
RelationSelection,
|
|
9
11
|
JsonSchemaProperty,
|
|
10
12
|
JsonSchemaType
|
|
11
13
|
} from './schema-types.js';
|
|
@@ -55,6 +57,7 @@ export const extractInputSchema = (
|
|
|
55
57
|
if (options.includeRelations && context.depth < context.maxDepth) {
|
|
56
58
|
for (const [relationName, relation] of Object.entries(table.relations)) {
|
|
57
59
|
properties[relationName] = extractInputRelationSchema(
|
|
60
|
+
relationName,
|
|
58
61
|
relation,
|
|
59
62
|
{ ...context, depth: context.depth + 1 },
|
|
60
63
|
options
|
|
@@ -101,6 +104,7 @@ const buildPrimaryKeySchema = (
|
|
|
101
104
|
};
|
|
102
105
|
|
|
103
106
|
const extractInputRelationSchema = (
|
|
107
|
+
relationName: string,
|
|
104
108
|
relation: RelationDef,
|
|
105
109
|
context: SchemaExtractionContext,
|
|
106
110
|
options: InputSchemaOptions
|
|
@@ -117,7 +121,11 @@ const extractInputRelationSchema = (
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
if (allowObjects) {
|
|
120
|
-
|
|
124
|
+
let targetSchema = extractInputSchema(relation.target, context, options);
|
|
125
|
+
targetSchema = applyRelationSelection(targetSchema, options.relationSelections?.[relationName]);
|
|
126
|
+
if (options.excludeRelationForeignKeys && isRelationForeignKeyToParent(relation)) {
|
|
127
|
+
targetSchema = removeForeignKey(targetSchema, relation.foreignKey);
|
|
128
|
+
}
|
|
121
129
|
variants.push(targetSchema as JsonSchemaProperty);
|
|
122
130
|
}
|
|
123
131
|
|
|
@@ -137,3 +145,49 @@ const extractInputRelationSchema = (
|
|
|
137
145
|
nullable: isNullable
|
|
138
146
|
};
|
|
139
147
|
};
|
|
148
|
+
|
|
149
|
+
const applyRelationSelection = (
|
|
150
|
+
schema: OpenApiSchema,
|
|
151
|
+
selection?: RelationSelection
|
|
152
|
+
): OpenApiSchema => {
|
|
153
|
+
if (!selection || (selection.pick === undefined && selection.omit === undefined)) {
|
|
154
|
+
return schema;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const hasPick = selection.pick !== undefined;
|
|
158
|
+
const pick = hasPick ? new Set(selection.pick ?? []) : undefined;
|
|
159
|
+
const omit = selection.omit !== undefined ? new Set(selection.omit ?? []) : undefined;
|
|
160
|
+
const properties = Object.entries(schema.properties).reduce<Record<string, JsonSchemaProperty>>(
|
|
161
|
+
(acc, [key, value]) => {
|
|
162
|
+
if (pick && !pick.has(key)) return acc;
|
|
163
|
+
if (omit && omit.has(key)) return acc;
|
|
164
|
+
acc[key] = value;
|
|
165
|
+
return acc;
|
|
166
|
+
},
|
|
167
|
+
{}
|
|
168
|
+
);
|
|
169
|
+
const required = schema.required.filter(name => properties[name] !== undefined);
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
...schema,
|
|
173
|
+
properties,
|
|
174
|
+
required
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const removeForeignKey = (schema: OpenApiSchema, foreignKey?: string): OpenApiSchema => {
|
|
179
|
+
if (!foreignKey || !schema.properties[foreignKey]) return schema;
|
|
180
|
+
const properties = { ...schema.properties };
|
|
181
|
+
delete properties[foreignKey];
|
|
182
|
+
const required = schema.required.filter(name => name !== foreignKey);
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
...schema,
|
|
186
|
+
properties,
|
|
187
|
+
required
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const isRelationForeignKeyToParent = (relation: RelationDef): relation is RelationDef & { foreignKey: string } => {
|
|
192
|
+
return relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne;
|
|
193
|
+
};
|
|
@@ -7,7 +7,8 @@ import type {
|
|
|
7
7
|
OpenApiSchemaBundle,
|
|
8
8
|
SchemaOptions,
|
|
9
9
|
OutputSchemaOptions,
|
|
10
|
-
InputSchemaOptions
|
|
10
|
+
InputSchemaOptions,
|
|
11
|
+
JsonSchemaProperty
|
|
11
12
|
} from './schema-types.js';
|
|
12
13
|
import { extractInputSchema } from './schema-extractor-input.js';
|
|
13
14
|
import { extractOutputSchema } from './schema-extractor-output.js';
|
|
@@ -36,15 +37,20 @@ export const extractSchema = (
|
|
|
36
37
|
if (outputOptions.refMode === 'components') {
|
|
37
38
|
outputContext.components = { schemas: {} };
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
+
const outputSchema = extractOutputSchema(table, plan, projectionNodes, outputContext, outputOptions);
|
|
41
|
+
let output: OpenApiSchema | JsonSchemaProperty = outputSchema;
|
|
40
42
|
const useSelected = shouldUseSelectedSchema(outputOptions, plan, projectionNodes);
|
|
41
43
|
const hasComputedFields = hasComputedProjection(projectionNodes);
|
|
44
|
+
const canUseComponents = outputOptions.refMode === 'components' && outputContext.components && !hasComputedFields;
|
|
42
45
|
|
|
43
|
-
if (
|
|
46
|
+
if (canUseComponents) {
|
|
44
47
|
const componentName = useSelected && plan
|
|
45
48
|
? resolveSelectedComponentName(table, plan, outputOptions)
|
|
46
49
|
: resolveComponentName(table, outputOptions);
|
|
47
|
-
registerComponentSchema(componentName,
|
|
50
|
+
registerComponentSchema(componentName, outputSchema, outputContext);
|
|
51
|
+
if (outputOptions.outputAsRef) {
|
|
52
|
+
output = { $ref: `#/components/schemas/${componentName}` };
|
|
53
|
+
}
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
const inputOptions = resolveInputOptions(options);
|
|
@@ -79,7 +85,8 @@ const resolveOutputOptions = (options: SchemaOptions): OutputSchemaOptions => ({
|
|
|
79
85
|
maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
80
86
|
refMode: options.refMode ?? 'inline',
|
|
81
87
|
selectedRefMode: options.selectedRefMode ?? 'inline',
|
|
82
|
-
componentName: options.componentName
|
|
88
|
+
componentName: options.componentName,
|
|
89
|
+
outputAsRef: options.outputAsRef ?? false
|
|
83
90
|
});
|
|
84
91
|
|
|
85
92
|
const resolveInputOptions = (options: SchemaOptions): InputSchemaOptions | undefined => {
|
|
@@ -99,7 +106,9 @@ const resolveInputOptions = (options: SchemaOptions): InputSchemaOptions | undef
|
|
|
99
106
|
maxDepth: input.maxDepth ?? options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
100
107
|
omitReadOnly: input.omitReadOnly ?? true,
|
|
101
108
|
excludePrimaryKey: input.excludePrimaryKey ?? false,
|
|
102
|
-
requirePrimaryKey: input.requirePrimaryKey ?? (mode === 'update')
|
|
109
|
+
requirePrimaryKey: input.requirePrimaryKey ?? (mode === 'update'),
|
|
110
|
+
excludeRelationForeignKeys: input.excludeRelationForeignKeys ?? false,
|
|
111
|
+
relationSelections: input.relationSelections
|
|
103
112
|
};
|
|
104
113
|
};
|
|
105
114
|
|
|
@@ -116,11 +116,18 @@ export interface OutputSchemaOptions extends ColumnSchemaOptions {
|
|
|
116
116
|
selectedRefMode?: 'inline' | 'components';
|
|
117
117
|
/** Customize component names */
|
|
118
118
|
componentName?: (table: TableDef) => string;
|
|
119
|
+
/** Emit output schema as a component $ref when refMode is components */
|
|
120
|
+
outputAsRef?: boolean;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
export type InputRelationMode = 'ids' | 'objects' | 'mixed';
|
|
122
124
|
export type InputSchemaMode = 'create' | 'update';
|
|
123
125
|
|
|
126
|
+
export interface RelationSelection {
|
|
127
|
+
pick?: string[];
|
|
128
|
+
omit?: string[];
|
|
129
|
+
}
|
|
130
|
+
|
|
124
131
|
/**
|
|
125
132
|
* Input schema generation options (write payloads)
|
|
126
133
|
*/
|
|
@@ -139,6 +146,10 @@ export interface InputSchemaOptions extends ColumnSchemaOptions {
|
|
|
139
146
|
excludePrimaryKey?: boolean;
|
|
140
147
|
/** Require primary key columns on update payloads */
|
|
141
148
|
requirePrimaryKey?: boolean;
|
|
149
|
+
/** Remove relation foreign keys pointing to the parent from nested inputs */
|
|
150
|
+
excludeRelationForeignKeys?: boolean;
|
|
151
|
+
/** Per-relation field selection for nested inputs */
|
|
152
|
+
relationSelections?: Record<string, RelationSelection>;
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
/**
|
|
@@ -153,7 +164,7 @@ export interface SchemaOptions extends OutputSchemaOptions {
|
|
|
153
164
|
* Input + output schema bundle
|
|
154
165
|
*/
|
|
155
166
|
export interface OpenApiSchemaBundle {
|
|
156
|
-
output: OpenApiSchema;
|
|
167
|
+
output: OpenApiSchema | JsonSchemaProperty;
|
|
157
168
|
input?: OpenApiSchema;
|
|
158
169
|
parameters?: OpenApiParameter[];
|
|
159
170
|
components?: OpenApiComponents;
|