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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.88",
3
+ "version": "1.0.89",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -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
- const targetSchema = extractInputSchema(relation.target, context, options);
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 output = extractOutputSchema(table, plan, projectionNodes, outputContext, outputOptions);
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 (outputOptions.refMode === 'components' && outputContext.components && !hasComputedFields) {
46
+ if (canUseComponents) {
44
47
  const componentName = useSelected && plan
45
48
  ? resolveSelectedComponentName(table, plan, outputOptions)
46
49
  : resolveComponentName(table, outputOptions);
47
- registerComponentSchema(componentName, output, outputContext);
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;