rads-db 3.2.23 → 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.
@@ -94,14 +94,174 @@ function buildObjectSchema(entity, schema, allOptional = false) {
94
94
  } : {})
95
95
  };
96
96
  }
97
- const whereSchema = {
98
- type: "object",
99
- additionalProperties: true
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
+ };
100
114
  };
101
- const includeSchema = {
102
- type: "object",
103
- additionalProperties: true
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
+ };
104
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
+ }
105
265
  const paginationProps = {
106
266
  cursor: {
107
267
  type: "string"
@@ -121,36 +281,6 @@ const paginationProps = {
121
281
  description: "Multiple order-by expressions"
122
282
  }
123
283
  };
124
- const getRequestSchema = {
125
- type: "object",
126
- properties: {
127
- where: whereSchema,
128
- include: includeSchema
129
- }
130
- };
131
- const getManyRequestSchema = {
132
- type: "object",
133
- properties: {
134
- where: whereSchema,
135
- include: includeSchema,
136
- ...paginationProps
137
- }
138
- };
139
- const getAggRequestSchema = {
140
- type: "object",
141
- required: ["agg"],
142
- properties: {
143
- where: whereSchema,
144
- agg: {
145
- type: "array",
146
- items: {
147
- type: "string"
148
- },
149
- description: 'Aggregation operations: "_count", "field_min", "field_max", "field_sum"'
150
- },
151
- ...paginationProps
152
- }
153
- };
154
284
  function jsonBody(s) {
155
285
  return {
156
286
  required: true,
@@ -182,6 +312,22 @@ function getOpenApiSpec(options, openApiOptions) {
182
312
  const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
183
313
  const paths = {};
184
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
+ };
185
331
  paths[`${base}/me`] = {
186
332
  get: {
187
333
  summary: "Get current user",
@@ -260,17 +406,52 @@ function getOpenApiSpec(options, openApiOptions) {
260
406
  handlePlural
261
407
  } = entity;
262
408
  const tag = name;
263
- schemas[name] = buildObjectSchema(entity, schema);
264
- schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
265
- schemas[`${name}Put`].required = ["id"];
266
409
  const ref = n => ({
267
410
  $ref: `#/components/schemas/${n}`
268
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
+ };
269
450
  paths[`${base}/${handle}`] = {
270
451
  post: {
271
452
  summary: `Get ${name}`,
272
453
  tags: [tag],
273
- requestBody: jsonBody(getRequestSchema),
454
+ requestBody: jsonBody(getReqBody),
274
455
  responses: jsonOk(ref(name), `${name} object`)
275
456
  },
276
457
  put: {
@@ -290,7 +471,7 @@ function getOpenApiSpec(options, openApiOptions) {
290
471
  post: {
291
472
  summary: `Get many ${name} objects`,
292
473
  tags: [tag],
293
- requestBody: jsonBody(getManyRequestSchema),
474
+ requestBody: jsonBody(getManyReqBody),
294
475
  responses: jsonOk({
295
476
  type: "object",
296
477
  required: ["nodes"],
@@ -329,7 +510,7 @@ function getOpenApiSpec(options, openApiOptions) {
329
510
  post: {
330
511
  summary: `Aggregate ${name} data`,
331
512
  tags: [tag],
332
- requestBody: jsonBody(getAggRequestSchema),
513
+ requestBody: jsonBody(getAggReqBody),
333
514
  responses: jsonOk({
334
515
  type: "object",
335
516
  additionalProperties: {
@@ -342,7 +523,7 @@ function getOpenApiSpec(options, openApiOptions) {
342
523
  }
343
524
  for (const key in schema) {
344
525
  const entry = schema[key];
345
- if (entry.decorators?.entity || schemas[key]) continue;
526
+ if (entry.decorators?.entity) continue;
346
527
  if (entry.enumValues) {
347
528
  schemas[key] = {
348
529
  type: "string",
@@ -352,7 +533,9 @@ function getOpenApiSpec(options, openApiOptions) {
352
533
  } : {})
353
534
  };
354
535
  } else if (entry.fields) {
355
- 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);
356
539
  }
357
540
  }
358
541
  return {
@@ -52,35 +52,119 @@ function buildObjectSchema(entity, schema, allOptional = false) {
52
52
  ...required.length > 0 ? { required } : {}
53
53
  };
54
54
  }
55
- const whereSchema = { type: "object", additionalProperties: true };
56
- const includeSchema = { type: "object", additionalProperties: true };
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
+ }
57
162
  const paginationProps = {
58
163
  cursor: { type: "string" },
59
164
  maxItemCount: { type: "integer" },
60
165
  orderBy: { type: "string", description: 'Single order-by expression, e.g. "name_asc"' },
61
166
  orderByArray: { type: "array", items: { type: "string" }, description: "Multiple order-by expressions" }
62
167
  };
63
- const getRequestSchema = {
64
- type: "object",
65
- properties: { where: whereSchema, include: includeSchema }
66
- };
67
- const getManyRequestSchema = {
68
- type: "object",
69
- properties: { where: whereSchema, include: includeSchema, ...paginationProps }
70
- };
71
- const getAggRequestSchema = {
72
- type: "object",
73
- required: ["agg"],
74
- properties: {
75
- where: whereSchema,
76
- agg: {
77
- type: "array",
78
- items: { type: "string" },
79
- description: 'Aggregation operations: "_count", "field_min", "field_max", "field_sum"'
80
- },
81
- ...paginationProps
82
- }
83
- };
84
168
  function jsonBody(s) {
85
169
  return { required: true, content: { "application/json": { schema: s } } };
86
170
  }
@@ -93,6 +177,15 @@ export function getOpenApiSpec(options, openApiOptions) {
93
177
  const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
94
178
  const paths = {};
95
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
+ };
96
189
  paths[`${base}/me`] = {
97
190
  get: {
98
191
  summary: "Get current user",
@@ -143,15 +236,36 @@ export function getOpenApiSpec(options, openApiOptions) {
143
236
  if (!entity.decorators?.entity) continue;
144
237
  const { name, handle, handlePlural } = entity;
145
238
  const tag = name;
239
+ const ref = (n) => ({ $ref: `#/components/schemas/${n}` });
146
240
  schemas[name] = buildObjectSchema(entity, schema);
147
241
  schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
148
242
  schemas[`${name}Put`].required = ["id"];
149
- const ref = (n) => ({ $ref: `#/components/schemas/${n}` });
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
+ };
150
264
  paths[`${base}/${handle}`] = {
151
265
  post: {
152
266
  summary: `Get ${name}`,
153
267
  tags: [tag],
154
- requestBody: jsonBody(getRequestSchema),
268
+ requestBody: jsonBody(getReqBody),
155
269
  responses: jsonOk(ref(name), `${name} object`)
156
270
  },
157
271
  put: {
@@ -169,7 +283,7 @@ export function getOpenApiSpec(options, openApiOptions) {
169
283
  post: {
170
284
  summary: `Get many ${name} objects`,
171
285
  tags: [tag],
172
- requestBody: jsonBody(getManyRequestSchema),
286
+ requestBody: jsonBody(getManyReqBody),
173
287
  responses: jsonOk(
174
288
  {
175
289
  type: "object",
@@ -197,7 +311,7 @@ export function getOpenApiSpec(options, openApiOptions) {
197
311
  post: {
198
312
  summary: `Aggregate ${name} data`,
199
313
  tags: [tag],
200
- requestBody: jsonBody(getAggRequestSchema),
314
+ requestBody: jsonBody(getAggReqBody),
201
315
  responses: jsonOk(
202
316
  { type: "object", additionalProperties: { type: "number", nullable: true } },
203
317
  `${name} aggregation result`
@@ -207,7 +321,7 @@ export function getOpenApiSpec(options, openApiOptions) {
207
321
  }
208
322
  for (const key in schema) {
209
323
  const entry = schema[key];
210
- if (entry.decorators?.entity || schemas[key]) continue;
324
+ if (entry.decorators?.entity) continue;
211
325
  if (entry.enumValues) {
212
326
  schemas[key] = {
213
327
  type: "string",
@@ -215,7 +329,9 @@ export function getOpenApiSpec(options, openApiOptions) {
215
329
  ...entry.comment ? { description: entry.comment } : {}
216
330
  };
217
331
  } else if (entry.fields) {
218
- 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);
219
335
  }
220
336
  }
221
337
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rads-db",
3
- "version": "3.2.23",
3
+ "version": "3.2.24",
4
4
  "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
5
5
  "author": "",
6
6
  "license": "ISC",