rads-db 3.2.22 → 3.2.24
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/integrations/getOpenApiSpec.cjs +246 -45
- package/integrations/getOpenApiSpec.mjs +156 -30
- package/package.json +1 -1
|
@@ -34,10 +34,28 @@ function primitiveToOas(type) {
|
|
|
34
34
|
return {};
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
function relationToOas(field, schema) {
|
|
38
|
+
const properties = {
|
|
39
|
+
id: {
|
|
40
|
+
type: "string"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
for (const fname of field.relationDenormFields ?? []) {
|
|
44
|
+
const relField = schema[field.type]?.fields?.[fname];
|
|
45
|
+
properties[fname] = relField ? primitiveToOas(relField.type) : {};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
type: "object",
|
|
49
|
+
required: ["id"],
|
|
50
|
+
properties
|
|
51
|
+
};
|
|
52
|
+
}
|
|
37
53
|
function fieldToOas(field, schema) {
|
|
38
54
|
const entry = schema[field.type];
|
|
39
55
|
let itemSchema;
|
|
40
|
-
if (
|
|
56
|
+
if (field.isRelation) {
|
|
57
|
+
itemSchema = relationToOas(field, schema);
|
|
58
|
+
} else if (entry) {
|
|
41
59
|
itemSchema = entry.enumValues ? {
|
|
42
60
|
type: "string",
|
|
43
61
|
enum: Object.keys(entry.enumValues)
|
|
@@ -76,14 +94,174 @@ function buildObjectSchema(entity, schema, allOptional = false) {
|
|
|
76
94
|
} : {})
|
|
77
95
|
};
|
|
78
96
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
const stringOps = name => {
|
|
98
|
+
const s = {
|
|
99
|
+
type: "string"
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
[`${name}_ieq`]: s,
|
|
103
|
+
[`${name}_istartsWith`]: s,
|
|
104
|
+
[`${name}_startsWith`]: s,
|
|
105
|
+
[`${name}_icontains`]: s,
|
|
106
|
+
[`${name}_contains`]: s,
|
|
107
|
+
[`${name}_iendsWith`]: s,
|
|
108
|
+
[`${name}_endsWith`]: s,
|
|
109
|
+
[`${name}_gt`]: s,
|
|
110
|
+
[`${name}_gte`]: s,
|
|
111
|
+
[`${name}_lt`]: s,
|
|
112
|
+
[`${name}_lte`]: s
|
|
113
|
+
};
|
|
82
114
|
};
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
const numberOps = name => {
|
|
116
|
+
const n = {
|
|
117
|
+
type: "number"
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
[`${name}_gt`]: n,
|
|
121
|
+
[`${name}_gte`]: n,
|
|
122
|
+
[`${name}_lt`]: n,
|
|
123
|
+
[`${name}_lte`]: n
|
|
124
|
+
};
|
|
86
125
|
};
|
|
126
|
+
const relationWhereOas = () => ({
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
id: {
|
|
130
|
+
type: "string"
|
|
131
|
+
},
|
|
132
|
+
id_in: {
|
|
133
|
+
type: "array",
|
|
134
|
+
items: {
|
|
135
|
+
type: "string"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
function addPrimitiveWhereProps(props, name, type) {
|
|
141
|
+
props[name] = primitiveToOas(type);
|
|
142
|
+
if (type !== "boolean") props[`${name}_in`] = {
|
|
143
|
+
type: "array",
|
|
144
|
+
items: primitiveToOas(type)
|
|
145
|
+
};
|
|
146
|
+
if (type === "string") Object.assign(props, stringOps(name));else if (type === "number") Object.assign(props, numberOps(name));else if (type === "Record<string, any>") props[`${name}_jsonContains`] = {
|
|
147
|
+
$ref: "#/components/schemas/WhereJsonContains"
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function buildWhereProps(typeDef, schema) {
|
|
151
|
+
const props = {};
|
|
152
|
+
for (const [, field] of Object.entries(typeDef.fields ?? {})) {
|
|
153
|
+
if (field.isInverseRelation) continue;
|
|
154
|
+
const {
|
|
155
|
+
name,
|
|
156
|
+
type,
|
|
157
|
+
isRelation,
|
|
158
|
+
isRequired,
|
|
159
|
+
isArray
|
|
160
|
+
} = field;
|
|
161
|
+
const entry = schema[type];
|
|
162
|
+
const isPrimitive = !entry;
|
|
163
|
+
const isEnum = !!entry?.enumValues;
|
|
164
|
+
const isObject = !!entry?.fields;
|
|
165
|
+
const optional = !isRequired;
|
|
166
|
+
const enumSchema = isEnum ? {
|
|
167
|
+
type: "string",
|
|
168
|
+
enum: Object.keys(entry.enumValues)
|
|
169
|
+
} : {};
|
|
170
|
+
if (isArray) {
|
|
171
|
+
if (isPrimitive || isEnum) props[`${name}_arrayContains`] = isEnum ? enumSchema : primitiveToOas(type);else if (isRelation) props[`${name}_some`] = relationWhereOas();else if (isObject) props[`${name}_some`] = {
|
|
172
|
+
$ref: `#/components/schemas/${type}Where`
|
|
173
|
+
};
|
|
174
|
+
props[`${name}_isEmpty`] = {
|
|
175
|
+
type: "boolean"
|
|
176
|
+
};
|
|
177
|
+
if (optional) props[`${name}_isNull`] = {
|
|
178
|
+
type: "boolean"
|
|
179
|
+
};
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (isRelation) {
|
|
183
|
+
props[name] = relationWhereOas();
|
|
184
|
+
} else if (isObject) {
|
|
185
|
+
props[name] = {
|
|
186
|
+
$ref: `#/components/schemas/${type}Where`
|
|
187
|
+
};
|
|
188
|
+
} else if (isEnum) {
|
|
189
|
+
props[name] = enumSchema;
|
|
190
|
+
props[`${name}_in`] = {
|
|
191
|
+
type: "array",
|
|
192
|
+
items: enumSchema
|
|
193
|
+
};
|
|
194
|
+
Object.assign(props, stringOps(name));
|
|
195
|
+
} else {
|
|
196
|
+
addPrimitiveWhereProps(props, name, type);
|
|
197
|
+
}
|
|
198
|
+
if (optional) props[`${name}_isNull`] = {
|
|
199
|
+
type: "boolean"
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return props;
|
|
203
|
+
}
|
|
204
|
+
function buildWhereSchema(typeName, typeDef, schema) {
|
|
205
|
+
const selfRef = `#/components/schemas/${typeName}Where`;
|
|
206
|
+
return {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
_not: {
|
|
210
|
+
$ref: selfRef
|
|
211
|
+
},
|
|
212
|
+
_and: {
|
|
213
|
+
type: "array",
|
|
214
|
+
items: {
|
|
215
|
+
$ref: selfRef
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
_or: {
|
|
219
|
+
type: "array",
|
|
220
|
+
items: {
|
|
221
|
+
$ref: selfRef
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
...buildWhereProps(typeDef, schema)
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function buildIncludeSchema(typeDef, schema) {
|
|
229
|
+
const primitiveFields = [];
|
|
230
|
+
const includeProps = {};
|
|
231
|
+
for (const [, field] of Object.entries(typeDef.fields ?? {})) {
|
|
232
|
+
if (field.isInverseRelation) continue;
|
|
233
|
+
const entry = schema[field.type];
|
|
234
|
+
const canSubInclude = field.isRelation || entry?.fields && !entry.enumValues;
|
|
235
|
+
if (canSubInclude) {
|
|
236
|
+
includeProps[field.name] = {
|
|
237
|
+
$ref: `#/components/schemas/${field.type}Include`
|
|
238
|
+
};
|
|
239
|
+
} else {
|
|
240
|
+
primitiveFields.push(field.name);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
_pick: {
|
|
247
|
+
type: "array",
|
|
248
|
+
items: {
|
|
249
|
+
type: "string",
|
|
250
|
+
enum: primitiveFields
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
...includeProps
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function buildAggEnum(typeDef) {
|
|
258
|
+
const values = ["_count"];
|
|
259
|
+
for (const [, field] of Object.entries(typeDef.fields ?? {})) {
|
|
260
|
+
if (field.isInverseRelation || field.isArray || field.isRelation) continue;
|
|
261
|
+
if (field.type === "number") values.push(`${field.name}_min`, `${field.name}_max`, `${field.name}_sum`);
|
|
262
|
+
}
|
|
263
|
+
return values;
|
|
264
|
+
}
|
|
87
265
|
const paginationProps = {
|
|
88
266
|
cursor: {
|
|
89
267
|
type: "string"
|
|
@@ -103,36 +281,6 @@ const paginationProps = {
|
|
|
103
281
|
description: "Multiple order-by expressions"
|
|
104
282
|
}
|
|
105
283
|
};
|
|
106
|
-
const getRequestSchema = {
|
|
107
|
-
type: "object",
|
|
108
|
-
properties: {
|
|
109
|
-
where: whereSchema,
|
|
110
|
-
include: includeSchema
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
const getManyRequestSchema = {
|
|
114
|
-
type: "object",
|
|
115
|
-
properties: {
|
|
116
|
-
where: whereSchema,
|
|
117
|
-
include: includeSchema,
|
|
118
|
-
...paginationProps
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
const getAggRequestSchema = {
|
|
122
|
-
type: "object",
|
|
123
|
-
required: ["agg"],
|
|
124
|
-
properties: {
|
|
125
|
-
where: whereSchema,
|
|
126
|
-
agg: {
|
|
127
|
-
type: "array",
|
|
128
|
-
items: {
|
|
129
|
-
type: "string"
|
|
130
|
-
},
|
|
131
|
-
description: 'Aggregation operations: "_count", "field_min", "field_max", "field_sum"'
|
|
132
|
-
},
|
|
133
|
-
...paginationProps
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
284
|
function jsonBody(s) {
|
|
137
285
|
return {
|
|
138
286
|
required: true,
|
|
@@ -164,6 +312,22 @@ function getOpenApiSpec(options, openApiOptions) {
|
|
|
164
312
|
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
165
313
|
const paths = {};
|
|
166
314
|
const schemas = {};
|
|
315
|
+
schemas.WhereJsonContains = {
|
|
316
|
+
type: "object",
|
|
317
|
+
required: ["path"],
|
|
318
|
+
properties: {
|
|
319
|
+
path: {
|
|
320
|
+
type: "string",
|
|
321
|
+
description: 'JSON path, e.g. "meta.countries[0].name"'
|
|
322
|
+
},
|
|
323
|
+
isNull: {
|
|
324
|
+
type: "boolean"
|
|
325
|
+
},
|
|
326
|
+
value: {
|
|
327
|
+
description: "Expected value at path (string, number, or boolean)"
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
167
331
|
paths[`${base}/me`] = {
|
|
168
332
|
get: {
|
|
169
333
|
summary: "Get current user",
|
|
@@ -242,17 +406,52 @@ function getOpenApiSpec(options, openApiOptions) {
|
|
|
242
406
|
handlePlural
|
|
243
407
|
} = entity;
|
|
244
408
|
const tag = name;
|
|
245
|
-
schemas[name] = buildObjectSchema(entity, schema);
|
|
246
|
-
schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
|
|
247
|
-
schemas[`${name}Put`].required = ["id"];
|
|
248
409
|
const ref = n => ({
|
|
249
410
|
$ref: `#/components/schemas/${n}`
|
|
250
411
|
});
|
|
412
|
+
schemas[name] = buildObjectSchema(entity, schema);
|
|
413
|
+
schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
|
|
414
|
+
schemas[`${name}Put`].required = ["id"];
|
|
415
|
+
schemas[`${name}Where`] = buildWhereSchema(name, entity, schema);
|
|
416
|
+
schemas[`${name}Include`] = buildIncludeSchema(entity, schema);
|
|
417
|
+
const aggValues = buildAggEnum(entity);
|
|
418
|
+
const aggSchema = {
|
|
419
|
+
type: "array",
|
|
420
|
+
items: {
|
|
421
|
+
type: "string",
|
|
422
|
+
enum: aggValues
|
|
423
|
+
},
|
|
424
|
+
description: "Aggregation operations to compute"
|
|
425
|
+
};
|
|
426
|
+
const getReqBody = {
|
|
427
|
+
type: "object",
|
|
428
|
+
properties: {
|
|
429
|
+
where: ref(`${name}Where`),
|
|
430
|
+
include: ref(`${name}Include`)
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const getManyReqBody = {
|
|
434
|
+
type: "object",
|
|
435
|
+
properties: {
|
|
436
|
+
where: ref(`${name}Where`),
|
|
437
|
+
include: ref(`${name}Include`),
|
|
438
|
+
...paginationProps
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
const getAggReqBody = {
|
|
442
|
+
type: "object",
|
|
443
|
+
required: ["agg"],
|
|
444
|
+
properties: {
|
|
445
|
+
where: ref(`${name}Where`),
|
|
446
|
+
agg: aggSchema,
|
|
447
|
+
...paginationProps
|
|
448
|
+
}
|
|
449
|
+
};
|
|
251
450
|
paths[`${base}/${handle}`] = {
|
|
252
451
|
post: {
|
|
253
452
|
summary: `Get ${name}`,
|
|
254
453
|
tags: [tag],
|
|
255
|
-
requestBody: jsonBody(
|
|
454
|
+
requestBody: jsonBody(getReqBody),
|
|
256
455
|
responses: jsonOk(ref(name), `${name} object`)
|
|
257
456
|
},
|
|
258
457
|
put: {
|
|
@@ -272,7 +471,7 @@ function getOpenApiSpec(options, openApiOptions) {
|
|
|
272
471
|
post: {
|
|
273
472
|
summary: `Get many ${name} objects`,
|
|
274
473
|
tags: [tag],
|
|
275
|
-
requestBody: jsonBody(
|
|
474
|
+
requestBody: jsonBody(getManyReqBody),
|
|
276
475
|
responses: jsonOk({
|
|
277
476
|
type: "object",
|
|
278
477
|
required: ["nodes"],
|
|
@@ -311,7 +510,7 @@ function getOpenApiSpec(options, openApiOptions) {
|
|
|
311
510
|
post: {
|
|
312
511
|
summary: `Aggregate ${name} data`,
|
|
313
512
|
tags: [tag],
|
|
314
|
-
requestBody: jsonBody(
|
|
513
|
+
requestBody: jsonBody(getAggReqBody),
|
|
315
514
|
responses: jsonOk({
|
|
316
515
|
type: "object",
|
|
317
516
|
additionalProperties: {
|
|
@@ -324,7 +523,7 @@ function getOpenApiSpec(options, openApiOptions) {
|
|
|
324
523
|
}
|
|
325
524
|
for (const key in schema) {
|
|
326
525
|
const entry = schema[key];
|
|
327
|
-
if (entry.decorators?.entity
|
|
526
|
+
if (entry.decorators?.entity) continue;
|
|
328
527
|
if (entry.enumValues) {
|
|
329
528
|
schemas[key] = {
|
|
330
529
|
type: "string",
|
|
@@ -334,7 +533,9 @@ function getOpenApiSpec(options, openApiOptions) {
|
|
|
334
533
|
} : {})
|
|
335
534
|
};
|
|
336
535
|
} else if (entry.fields) {
|
|
337
|
-
schemas[key] = buildObjectSchema(entry, schema);
|
|
536
|
+
if (!schemas[key]) schemas[key] = buildObjectSchema(entry, schema);
|
|
537
|
+
schemas[`${key}Where`] = buildWhereSchema(key, entry, schema);
|
|
538
|
+
schemas[`${key}Include`] = buildIncludeSchema(entry, schema);
|
|
338
539
|
}
|
|
339
540
|
}
|
|
340
541
|
return {
|
|
@@ -14,10 +14,20 @@ function primitiveToOas(type) {
|
|
|
14
14
|
return {};
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
function relationToOas(field, schema) {
|
|
18
|
+
const properties = { id: { type: "string" } };
|
|
19
|
+
for (const fname of field.relationDenormFields ?? []) {
|
|
20
|
+
const relField = schema[field.type]?.fields?.[fname];
|
|
21
|
+
properties[fname] = relField ? primitiveToOas(relField.type) : {};
|
|
22
|
+
}
|
|
23
|
+
return { type: "object", required: ["id"], properties };
|
|
24
|
+
}
|
|
17
25
|
function fieldToOas(field, schema) {
|
|
18
26
|
const entry = schema[field.type];
|
|
19
27
|
let itemSchema;
|
|
20
|
-
if (
|
|
28
|
+
if (field.isRelation) {
|
|
29
|
+
itemSchema = relationToOas(field, schema);
|
|
30
|
+
} else if (entry) {
|
|
21
31
|
itemSchema = entry.enumValues ? { type: "string", enum: Object.keys(entry.enumValues) } : { $ref: `#/components/schemas/${field.type}` };
|
|
22
32
|
} else {
|
|
23
33
|
itemSchema = primitiveToOas(field.type);
|
|
@@ -42,35 +52,119 @@ function buildObjectSchema(entity, schema, allOptional = false) {
|
|
|
42
52
|
...required.length > 0 ? { required } : {}
|
|
43
53
|
};
|
|
44
54
|
}
|
|
45
|
-
const
|
|
46
|
-
const
|
|
55
|
+
const stringOps = (name) => {
|
|
56
|
+
const s = { type: "string" };
|
|
57
|
+
return {
|
|
58
|
+
[`${name}_ieq`]: s,
|
|
59
|
+
[`${name}_istartsWith`]: s,
|
|
60
|
+
[`${name}_startsWith`]: s,
|
|
61
|
+
[`${name}_icontains`]: s,
|
|
62
|
+
[`${name}_contains`]: s,
|
|
63
|
+
[`${name}_iendsWith`]: s,
|
|
64
|
+
[`${name}_endsWith`]: s,
|
|
65
|
+
[`${name}_gt`]: s,
|
|
66
|
+
[`${name}_gte`]: s,
|
|
67
|
+
[`${name}_lt`]: s,
|
|
68
|
+
[`${name}_lte`]: s
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const numberOps = (name) => {
|
|
72
|
+
const n = { type: "number" };
|
|
73
|
+
return { [`${name}_gt`]: n, [`${name}_gte`]: n, [`${name}_lt`]: n, [`${name}_lte`]: n };
|
|
74
|
+
};
|
|
75
|
+
const relationWhereOas = () => ({
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: { id: { type: "string" }, id_in: { type: "array", items: { type: "string" } } }
|
|
78
|
+
});
|
|
79
|
+
function addPrimitiveWhereProps(props, name, type) {
|
|
80
|
+
props[name] = primitiveToOas(type);
|
|
81
|
+
if (type !== "boolean") props[`${name}_in`] = { type: "array", items: primitiveToOas(type) };
|
|
82
|
+
if (type === "string") Object.assign(props, stringOps(name));
|
|
83
|
+
else if (type === "number") Object.assign(props, numberOps(name));
|
|
84
|
+
else if (type === "Record<string, any>")
|
|
85
|
+
props[`${name}_jsonContains`] = { $ref: "#/components/schemas/WhereJsonContains" };
|
|
86
|
+
}
|
|
87
|
+
function buildWhereProps(typeDef, schema) {
|
|
88
|
+
const props = {};
|
|
89
|
+
for (const [, field] of Object.entries(typeDef.fields ?? {})) {
|
|
90
|
+
if (field.isInverseRelation) continue;
|
|
91
|
+
const { name, type, isRelation, isRequired, isArray } = field;
|
|
92
|
+
const entry = schema[type];
|
|
93
|
+
const isPrimitive = !entry;
|
|
94
|
+
const isEnum = !!entry?.enumValues;
|
|
95
|
+
const isObject = !!entry?.fields;
|
|
96
|
+
const optional = !isRequired;
|
|
97
|
+
const enumSchema = isEnum ? { type: "string", enum: Object.keys(entry.enumValues) } : {};
|
|
98
|
+
if (isArray) {
|
|
99
|
+
if (isPrimitive || isEnum) props[`${name}_arrayContains`] = isEnum ? enumSchema : primitiveToOas(type);
|
|
100
|
+
else if (isRelation) props[`${name}_some`] = relationWhereOas();
|
|
101
|
+
else if (isObject) props[`${name}_some`] = { $ref: `#/components/schemas/${type}Where` };
|
|
102
|
+
props[`${name}_isEmpty`] = { type: "boolean" };
|
|
103
|
+
if (optional) props[`${name}_isNull`] = { type: "boolean" };
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (isRelation) {
|
|
107
|
+
props[name] = relationWhereOas();
|
|
108
|
+
} else if (isObject) {
|
|
109
|
+
props[name] = { $ref: `#/components/schemas/${type}Where` };
|
|
110
|
+
} else if (isEnum) {
|
|
111
|
+
props[name] = enumSchema;
|
|
112
|
+
props[`${name}_in`] = { type: "array", items: enumSchema };
|
|
113
|
+
Object.assign(props, stringOps(name));
|
|
114
|
+
} else {
|
|
115
|
+
addPrimitiveWhereProps(props, name, type);
|
|
116
|
+
}
|
|
117
|
+
if (optional) props[`${name}_isNull`] = { type: "boolean" };
|
|
118
|
+
}
|
|
119
|
+
return props;
|
|
120
|
+
}
|
|
121
|
+
function buildWhereSchema(typeName, typeDef, schema) {
|
|
122
|
+
const selfRef = `#/components/schemas/${typeName}Where`;
|
|
123
|
+
return {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
_not: { $ref: selfRef },
|
|
127
|
+
_and: { type: "array", items: { $ref: selfRef } },
|
|
128
|
+
_or: { type: "array", items: { $ref: selfRef } },
|
|
129
|
+
...buildWhereProps(typeDef, schema)
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function buildIncludeSchema(typeDef, schema) {
|
|
134
|
+
const primitiveFields = [];
|
|
135
|
+
const includeProps = {};
|
|
136
|
+
for (const [, field] of Object.entries(typeDef.fields ?? {})) {
|
|
137
|
+
if (field.isInverseRelation) continue;
|
|
138
|
+
const entry = schema[field.type];
|
|
139
|
+
const canSubInclude = field.isRelation || entry?.fields && !entry.enumValues;
|
|
140
|
+
if (canSubInclude) {
|
|
141
|
+
includeProps[field.name] = { $ref: `#/components/schemas/${field.type}Include` };
|
|
142
|
+
} else {
|
|
143
|
+
primitiveFields.push(field.name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
_pick: { type: "array", items: { type: "string", enum: primitiveFields } },
|
|
150
|
+
...includeProps
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function buildAggEnum(typeDef) {
|
|
155
|
+
const values = ["_count"];
|
|
156
|
+
for (const [, field] of Object.entries(typeDef.fields ?? {})) {
|
|
157
|
+
if (field.isInverseRelation || field.isArray || field.isRelation) continue;
|
|
158
|
+
if (field.type === "number") values.push(`${field.name}_min`, `${field.name}_max`, `${field.name}_sum`);
|
|
159
|
+
}
|
|
160
|
+
return values;
|
|
161
|
+
}
|
|
47
162
|
const paginationProps = {
|
|
48
163
|
cursor: { type: "string" },
|
|
49
164
|
maxItemCount: { type: "integer" },
|
|
50
165
|
orderBy: { type: "string", description: 'Single order-by expression, e.g. "name_asc"' },
|
|
51
166
|
orderByArray: { type: "array", items: { type: "string" }, description: "Multiple order-by expressions" }
|
|
52
167
|
};
|
|
53
|
-
const getRequestSchema = {
|
|
54
|
-
type: "object",
|
|
55
|
-
properties: { where: whereSchema, include: includeSchema }
|
|
56
|
-
};
|
|
57
|
-
const getManyRequestSchema = {
|
|
58
|
-
type: "object",
|
|
59
|
-
properties: { where: whereSchema, include: includeSchema, ...paginationProps }
|
|
60
|
-
};
|
|
61
|
-
const getAggRequestSchema = {
|
|
62
|
-
type: "object",
|
|
63
|
-
required: ["agg"],
|
|
64
|
-
properties: {
|
|
65
|
-
where: whereSchema,
|
|
66
|
-
agg: {
|
|
67
|
-
type: "array",
|
|
68
|
-
items: { type: "string" },
|
|
69
|
-
description: 'Aggregation operations: "_count", "field_min", "field_max", "field_sum"'
|
|
70
|
-
},
|
|
71
|
-
...paginationProps
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
168
|
function jsonBody(s) {
|
|
75
169
|
return { required: true, content: { "application/json": { schema: s } } };
|
|
76
170
|
}
|
|
@@ -83,6 +177,15 @@ export function getOpenApiSpec(options, openApiOptions) {
|
|
|
83
177
|
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
84
178
|
const paths = {};
|
|
85
179
|
const schemas = {};
|
|
180
|
+
schemas.WhereJsonContains = {
|
|
181
|
+
type: "object",
|
|
182
|
+
required: ["path"],
|
|
183
|
+
properties: {
|
|
184
|
+
path: { type: "string", description: 'JSON path, e.g. "meta.countries[0].name"' },
|
|
185
|
+
isNull: { type: "boolean" },
|
|
186
|
+
value: { description: "Expected value at path (string, number, or boolean)" }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
86
189
|
paths[`${base}/me`] = {
|
|
87
190
|
get: {
|
|
88
191
|
summary: "Get current user",
|
|
@@ -133,15 +236,36 @@ export function getOpenApiSpec(options, openApiOptions) {
|
|
|
133
236
|
if (!entity.decorators?.entity) continue;
|
|
134
237
|
const { name, handle, handlePlural } = entity;
|
|
135
238
|
const tag = name;
|
|
239
|
+
const ref = (n) => ({ $ref: `#/components/schemas/${n}` });
|
|
136
240
|
schemas[name] = buildObjectSchema(entity, schema);
|
|
137
241
|
schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
|
|
138
242
|
schemas[`${name}Put`].required = ["id"];
|
|
139
|
-
|
|
243
|
+
schemas[`${name}Where`] = buildWhereSchema(name, entity, schema);
|
|
244
|
+
schemas[`${name}Include`] = buildIncludeSchema(entity, schema);
|
|
245
|
+
const aggValues = buildAggEnum(entity);
|
|
246
|
+
const aggSchema = {
|
|
247
|
+
type: "array",
|
|
248
|
+
items: { type: "string", enum: aggValues },
|
|
249
|
+
description: "Aggregation operations to compute"
|
|
250
|
+
};
|
|
251
|
+
const getReqBody = {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: { where: ref(`${name}Where`), include: ref(`${name}Include`) }
|
|
254
|
+
};
|
|
255
|
+
const getManyReqBody = {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: { where: ref(`${name}Where`), include: ref(`${name}Include`), ...paginationProps }
|
|
258
|
+
};
|
|
259
|
+
const getAggReqBody = {
|
|
260
|
+
type: "object",
|
|
261
|
+
required: ["agg"],
|
|
262
|
+
properties: { where: ref(`${name}Where`), agg: aggSchema, ...paginationProps }
|
|
263
|
+
};
|
|
140
264
|
paths[`${base}/${handle}`] = {
|
|
141
265
|
post: {
|
|
142
266
|
summary: `Get ${name}`,
|
|
143
267
|
tags: [tag],
|
|
144
|
-
requestBody: jsonBody(
|
|
268
|
+
requestBody: jsonBody(getReqBody),
|
|
145
269
|
responses: jsonOk(ref(name), `${name} object`)
|
|
146
270
|
},
|
|
147
271
|
put: {
|
|
@@ -159,7 +283,7 @@ export function getOpenApiSpec(options, openApiOptions) {
|
|
|
159
283
|
post: {
|
|
160
284
|
summary: `Get many ${name} objects`,
|
|
161
285
|
tags: [tag],
|
|
162
|
-
requestBody: jsonBody(
|
|
286
|
+
requestBody: jsonBody(getManyReqBody),
|
|
163
287
|
responses: jsonOk(
|
|
164
288
|
{
|
|
165
289
|
type: "object",
|
|
@@ -187,7 +311,7 @@ export function getOpenApiSpec(options, openApiOptions) {
|
|
|
187
311
|
post: {
|
|
188
312
|
summary: `Aggregate ${name} data`,
|
|
189
313
|
tags: [tag],
|
|
190
|
-
requestBody: jsonBody(
|
|
314
|
+
requestBody: jsonBody(getAggReqBody),
|
|
191
315
|
responses: jsonOk(
|
|
192
316
|
{ type: "object", additionalProperties: { type: "number", nullable: true } },
|
|
193
317
|
`${name} aggregation result`
|
|
@@ -197,7 +321,7 @@ export function getOpenApiSpec(options, openApiOptions) {
|
|
|
197
321
|
}
|
|
198
322
|
for (const key in schema) {
|
|
199
323
|
const entry = schema[key];
|
|
200
|
-
if (entry.decorators?.entity
|
|
324
|
+
if (entry.decorators?.entity) continue;
|
|
201
325
|
if (entry.enumValues) {
|
|
202
326
|
schemas[key] = {
|
|
203
327
|
type: "string",
|
|
@@ -205,7 +329,9 @@ export function getOpenApiSpec(options, openApiOptions) {
|
|
|
205
329
|
...entry.comment ? { description: entry.comment } : {}
|
|
206
330
|
};
|
|
207
331
|
} else if (entry.fields) {
|
|
208
|
-
schemas[key] = buildObjectSchema(entry, schema);
|
|
332
|
+
if (!schemas[key]) schemas[key] = buildObjectSchema(entry, schema);
|
|
333
|
+
schemas[`${key}Where`] = buildWhereSchema(key, entry, schema);
|
|
334
|
+
schemas[`${key}Include`] = buildIncludeSchema(entry, schema);
|
|
209
335
|
}
|
|
210
336
|
}
|
|
211
337
|
return {
|