@nhtio/lucid-resourceful 0.1.0-master-71781cfd → 0.1.0-master-3ec631a4
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/{definitions-DgI468dW.cjs → data_type_schemas-BqeljaQB.cjs} +21 -177
- package/data_type_schemas-BqeljaQB.cjs.map +1 -0
- package/data_type_schemas-Cpco9Zba.js +225 -0
- package/data_type_schemas-Cpco9Zba.js.map +1 -0
- package/{decorator_utils-U_rZo8tv.cjs → decorator_utils-DSvYjLYD.cjs} +204 -60
- package/decorator_utils-DSvYjLYD.cjs.map +1 -0
- package/{decorator_utils-YSb1EGJ6.js → decorator_utils-gyymixlk.js} +207 -65
- package/decorator_utils-gyymixlk.js.map +1 -0
- package/definitions-DcB6B_4d.js +174 -0
- package/definitions-DcB6B_4d.js.map +1 -0
- package/definitions-DjQRkCeH.cjs +173 -0
- package/definitions-DjQRkCeH.cjs.map +1 -0
- package/definitions.cjs +1 -1
- package/definitions.mjs +11 -11
- package/{errors-B1rr67uM.js → errors-C-x5_jRE.js} +162 -27
- package/errors-C-x5_jRE.js.map +1 -0
- package/{errors-D8jb9VxY.cjs → errors-CNwuNhBV.cjs} +138 -5
- package/errors-CNwuNhBV.cjs.map +1 -0
- package/errors.cjs +5 -1
- package/errors.cjs.map +1 -1
- package/errors.d.ts +24 -2
- package/errors.mjs +19 -15
- package/index.cjs +1380 -652
- package/index.cjs.map +1 -1
- package/index.mjs +1624 -896
- package/index.mjs.map +1 -1
- package/joi.cjs +1854 -3368
- package/joi.cjs.map +1 -1
- package/joi.mjs +1857 -3371
- package/joi.mjs.map +1 -1
- package/package.json +1 -1
- package/private/decorators.d.ts +8 -8
- package/private/lucene_to_lucid_translator.d.ts +21 -175
- package/private/mixin.d.ts +68 -5
- package/private/services/open_api_schema_service.d.ts +111 -0
- package/private/type_guards.d.ts +62 -0
- package/private/types.d.ts +1 -1
- package/private/utils.d.ts +24 -0
- package/utils.cjs +1 -1
- package/utils.mjs +2 -2
- package/decorator_utils-U_rZo8tv.cjs.map +0 -1
- package/decorator_utils-YSb1EGJ6.js.map +0 -1
- package/definitions-B88XBTUF.js +0 -381
- package/definitions-B88XBTUF.js.map +0 -1
- package/definitions-DgI468dW.cjs.map +0 -1
- package/errors-B1rr67uM.js.map +0 -1
- package/errors-D8jb9VxY.cjs.map +0 -1
package/index.cjs
CHANGED
|
@@ -10,19 +10,16 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
|
|
|
10
10
|
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
11
11
|
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
12
12
|
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
13
|
-
var _a, _hookHandlers, _cleanupHandlers, _state, _handlersToIgnore, _skipAllHooks, _Runner_instances, filter_fn, exec_fn, _b, _hooks, _c, _d;
|
|
13
|
+
var _attributesToColumns, _allowedColumns, _connection, _dialect, _primaryKey, _tableName, _model, _LuceneLucidQueryBuilder_instances, getSearchableColumns_fn, _OpenApiSchemaService_instances, getFieldKey_fn, extractDataTypeInfo_fn, _a, _hookHandlers, _cleanupHandlers, _state, _handlersToIgnore, _skipAllHooks, _Runner_instances, filter_fn, exec_fn, _b, _hooks, _c, _d;
|
|
14
14
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
15
|
-
const decorator_utils = require("./decorator_utils-
|
|
15
|
+
const decorator_utils = require("./decorator_utils-DSvYjLYD.cjs");
|
|
16
16
|
const joi = require("./joi.cjs");
|
|
17
17
|
const liqe = require("liqe");
|
|
18
|
-
const errors = require("./errors-
|
|
19
|
-
const definitions = require("./definitions-DgI468dW.cjs");
|
|
18
|
+
const errors = require("./errors-CNwuNhBV.cjs");
|
|
20
19
|
const require$$0 = require("util");
|
|
21
20
|
require("node:path");
|
|
22
21
|
require("node:url");
|
|
23
|
-
const
|
|
24
|
-
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== void 0));
|
|
25
|
-
};
|
|
22
|
+
const definitions = require("./definitions-DjQRkCeH.cjs");
|
|
26
23
|
async function pMap(iterable, mapper, {
|
|
27
24
|
concurrency = Number.POSITIVE_INFINITY,
|
|
28
25
|
stopOnError = true,
|
|
@@ -144,384 +141,426 @@ async function pMap(iterable, mapper, {
|
|
|
144
141
|
});
|
|
145
142
|
}
|
|
146
143
|
const pMapSkip = Symbol("skip");
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
144
|
+
const _LuceneLucidQueryBuilder = class _LuceneLucidQueryBuilder {
|
|
145
|
+
constructor(connection, query, attributesToColumns, primaryKey, tableName, allowedColumns, model) {
|
|
146
|
+
__privateAdd(this, _LuceneLucidQueryBuilder_instances);
|
|
147
|
+
__privateAdd(this, _attributesToColumns);
|
|
148
|
+
__privateAdd(this, _allowedColumns);
|
|
149
|
+
__privateAdd(this, _connection);
|
|
150
|
+
__privateAdd(this, _dialect);
|
|
151
|
+
// @ts-ignore - not used yet
|
|
152
|
+
__privateAdd(this, _primaryKey);
|
|
153
|
+
// @ts-ignore - not used yet
|
|
154
|
+
__privateAdd(this, _tableName);
|
|
155
|
+
// @ts-ignore - not used yet
|
|
156
|
+
__privateAdd(this, _model);
|
|
157
|
+
__privateSet(this, _attributesToColumns, attributesToColumns);
|
|
158
|
+
__privateSet(this, _primaryKey, primaryKey);
|
|
159
|
+
__privateSet(this, _tableName, tableName);
|
|
160
|
+
__privateSet(this, _allowedColumns, allowedColumns);
|
|
161
|
+
__privateSet(this, _model, model);
|
|
162
|
+
__privateSet(this, _connection, connection);
|
|
163
|
+
__privateSet(this, _dialect, query.client.dialect.name);
|
|
164
|
+
}
|
|
165
|
+
applyLuceneAst(ast, query, throwOnInvalidType = false) {
|
|
166
|
+
switch (ast.type) {
|
|
167
|
+
case "EmptyExpression":
|
|
168
|
+
this.applyLuceneEmptyExpressionAst(ast, query);
|
|
169
|
+
break;
|
|
170
|
+
case "LogicalExpression":
|
|
171
|
+
this.applyLuceneLogicalExpressionAst(ast, query);
|
|
172
|
+
break;
|
|
173
|
+
case "ParenthesizedExpression":
|
|
174
|
+
this.applyLuceneParenthesizedExpressionAst(ast, query);
|
|
175
|
+
break;
|
|
176
|
+
case "UnaryOperator":
|
|
177
|
+
this.applyLuceneUnaryOperatorAst(ast, query);
|
|
178
|
+
break;
|
|
179
|
+
case "Tag":
|
|
180
|
+
this.applyLuceneTagAst(ast, query);
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
if (throwOnInvalidType) {
|
|
184
|
+
throw new errors.E_LUCENE_INVALID_TYPE(ast.type);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
applyLuceneEmptyExpressionAst(_ast, _query) {
|
|
190
|
+
}
|
|
191
|
+
applyLuceneLogicalExpressionAst(ast, query) {
|
|
192
|
+
const operatorType = ast.operator.operator;
|
|
193
|
+
query.where((subQuery) => {
|
|
194
|
+
if (operatorType === "AND") {
|
|
195
|
+
this.applyLuceneAst(ast.left, subQuery);
|
|
196
|
+
this.applyLuceneAst(ast.right, subQuery);
|
|
197
|
+
} else {
|
|
198
|
+
subQuery.orWhere((orSubQuery) => {
|
|
199
|
+
this.applyLuceneAst(ast.left, orSubQuery);
|
|
200
|
+
});
|
|
201
|
+
subQuery.orWhere((orSubQuery) => {
|
|
202
|
+
this.applyLuceneAst(ast.right, orSubQuery);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
applyLuceneParenthesizedExpressionAst(ast, query) {
|
|
208
|
+
query.where((subQuery) => {
|
|
209
|
+
this.applyLuceneAst(ast.expression, subQuery);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
applyLuceneUnaryOperatorAst(ast, query) {
|
|
213
|
+
const operator = ast.operator;
|
|
214
|
+
if (operator === "NOT" || operator === "-") {
|
|
215
|
+
query.whereNot((subQuery) => {
|
|
216
|
+
this.applyLuceneAst(ast.operand, subQuery);
|
|
170
217
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
orSubQuery,
|
|
175
|
-
attributesToColumns,
|
|
176
|
-
primaryKey,
|
|
177
|
-
tableName,
|
|
178
|
-
allowedColumns
|
|
179
|
-
);
|
|
218
|
+
} else {
|
|
219
|
+
query.where((subQuery) => {
|
|
220
|
+
this.applyLuceneAst(ast.operand, subQuery);
|
|
180
221
|
});
|
|
181
222
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
223
|
+
}
|
|
224
|
+
applyLuceneTagAst(ast, query) {
|
|
225
|
+
const { expression, field, operator } = ast;
|
|
226
|
+
switch (field.type) {
|
|
227
|
+
case "ImplicitField":
|
|
228
|
+
this.applyLuceneImplicitFieldAst(field, expression, operator, query);
|
|
229
|
+
break;
|
|
230
|
+
case "Field":
|
|
231
|
+
this.applyLuceneFieldAst(field, expression, operator, query);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
applyLuceneImplicitFieldAst(_field, expression, operator, query) {
|
|
236
|
+
const searchableColumns = __privateMethod(this, _LuceneLucidQueryBuilder_instances, getSearchableColumns_fn).call(this);
|
|
237
|
+
const defaultOperator = operator || {
|
|
238
|
+
operator: ":",
|
|
239
|
+
type: "ComparisonOperator",
|
|
240
|
+
location: { start: 0, end: 0 }
|
|
241
|
+
};
|
|
242
|
+
searchableColumns.forEach((columnName) => {
|
|
243
|
+
const fakeField = {
|
|
244
|
+
name: columnName,
|
|
245
|
+
type: "Field",
|
|
246
|
+
location: { start: 0, end: 0 },
|
|
247
|
+
quoted: false
|
|
248
|
+
};
|
|
249
|
+
query.orWhere((s) => {
|
|
250
|
+
this.applyLuceneFieldAst(fakeField, expression, defaultOperator, s);
|
|
205
251
|
});
|
|
206
|
-
|
|
252
|
+
});
|
|
207
253
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
254
|
+
applyLuceneFieldAst(field, expression, operator, query, forModel) {
|
|
255
|
+
const columnName = !forModel ? __privateGet(this, _attributesToColumns).get(field.name) : forModel.$keys.attributesToColumns.get(field.name);
|
|
256
|
+
if (!columnName || !forModel && __privateGet(this, _allowedColumns) && !__privateGet(this, _allowedColumns).includes(field.name)) {
|
|
257
|
+
if (field.name.includes(".")) {
|
|
258
|
+
const parts = field.name.split(".").filter((p) => "string" === typeof p && p.trim().length > 0).map((p) => p.trim());
|
|
259
|
+
const relationship = parts.shift();
|
|
260
|
+
if (!relationship || parts.length === 0) return;
|
|
261
|
+
this.applyLuceneRelatedFieldAst(
|
|
262
|
+
relationship,
|
|
263
|
+
parts.join("."),
|
|
264
|
+
expression,
|
|
265
|
+
operator,
|
|
266
|
+
query,
|
|
267
|
+
forModel
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
switch (expression.type) {
|
|
273
|
+
case "EmptyExpression":
|
|
274
|
+
this.applyLuceneEmptyExpression(columnName, expression, operator, query);
|
|
275
|
+
break;
|
|
276
|
+
case "LiteralExpression":
|
|
277
|
+
this.applyLuceneLiteralExpression(columnName, expression, operator, query);
|
|
278
|
+
break;
|
|
279
|
+
case "RangeExpression":
|
|
280
|
+
this.applyLuceneRangeExpression(columnName, expression, operator, query);
|
|
281
|
+
break;
|
|
282
|
+
case "RegexExpression":
|
|
283
|
+
this.applyLuceneRegexExpression(columnName, expression, operator, query);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
applyLuceneRelatedFieldAst(relationship, relatedProperty, expression, operator, query, forModel) {
|
|
288
|
+
if (!forModel && !__privateGet(this, _model)) return;
|
|
289
|
+
const targetModel = forModel || __privateGet(this, _model);
|
|
290
|
+
const lucidRelationshipDefinition = targetModel.$relationsDefinitions.get(relationship);
|
|
291
|
+
if (!lucidRelationshipDefinition) return;
|
|
292
|
+
const sub = lucidRelationshipDefinition.subQuery(__privateGet(this, _connection));
|
|
293
|
+
const relatedField = {
|
|
294
|
+
name: relatedProperty,
|
|
295
|
+
type: "Field",
|
|
296
|
+
location: { start: 0, end: 0 },
|
|
297
|
+
quoted: false
|
|
298
|
+
};
|
|
299
|
+
const relatedModel = lucidRelationshipDefinition.relatedModel();
|
|
300
|
+
this.applyLuceneFieldAst(relatedField, expression, operator, sub, relatedModel);
|
|
301
|
+
query.whereExists(sub.prepare());
|
|
302
|
+
}
|
|
303
|
+
applyLuceneEmptyExpression(column2, _expression, operator, query) {
|
|
212
304
|
switch (operator.operator) {
|
|
213
305
|
case ":":
|
|
214
306
|
case ":=":
|
|
307
|
+
case ":<=":
|
|
308
|
+
case ":>=":
|
|
215
309
|
query.where((sub) => {
|
|
216
310
|
sub.whereNull(column2);
|
|
217
311
|
sub.orWhere(column2, "=", "");
|
|
218
|
-
sub.orWhere(column2, "=", 0);
|
|
219
312
|
});
|
|
220
313
|
break;
|
|
221
|
-
case ":<":
|
|
222
|
-
query.where(column2, "<", 0);
|
|
223
|
-
break;
|
|
224
|
-
case ":<=":
|
|
225
|
-
query.where(column2, "<=", 0);
|
|
226
|
-
break;
|
|
227
|
-
case ":>":
|
|
228
|
-
query.where(column2, ">", 0);
|
|
229
|
-
break;
|
|
230
|
-
case ":>=":
|
|
231
|
-
query.where(column2, ">=", 0);
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
if ("string" === typeof value && expression.quoted) {
|
|
237
|
-
if (expression.quotes === "double") {
|
|
238
|
-
value = value.replace(/^"/g, "").replace(/"$/g, "");
|
|
239
|
-
} else if (expression.quotes === "single") {
|
|
240
|
-
value = value.replace(/^'/g, "").replace(/'$/g, "");
|
|
241
314
|
}
|
|
242
315
|
}
|
|
243
|
-
|
|
244
|
-
|
|
316
|
+
applyLuceneLiteralExpression(column2, expression, operator, query) {
|
|
317
|
+
let value = expression.value;
|
|
318
|
+
let isCaseSensitive = false;
|
|
319
|
+
if (value === null) {
|
|
245
320
|
switch (operator.operator) {
|
|
246
321
|
case ":":
|
|
247
322
|
case ":=":
|
|
248
323
|
query.where((sub) => {
|
|
249
|
-
sub.
|
|
250
|
-
sub.orWhere(column2, "
|
|
324
|
+
sub.whereNull(column2);
|
|
325
|
+
sub.orWhere(column2, "=", "");
|
|
326
|
+
sub.orWhere(column2, "=", 0);
|
|
251
327
|
});
|
|
252
328
|
break;
|
|
253
329
|
case ":<":
|
|
254
|
-
query.where(column2, "<",
|
|
255
|
-
break;
|
|
256
|
-
case ":<=":
|
|
257
|
-
query.where(column2, "<=", value);
|
|
258
|
-
break;
|
|
259
|
-
case ":>":
|
|
260
|
-
query.where(column2, ">", value);
|
|
261
|
-
break;
|
|
262
|
-
case ":>=":
|
|
263
|
-
query.where(column2, ">=", value);
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
break;
|
|
267
|
-
case "number":
|
|
268
|
-
switch (operator.operator) {
|
|
269
|
-
case ":":
|
|
270
|
-
case ":=":
|
|
271
|
-
query.where(column2, "=", value);
|
|
272
|
-
break;
|
|
273
|
-
case ":<":
|
|
274
|
-
query.where(column2, "<", value);
|
|
330
|
+
query.where(column2, "<", 0);
|
|
275
331
|
break;
|
|
276
332
|
case ":<=":
|
|
277
|
-
query.where(column2, "<=",
|
|
333
|
+
query.where(column2, "<=", 0);
|
|
278
334
|
break;
|
|
279
335
|
case ":>":
|
|
280
|
-
query.where(column2, ">",
|
|
336
|
+
query.where(column2, ">", 0);
|
|
281
337
|
break;
|
|
282
338
|
case ":>=":
|
|
283
|
-
query.where(column2, ">=",
|
|
339
|
+
query.where(column2, ">=", 0);
|
|
284
340
|
break;
|
|
285
341
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
query.where(column2, "<", value);
|
|
295
|
-
break;
|
|
296
|
-
case ":<=":
|
|
297
|
-
query.where(column2, "<=", value);
|
|
298
|
-
break;
|
|
299
|
-
case ":>":
|
|
300
|
-
query.where(column2, ">", value);
|
|
301
|
-
break;
|
|
302
|
-
case ":>=":
|
|
303
|
-
query.where(column2, ">=", value);
|
|
304
|
-
break;
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if ("string" === typeof value && expression.quoted) {
|
|
345
|
+
isCaseSensitive = true;
|
|
346
|
+
if (expression.quotes === "double") {
|
|
347
|
+
value = value.replace(/^"/g, "").replace(/"$/g, "");
|
|
348
|
+
} else if (expression.quotes === "single") {
|
|
349
|
+
value = value.replace(/^'/g, "").replace(/'$/g, "");
|
|
305
350
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
);
|
|
402
|
-
break;
|
|
403
|
-
case "RegexExpression":
|
|
404
|
-
applyLuceneRegexExpression(
|
|
405
|
-
columnName,
|
|
406
|
-
expression,
|
|
407
|
-
operator,
|
|
408
|
-
query
|
|
409
|
-
);
|
|
410
|
-
break;
|
|
351
|
+
}
|
|
352
|
+
switch (typeof value) {
|
|
353
|
+
case "string":
|
|
354
|
+
switch (operator.operator) {
|
|
355
|
+
case ":":
|
|
356
|
+
case ":=":
|
|
357
|
+
if (value.includes("*") || value.includes("?")) {
|
|
358
|
+
let likeValue = value.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
359
|
+
likeValue = likeValue.replace(/\*/g, "%").replace(/\?/g, "_");
|
|
360
|
+
if (isCaseSensitive) {
|
|
361
|
+
query.whereLike(column2, likeValue);
|
|
362
|
+
} else {
|
|
363
|
+
query.if(
|
|
364
|
+
["postgres", "pg"].includes(__privateGet(this, _dialect)),
|
|
365
|
+
(q) => {
|
|
366
|
+
q.whereILike(column2, likeValue);
|
|
367
|
+
},
|
|
368
|
+
(q) => {
|
|
369
|
+
q.whereLike(column2, likeValue);
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
query.where((sub) => {
|
|
375
|
+
if (isCaseSensitive) {
|
|
376
|
+
sub.where(column2, "=", value);
|
|
377
|
+
} else {
|
|
378
|
+
query.if(
|
|
379
|
+
["postgres", "pg"].includes(__privateGet(this, _dialect)),
|
|
380
|
+
() => {
|
|
381
|
+
sub.orWhereILike(column2, value);
|
|
382
|
+
},
|
|
383
|
+
() => {
|
|
384
|
+
sub.whereLike(column2, value);
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
break;
|
|
391
|
+
case ":<":
|
|
392
|
+
query.where(column2, "<", value);
|
|
393
|
+
break;
|
|
394
|
+
case ":<=":
|
|
395
|
+
query.where(column2, "<=", value);
|
|
396
|
+
break;
|
|
397
|
+
case ":>":
|
|
398
|
+
query.where(column2, ">", value);
|
|
399
|
+
break;
|
|
400
|
+
case ":>=":
|
|
401
|
+
query.where(column2, ">=", value);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
break;
|
|
405
|
+
case "number":
|
|
406
|
+
switch (operator.operator) {
|
|
407
|
+
case ":":
|
|
408
|
+
case ":=":
|
|
409
|
+
query.where(column2, "=", value);
|
|
410
|
+
break;
|
|
411
|
+
case ":<":
|
|
412
|
+
query.where(column2, "<", value);
|
|
413
|
+
break;
|
|
414
|
+
case ":<=":
|
|
415
|
+
query.where(column2, "<=", value);
|
|
416
|
+
break;
|
|
417
|
+
case ":>":
|
|
418
|
+
query.where(column2, ">", value);
|
|
419
|
+
break;
|
|
420
|
+
case ":>=":
|
|
421
|
+
query.where(column2, ">=", value);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
case "boolean":
|
|
426
|
+
switch (operator.operator) {
|
|
427
|
+
case ":":
|
|
428
|
+
case ":=":
|
|
429
|
+
query.where(column2, "=", value);
|
|
430
|
+
break;
|
|
431
|
+
case ":<":
|
|
432
|
+
query.where(column2, "<", value);
|
|
433
|
+
break;
|
|
434
|
+
case ":<=":
|
|
435
|
+
query.where(column2, "<=", value);
|
|
436
|
+
break;
|
|
437
|
+
case ":>":
|
|
438
|
+
query.where(column2, ">", value);
|
|
439
|
+
break;
|
|
440
|
+
case ":>=":
|
|
441
|
+
query.where(column2, ">=", value);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
411
446
|
}
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
query,
|
|
422
|
-
attributesToColumns,
|
|
423
|
-
primaryKey,
|
|
424
|
-
tableName,
|
|
425
|
-
allowedColumns
|
|
426
|
-
);
|
|
427
|
-
break;
|
|
428
|
-
case "Field":
|
|
429
|
-
applyLuceneFieldAst(
|
|
430
|
-
field,
|
|
431
|
-
expression,
|
|
432
|
-
operator,
|
|
433
|
-
query,
|
|
434
|
-
attributesToColumns,
|
|
435
|
-
primaryKey,
|
|
436
|
-
tableName,
|
|
437
|
-
allowedColumns
|
|
438
|
-
);
|
|
439
|
-
break;
|
|
447
|
+
applyLuceneRangeExpression(column2, expression, operator, query) {
|
|
448
|
+
const comparableMin = expression.range.minInclusive ? expression.range.min : expression.range.min + 1;
|
|
449
|
+
const comparableMax = expression.range.maxInclusive ? expression.range.max : expression.range.max - 1;
|
|
450
|
+
switch (operator.operator) {
|
|
451
|
+
case ":":
|
|
452
|
+
case ":=":
|
|
453
|
+
query.whereBetween(column2, [comparableMin, comparableMax]);
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
440
456
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
457
|
+
applyLuceneRegexExpression(column2, expression, operator, query) {
|
|
458
|
+
let value = expression.value;
|
|
459
|
+
switch (__privateGet(this, _dialect)) {
|
|
460
|
+
case "mysql":
|
|
461
|
+
case "mariadb":
|
|
462
|
+
switch (operator.operator) {
|
|
463
|
+
case ":":
|
|
464
|
+
case ":=":
|
|
465
|
+
query.whereRaw(`?? REGEXP ?`, [column2, value]);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
break;
|
|
469
|
+
case "pg":
|
|
470
|
+
case "postgres":
|
|
471
|
+
switch (operator.operator) {
|
|
472
|
+
case ":":
|
|
473
|
+
case ":=":
|
|
474
|
+
query.whereRaw(`?? ~ ?`, [column2, value]);
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
case "oracle":
|
|
479
|
+
case "oracledb":
|
|
480
|
+
case "tedious":
|
|
481
|
+
case "mssql":
|
|
482
|
+
switch (operator.operator) {
|
|
483
|
+
case ":":
|
|
484
|
+
case ":=":
|
|
485
|
+
query.whereRaw(`REGEXP_LIKE(??, ?)`, [column2, value]);
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
break;
|
|
489
|
+
case "sqlite3":
|
|
490
|
+
case "better-sqlite3":
|
|
491
|
+
throw new errors.E_LUCENE_REGEX_NOT_SUPPORTED();
|
|
492
|
+
default:
|
|
493
|
+
value = value.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
494
|
+
value = value.replace(/\*/g, "%").replace(/\?/g, "_");
|
|
495
|
+
switch (operator.operator) {
|
|
496
|
+
case ":":
|
|
497
|
+
case ":=":
|
|
498
|
+
query.whereLike(column2, value);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
466
503
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
ast,
|
|
475
|
-
query,
|
|
476
|
-
attributesToColumns,
|
|
477
|
-
primaryKey,
|
|
478
|
-
tableName,
|
|
479
|
-
allowedColumns
|
|
480
|
-
);
|
|
481
|
-
break;
|
|
482
|
-
case "ParenthesizedExpression":
|
|
483
|
-
applyLuceneParenthesizedExpressionAst(
|
|
484
|
-
ast,
|
|
485
|
-
query,
|
|
486
|
-
attributesToColumns,
|
|
487
|
-
primaryKey,
|
|
488
|
-
tableName,
|
|
489
|
-
allowedColumns
|
|
490
|
-
);
|
|
491
|
-
break;
|
|
492
|
-
case "Tag":
|
|
493
|
-
applyLuceneTagAst(ast, query, attributesToColumns, primaryKey, tableName, allowedColumns);
|
|
494
|
-
break;
|
|
495
|
-
case "UnaryOperator":
|
|
496
|
-
applyLuceneUnaryOperatorAst(
|
|
497
|
-
ast,
|
|
498
|
-
query,
|
|
499
|
-
attributesToColumns,
|
|
500
|
-
primaryKey,
|
|
501
|
-
tableName,
|
|
502
|
-
allowedColumns
|
|
503
|
-
);
|
|
504
|
-
break;
|
|
505
|
-
default:
|
|
506
|
-
if (throwOnInvalidType) {
|
|
507
|
-
throw new errors.E_LUCENE_INVALID_TYPE(ast.type);
|
|
504
|
+
static get(luceneQuery, connection, attributesToColumns, primaryKey, tableName, allowedColumns, model) {
|
|
505
|
+
let parsed;
|
|
506
|
+
try {
|
|
507
|
+
parsed = liqe.parse(luceneQuery);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
if (error instanceof liqe.SyntaxError) {
|
|
510
|
+
throw new errors.E_LUCENE_SYNTAX_EXCEPTION(luceneQuery, error);
|
|
508
511
|
}
|
|
509
|
-
|
|
512
|
+
throw new errors.E_LUCENE_UNEXPECTED_EXCEPTION(error);
|
|
513
|
+
}
|
|
514
|
+
const query = connection.query().from(tableName);
|
|
515
|
+
const builder = new _LuceneLucidQueryBuilder(
|
|
516
|
+
connection,
|
|
517
|
+
query,
|
|
518
|
+
attributesToColumns,
|
|
519
|
+
primaryKey,
|
|
520
|
+
tableName,
|
|
521
|
+
allowedColumns,
|
|
522
|
+
model
|
|
523
|
+
);
|
|
524
|
+
builder.applyLuceneAst(parsed, query, true);
|
|
525
|
+
return query;
|
|
510
526
|
}
|
|
511
527
|
};
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
528
|
+
_attributesToColumns = new WeakMap();
|
|
529
|
+
_allowedColumns = new WeakMap();
|
|
530
|
+
_connection = new WeakMap();
|
|
531
|
+
_dialect = new WeakMap();
|
|
532
|
+
_primaryKey = new WeakMap();
|
|
533
|
+
_tableName = new WeakMap();
|
|
534
|
+
_model = new WeakMap();
|
|
535
|
+
_LuceneLucidQueryBuilder_instances = new WeakSet();
|
|
536
|
+
getSearchableColumns_fn = function() {
|
|
537
|
+
if (__privateGet(this, _model) && "$resourcefulColumns" in __privateGet(this, _model) && __privateGet(this, _model).$resourcefulColumns instanceof Map) {
|
|
538
|
+
const modelFields = [];
|
|
539
|
+
__privateGet(this, _model).$resourcefulColumns.forEach((_c2, k) => {
|
|
540
|
+
modelFields.push(k);
|
|
541
|
+
});
|
|
542
|
+
if ("$resourcefulRelationships" in __privateGet(this, _model) && __privateGet(this, _model).$resourcefulRelationships instanceof Map) {
|
|
543
|
+
__privateGet(this, _model).$resourcefulRelationships.forEach((_r, k) => {
|
|
544
|
+
modelFields.push(k);
|
|
545
|
+
});
|
|
519
546
|
}
|
|
520
|
-
|
|
547
|
+
return Array.from(new Set(modelFields)).filter(
|
|
548
|
+
(f) => !__privateGet(this, _allowedColumns) || __privateGet(this, _allowedColumns).includes(f)
|
|
549
|
+
);
|
|
521
550
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
551
|
+
return Object.entries(__privateGet(this, _attributesToColumns).all()).filter(([attr]) => !__privateGet(this, _allowedColumns) || __privateGet(this, _allowedColumns).includes(attr)).map(([, column2]) => column2);
|
|
552
|
+
};
|
|
553
|
+
let LuceneLucidQueryBuilder = _LuceneLucidQueryBuilder;
|
|
554
|
+
const luceneToLucid = (luceneQuery, connection, attributesToColumns, primaryKey, tableName, allowedColumns, model) => {
|
|
555
|
+
return LuceneLucidQueryBuilder.get(
|
|
556
|
+
luceneQuery,
|
|
557
|
+
connection,
|
|
558
|
+
attributesToColumns,
|
|
559
|
+
primaryKey,
|
|
560
|
+
tableName,
|
|
561
|
+
allowedColumns,
|
|
562
|
+
model
|
|
563
|
+
);
|
|
525
564
|
};
|
|
526
565
|
var o = Object.defineProperty;
|
|
527
566
|
var a = (t, s, r) => s in t ? o(t, s, { enumerable: true, configurable: true, writable: true, value: r }) : t[s] = r;
|
|
@@ -589,9 +628,388 @@ class l {
|
|
|
589
628
|
) : this.e[s] = []), this;
|
|
590
629
|
}
|
|
591
630
|
}
|
|
631
|
+
class OpenApiSchemaService {
|
|
632
|
+
constructor() {
|
|
633
|
+
__privateAdd(this, _OpenApiSchemaService_instances);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Generates a complete OpenAPI schema object for a resourceful model.
|
|
637
|
+
*
|
|
638
|
+
* This method creates a comprehensive OpenAPI v3 schema representation of the model
|
|
639
|
+
* by processing the provided metadata schema. The metadata should already have
|
|
640
|
+
* field-level access control permissions evaluated for the given request context.
|
|
641
|
+
*
|
|
642
|
+
* @param resourcefulModelMetaSchema - The resourceful model metadata with ACL filtering applied
|
|
643
|
+
* @returns Complete OpenAPI schema object
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```typescript
|
|
647
|
+
* const service = new OpenApiSchemaService()
|
|
648
|
+
* const schema = service.generateModelSchema(metaSchema)
|
|
649
|
+
* // Returns complete OpenAPI schema with accessible fields only
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
/* istanbul ignore next -- @preserve */
|
|
653
|
+
generateModelSchema(operation = "read", resourcefulModelMetaSchema, model) {
|
|
654
|
+
if (!errors.isObject(resourcefulModelMetaSchema) || !resourcefulModelMetaSchema.name || !decorator_utils.isResourcefulModel(model)) {
|
|
655
|
+
return {
|
|
656
|
+
type: "object",
|
|
657
|
+
properties: {},
|
|
658
|
+
required: []
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const schema = {
|
|
662
|
+
type: "object",
|
|
663
|
+
title: resourcefulModelMetaSchema.name,
|
|
664
|
+
description: resourcefulModelMetaSchema.description,
|
|
665
|
+
externalDocs: resourcefulModelMetaSchema.externalDocs,
|
|
666
|
+
example: resourcefulModelMetaSchema.example,
|
|
667
|
+
properties: {}
|
|
668
|
+
};
|
|
669
|
+
/* istanbul ignore next -- @preserve */
|
|
670
|
+
Object.entries(resourcefulModelMetaSchema.properties).forEach(([propertyKey, propertyMeta]) => {
|
|
671
|
+
const resolvedColumnName = __privateMethod(this, _OpenApiSchemaService_instances, getFieldKey_fn).call(this, propertyKey, propertyMeta.lucidDefinitions);
|
|
672
|
+
if (null === resolvedColumnName) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
switch (propertyMeta.kind) {
|
|
676
|
+
case "column": {
|
|
677
|
+
schema.properties[propertyKey] = this.generateColumnSchema(
|
|
678
|
+
propertyKey,
|
|
679
|
+
propertyMeta
|
|
680
|
+
);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
case "computedAccessor": {
|
|
684
|
+
schema.properties[propertyKey] = this.generateComputedAccessorSchema(
|
|
685
|
+
propertyKey,
|
|
686
|
+
propertyMeta
|
|
687
|
+
);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
case "relationship": {
|
|
691
|
+
const relatedSchema = this.generateRelationshipSchema(
|
|
692
|
+
propertyKey,
|
|
693
|
+
propertyMeta
|
|
694
|
+
);
|
|
695
|
+
if (relatedSchema) {
|
|
696
|
+
schema.properties[propertyKey] = relatedSchema;
|
|
697
|
+
}
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
const requiredPropSet = /* @__PURE__ */ new Set();
|
|
703
|
+
Object.entries(schema.properties).forEach(([propertyKey, propertySchema]) => {
|
|
704
|
+
if (!errors.isObject(propertySchema) || "$ref" in propertySchema) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const isNullable = propertySchema.nullable === true;
|
|
708
|
+
if (!isNullable && ("read" === operation || !model.$resourcefulComputedAccessors.has(propertyKey) && !model.$resourcefulRelationships.has(propertyKey))) {
|
|
709
|
+
requiredPropSet.add(propertyKey);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
schema.required = Array.from(requiredPropSet);
|
|
713
|
+
return schema;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Generates OpenAPI schema for a column property.
|
|
717
|
+
*
|
|
718
|
+
* This method converts resourceful column metadata into an OpenAPI schema object,
|
|
719
|
+
* properly extracting data type information and preserving all existing schema
|
|
720
|
+
* properties such as validation constraints, nullability, and access modifiers.
|
|
721
|
+
*
|
|
722
|
+
* @param propertyKey - The name of the column property
|
|
723
|
+
* @param propertyMeta - The resourceful metadata for the column
|
|
724
|
+
* @returns OpenAPI schema object for the column
|
|
725
|
+
*
|
|
726
|
+
* @example
|
|
727
|
+
* ```typescript
|
|
728
|
+
* const service = new OpenApiSchemaService()
|
|
729
|
+
* const schema = service.generateColumnSchema('name', columnMeta)
|
|
730
|
+
* // Returns: { type: 'string', minLength: 1, maxLength: 100, ... }
|
|
731
|
+
* ```
|
|
732
|
+
*/
|
|
733
|
+
generateColumnSchema(propertyKey, propertyMeta) {
|
|
734
|
+
if (!errors.isObject(propertyMeta) || !propertyMeta.definition || !propertyMeta.lucidDefinitions) {
|
|
735
|
+
return {};
|
|
736
|
+
}
|
|
737
|
+
const dataTypeInfo = __privateMethod(this, _OpenApiSchemaService_instances, extractDataTypeInfo_fn).call(this, propertyMeta.definition.type);
|
|
738
|
+
const fieldKey = __privateMethod(this, _OpenApiSchemaService_instances, getFieldKey_fn).call(this, propertyKey, propertyMeta.lucidDefinitions);
|
|
739
|
+
const dataType = propertyMeta.definition.type;
|
|
740
|
+
const definition = propertyMeta.definition;
|
|
741
|
+
/* istanbul ignore next -- @preserve */
|
|
742
|
+
const schemaProperties = errors.stripUndefinedValuesFromObject({
|
|
743
|
+
// Core OpenAPI type information (properly extracted)
|
|
744
|
+
type: dataTypeInfo.type,
|
|
745
|
+
format: dataTypeInfo.format,
|
|
746
|
+
// Schema metadata
|
|
747
|
+
items: definition.items || dataType.items,
|
|
748
|
+
title: fieldKey || propertyKey,
|
|
749
|
+
description: definition.description,
|
|
750
|
+
default: "default" in definition ? definition.default : this.getDefaultValueFromPropertyMeta(propertyMeta),
|
|
751
|
+
// Numeric constraints (from data type or definition)
|
|
752
|
+
multipleOf: definition.multipleOf || dataType.multipleOf,
|
|
753
|
+
maximum: definition.maximum || dataType.maximum,
|
|
754
|
+
exclusiveMaximum: definition.exclusiveMaximum || dataType.exclusiveMaximum,
|
|
755
|
+
minimum: definition.minimum || dataType.minimum,
|
|
756
|
+
exclusiveMinimum: definition.exclusiveMinimum || dataType.exclusiveMinimum,
|
|
757
|
+
// String constraints (from data type or definition)
|
|
758
|
+
maxLength: definition.maxLength || dataType.maxLength,
|
|
759
|
+
minLength: definition.minLength || dataType.minLength,
|
|
760
|
+
pattern: definition.pattern || dataType.pattern,
|
|
761
|
+
// Object constraints (from data type or definition)
|
|
762
|
+
additionalProperties: definition.additionalProperties || dataType.additionalProperties,
|
|
763
|
+
maxProperties: definition.maxProperties || dataType.maxProperties,
|
|
764
|
+
minProperties: definition.minProperties || dataType.minProperties,
|
|
765
|
+
properties: definition.properties || dataType.properties,
|
|
766
|
+
// Array constraints (from data type or definition)
|
|
767
|
+
maxItems: definition.maxItems || dataType.maxItems,
|
|
768
|
+
minItems: definition.minItems || dataType.minItems,
|
|
769
|
+
uniqueItems: definition.uniqueItems || dataType.uniqueItems,
|
|
770
|
+
// Schema composition (from data type or definition)
|
|
771
|
+
allOf: definition.allOf || dataType.allOf,
|
|
772
|
+
oneOf: definition.oneOf || dataType.oneOf,
|
|
773
|
+
anyOf: definition.anyOf || dataType.anyOf,
|
|
774
|
+
not: definition.not || dataType.not,
|
|
775
|
+
// Validation and constraints (from data type or definition)
|
|
776
|
+
required: definition.required || dataType.required,
|
|
777
|
+
enum: definition.enum || dataType.enum,
|
|
778
|
+
nullable: definition.nullable !== void 0 ? definition.nullable : dataType.nullable || false,
|
|
779
|
+
// Access control
|
|
780
|
+
readOnly: propertyMeta.canRead && !propertyMeta.canWrite,
|
|
781
|
+
writeOnly: !propertyMeta.canRead && propertyMeta.canWrite,
|
|
782
|
+
// Documentation
|
|
783
|
+
externalDocs: definition.externalDocs,
|
|
784
|
+
example: definition.example
|
|
785
|
+
});
|
|
786
|
+
return schemaProperties;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Generates OpenAPI schema for a computed accessor property.
|
|
790
|
+
*
|
|
791
|
+
* This method converts resourceful computed accessor metadata into an OpenAPI
|
|
792
|
+
* schema object, applying the same data type extraction improvements as column
|
|
793
|
+
* schema generation while maintaining all existing functionality.
|
|
794
|
+
*
|
|
795
|
+
* @param propertyKey - The name of the computed accessor property
|
|
796
|
+
* @param propertyMeta - The resourceful metadata for the computed accessor
|
|
797
|
+
* @returns OpenAPI schema object for the computed accessor
|
|
798
|
+
*
|
|
799
|
+
* @example
|
|
800
|
+
* ```typescript
|
|
801
|
+
* const service = new OpenApiSchemaService()
|
|
802
|
+
* const schema = service.generateComputedAccessorSchema('fullName', accessorMeta)
|
|
803
|
+
* // Returns: { type: 'string', readOnly: true, ... }
|
|
804
|
+
* ```
|
|
805
|
+
*/
|
|
806
|
+
generateComputedAccessorSchema(propertyKey, propertyMeta) {
|
|
807
|
+
if (!errors.isObject(propertyMeta) || !propertyMeta.definition || !propertyMeta.lucidDefinitions) {
|
|
808
|
+
return {};
|
|
809
|
+
}
|
|
810
|
+
const dataTypeInfo = __privateMethod(this, _OpenApiSchemaService_instances, extractDataTypeInfo_fn).call(this, propertyMeta.definition.type);
|
|
811
|
+
const fieldKey = __privateMethod(this, _OpenApiSchemaService_instances, getFieldKey_fn).call(this, propertyKey, propertyMeta.lucidDefinitions);
|
|
812
|
+
const dataType = propertyMeta.definition.type;
|
|
813
|
+
const definition = propertyMeta.definition;
|
|
814
|
+
/* istanbul ignore next -- @preserve */
|
|
815
|
+
const schemaProperties = errors.stripUndefinedValuesFromObject({
|
|
816
|
+
// Core OpenAPI type information (properly extracted)
|
|
817
|
+
type: dataTypeInfo.type,
|
|
818
|
+
format: dataTypeInfo.format,
|
|
819
|
+
// Schema metadata
|
|
820
|
+
items: definition.items || dataType.items,
|
|
821
|
+
title: fieldKey || propertyKey,
|
|
822
|
+
description: definition.description,
|
|
823
|
+
default: "default" in definition ? definition.default : this.getDefaultValueFromPropertyMeta(propertyMeta),
|
|
824
|
+
// Numeric constraints (from data type or definition)
|
|
825
|
+
multipleOf: definition.multipleOf || dataType.multipleOf,
|
|
826
|
+
maximum: definition.maximum || dataType.maximum,
|
|
827
|
+
exclusiveMaximum: definition.exclusiveMaximum || dataType.exclusiveMaximum,
|
|
828
|
+
minimum: definition.minimum || dataType.minimum,
|
|
829
|
+
exclusiveMinimum: definition.exclusiveMinimum || dataType.exclusiveMinimum,
|
|
830
|
+
// String constraints (from data type or definition)
|
|
831
|
+
maxLength: definition.maxLength || dataType.maxLength,
|
|
832
|
+
minLength: definition.minLength || dataType.minLength,
|
|
833
|
+
pattern: definition.pattern || dataType.pattern,
|
|
834
|
+
// Object constraints (from data type or definition)
|
|
835
|
+
additionalProperties: definition.additionalProperties || dataType.additionalProperties,
|
|
836
|
+
maxProperties: definition.maxProperties || dataType.maxProperties,
|
|
837
|
+
minProperties: definition.minProperties || dataType.minProperties,
|
|
838
|
+
properties: definition.properties || dataType.properties,
|
|
839
|
+
// Array constraints (from data type or definition)
|
|
840
|
+
maxItems: definition.maxItems || dataType.maxItems,
|
|
841
|
+
minItems: definition.minItems || dataType.minItems,
|
|
842
|
+
uniqueItems: definition.uniqueItems || dataType.uniqueItems,
|
|
843
|
+
// Schema composition (from data type or definition)
|
|
844
|
+
allOf: definition.allOf || dataType.allOf,
|
|
845
|
+
oneOf: definition.oneOf || dataType.oneOf,
|
|
846
|
+
anyOf: definition.anyOf || dataType.anyOf,
|
|
847
|
+
not: definition.not || dataType.not,
|
|
848
|
+
// Validation and constraints (from data type or definition)
|
|
849
|
+
required: definition.required || dataType.required,
|
|
850
|
+
enum: definition.enum || dataType.enum,
|
|
851
|
+
nullable: definition.nullable !== void 0 ? definition.nullable : dataType.nullable || false,
|
|
852
|
+
// Access control
|
|
853
|
+
readOnly: propertyMeta.canRead && !propertyMeta.canWrite,
|
|
854
|
+
writeOnly: !propertyMeta.canRead && propertyMeta.canWrite,
|
|
855
|
+
// Documentation
|
|
856
|
+
externalDocs: definition.externalDocs,
|
|
857
|
+
example: definition.example
|
|
858
|
+
});
|
|
859
|
+
return schemaProperties;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Generates OpenAPI schema for a relationship property.
|
|
863
|
+
*
|
|
864
|
+
* This method converts resourceful relationship metadata into an OpenAPI
|
|
865
|
+
* reference object, preserving existing relationship reference generation
|
|
866
|
+
* logic and ensuring proper handling of related model references.
|
|
867
|
+
*
|
|
868
|
+
* @param propertyKey - The name of the relationship property
|
|
869
|
+
* @param propertyMeta - The resourceful metadata for the relationship
|
|
870
|
+
* @returns OpenAPI reference object for the relationship, or undefined if not applicable
|
|
871
|
+
*
|
|
872
|
+
* @example
|
|
873
|
+
* ```typescript
|
|
874
|
+
* const service = new OpenApiSchemaService()
|
|
875
|
+
* const schema = service.generateRelationshipSchema('user', relationshipMeta)
|
|
876
|
+
* // Returns: { $ref: '#/components/schemas/User' }
|
|
877
|
+
* ```
|
|
878
|
+
*/
|
|
879
|
+
generateRelationshipSchema(_propertyKey, propertyMeta) {
|
|
880
|
+
if (!errors.isObject(propertyMeta) || !propertyMeta.definition || !propertyMeta.lucidDefinitions || !propertyMeta.relatedModel) {
|
|
881
|
+
return void 0;
|
|
882
|
+
}
|
|
883
|
+
const relatedModel = propertyMeta.relatedModel();
|
|
884
|
+
/* istanbul ignore if -- @preserve */
|
|
885
|
+
if (!relatedModel) {
|
|
886
|
+
return void 0;
|
|
887
|
+
}
|
|
888
|
+
/* istanbul ignore if -- @preserve */
|
|
889
|
+
if (!decorator_utils.isResourcefulModel(relatedModel)) {
|
|
890
|
+
return void 0;
|
|
891
|
+
}
|
|
892
|
+
const ref2 = `#/components/schemas/${relatedModel.$resourcefulName}`;
|
|
893
|
+
return {
|
|
894
|
+
$ref: ref2
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Extracts default value from property metadata using Joi description.
|
|
899
|
+
*
|
|
900
|
+
* This protected method provides access to the default value extraction logic
|
|
901
|
+
* that was previously part of the mixin. It examines the Joi validator
|
|
902
|
+
* description to find default values specified in the schema.
|
|
903
|
+
*
|
|
904
|
+
* @param propertyMeta - The resourceful metadata containing the validator
|
|
905
|
+
* @returns The default value if found, undefined otherwise
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* ```typescript
|
|
909
|
+
* const service = new OpenApiSchemaService()
|
|
910
|
+
* const defaultValue = service.getDefaultValueFromPropertyMeta(propertyMeta)
|
|
911
|
+
* ```
|
|
912
|
+
*/
|
|
913
|
+
getDefaultValueFromPropertyMeta(propertyMeta) {
|
|
914
|
+
const joiDescription = propertyMeta.validator.describe();
|
|
915
|
+
/* istanbul ignore if -- @preserve */
|
|
916
|
+
if (errors.isObject(joiDescription) && "flags" in joiDescription && errors.isObject(joiDescription.flags) && "default" in joiDescription.flags) {
|
|
917
|
+
return joiDescription.flags.default;
|
|
918
|
+
}
|
|
919
|
+
/* istanbul ignore next -- @preserve */
|
|
920
|
+
return void 0;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
_OpenApiSchemaService_instances = new WeakSet();
|
|
924
|
+
/**
|
|
925
|
+
* Gets the field key for a property, handling serializeAs transformation.
|
|
926
|
+
*
|
|
927
|
+
* This private method extracts the appropriate field name for a property,
|
|
928
|
+
* taking into account the serializeAs option from Lucid model definitions.
|
|
929
|
+
* Returns null if the field cannot be read (following Lucid's serializeAs behavior).
|
|
930
|
+
*
|
|
931
|
+
* @param key - The original property key
|
|
932
|
+
* @param definition - The Lucid model definition (column, computed, or relation)
|
|
933
|
+
* @returns The field key to use, the original key if no transformation is needed, or null if field cannot be read
|
|
934
|
+
*/
|
|
935
|
+
getFieldKey_fn = function(key, definition) {
|
|
936
|
+
if (definition.serializeAs === null) {
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
/* istanbul ignore if -- @preserve */
|
|
940
|
+
if (errors.isString(definition.serializeAs)) {
|
|
941
|
+
return definition.serializeAs;
|
|
942
|
+
}
|
|
943
|
+
/* istanbul ignore next -- @preserve */
|
|
944
|
+
return key;
|
|
945
|
+
};
|
|
946
|
+
/**
|
|
947
|
+
* Extracts OpenAPI type and format information from resourceful data types.
|
|
948
|
+
*
|
|
949
|
+
* This private method handles the mapping from resourceful data type instances
|
|
950
|
+
* to their corresponding OpenAPI type and format specifications. It properly
|
|
951
|
+
* handles all supported resourceful data types and provides appropriate
|
|
952
|
+
* fallback behavior for unknown types.
|
|
953
|
+
*
|
|
954
|
+
* @param dataType - The resourceful data type instance to extract information from
|
|
955
|
+
* @returns Object containing the OpenAPI type and optional format
|
|
956
|
+
*
|
|
957
|
+
* @example
|
|
958
|
+
* ```typescript
|
|
959
|
+
* const info = this.#extractDataTypeInfo(ResourcefulStringType())
|
|
960
|
+
* // Returns: { type: 'string' }
|
|
961
|
+
*
|
|
962
|
+
* const info = this.#extractDataTypeInfo(ResourcefulDateTimeType())
|
|
963
|
+
* // Returns: { type: 'string', format: 'date-time' }
|
|
964
|
+
* ```
|
|
965
|
+
*/
|
|
966
|
+
extractDataTypeInfo_fn = function(dataType) {
|
|
967
|
+
let baseType;
|
|
968
|
+
try {
|
|
969
|
+
baseType = dataType.type;
|
|
970
|
+
} catch (error) {
|
|
971
|
+
/* istanbul ignore next -- @preserve */
|
|
972
|
+
return { type: "string" };
|
|
973
|
+
}
|
|
974
|
+
const format = "format" in dataType && typeof dataType.format === "string" ? dataType.format : void 0;
|
|
975
|
+
/* istanbul ignore next -- @preserve */
|
|
976
|
+
switch (baseType) {
|
|
977
|
+
case "string": {
|
|
978
|
+
if (format) {
|
|
979
|
+
return { type: "string", format };
|
|
980
|
+
}
|
|
981
|
+
return { type: "string" };
|
|
982
|
+
}
|
|
983
|
+
case "number": {
|
|
984
|
+
if (format === "float" || format === "double") {
|
|
985
|
+
return { type: "number", format };
|
|
986
|
+
}
|
|
987
|
+
return { type: "number" };
|
|
988
|
+
}
|
|
989
|
+
case "integer": {
|
|
990
|
+
if (format === "int32" || format === "int64") {
|
|
991
|
+
return { type: "integer", format };
|
|
992
|
+
}
|
|
993
|
+
return { type: "integer" };
|
|
994
|
+
}
|
|
995
|
+
case "boolean": {
|
|
996
|
+
return { type: "boolean" };
|
|
997
|
+
}
|
|
998
|
+
case "object": {
|
|
999
|
+
return { type: "object" };
|
|
1000
|
+
}
|
|
1001
|
+
case "array": {
|
|
1002
|
+
return { type: "array" };
|
|
1003
|
+
}
|
|
1004
|
+
default: {
|
|
1005
|
+
/* istanbul ignore next -- @preserve */
|
|
1006
|
+
return { type: "string" };
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
592
1010
|
const ResourcefulErrorHandlerMethod = ["bubble", "pass", "fail"];
|
|
593
1011
|
const getFieldKey = (key, definition) => {
|
|
594
|
-
if (
|
|
1012
|
+
if (errors.isString(definition.serializeAs)) {
|
|
595
1013
|
return definition.serializeAs;
|
|
596
1014
|
}
|
|
597
1015
|
return key;
|
|
@@ -614,6 +1032,10 @@ function withResourceful(options = {}) {
|
|
|
614
1032
|
list: joi.joi.array().items(joi.joi.function()).default([]),
|
|
615
1033
|
access: joi.joi.array().items(joi.joi.function()).default([])
|
|
616
1034
|
}).default({}),
|
|
1035
|
+
payloadValidationSchemaBuilders: joi.joi.object({
|
|
1036
|
+
create: joi.joi.array().items(joi.joi.function()).default([]),
|
|
1037
|
+
update: joi.joi.array().items(joi.joi.function()).default([])
|
|
1038
|
+
}).default({ create: [], update: [] }),
|
|
617
1039
|
description: joi.joi.string().optional(),
|
|
618
1040
|
externalDocs: joi.joi.object({
|
|
619
1041
|
description: joi.joi.string().optional(),
|
|
@@ -672,6 +1094,7 @@ function withResourceful(options = {}) {
|
|
|
672
1094
|
Array.from(this.$resourcefulColumns.entries()),
|
|
673
1095
|
async ([propertyKey, columnDefinition]) => {
|
|
674
1096
|
const lucidDefinitions = this.$getColumn(propertyKey);
|
|
1097
|
+
/* istanbul ignore if -- @preserve */
|
|
675
1098
|
if (!lucidDefinitions) return;
|
|
676
1099
|
const resourcefulModelColumnMetaSchema = {
|
|
677
1100
|
propertyKey,
|
|
@@ -709,6 +1132,7 @@ function withResourceful(options = {}) {
|
|
|
709
1132
|
Array.from(this.$resourcefulComputedAccessors.entries()),
|
|
710
1133
|
async ([propertyKey, computedAccessorDefinition]) => {
|
|
711
1134
|
const lucidDefinitions = this.$getComputed(propertyKey);
|
|
1135
|
+
/* istanbul ignore if -- @preserve */
|
|
712
1136
|
if (!lucidDefinitions) return;
|
|
713
1137
|
const resourcefulModelComputedAccessorMetaSchema = {
|
|
714
1138
|
propertyKey,
|
|
@@ -736,6 +1160,7 @@ function withResourceful(options = {}) {
|
|
|
736
1160
|
computedAccessorDefinition.writable
|
|
737
1161
|
)
|
|
738
1162
|
};
|
|
1163
|
+
/* istanbul ignore if -- @preserve */
|
|
739
1164
|
if (!resourcefulModelComputedAccessorMetaSchema.canRead && !resourcefulModelComputedAccessorMetaSchema.canWrite)
|
|
740
1165
|
return;
|
|
741
1166
|
resourcefulModelMetaSchema.properties[propertyKey] = resourcefulModelComputedAccessorMetaSchema;
|
|
@@ -748,6 +1173,7 @@ function withResourceful(options = {}) {
|
|
|
748
1173
|
Array.from(this.$resourcefulRelationships.entries()),
|
|
749
1174
|
async ([propertyKey, relationshipDefinition]) => {
|
|
750
1175
|
const lucidDefinitions = this.$getRelation(propertyKey);
|
|
1176
|
+
/* istanbul ignore if -- @preserve */
|
|
751
1177
|
if (!lucidDefinitions) return;
|
|
752
1178
|
const resourcefulModelRelationshipMetaSchema = {
|
|
753
1179
|
propertyKey,
|
|
@@ -766,6 +1192,7 @@ function withResourceful(options = {}) {
|
|
|
766
1192
|
validator: joi.joi.forbidden(),
|
|
767
1193
|
relatedModel: relationshipDefinition.relatedModel
|
|
768
1194
|
};
|
|
1195
|
+
/* istanbul ignore if -- @preserve */
|
|
769
1196
|
if (!resourcefulModelRelationshipMetaSchema.canRead && !resourcefulModelRelationshipMetaSchema.canWrite)
|
|
770
1197
|
return;
|
|
771
1198
|
resourcefulModelMetaSchema.properties[propertyKey] = resourcefulModelRelationshipMetaSchema;
|
|
@@ -781,8 +1208,11 @@ function withResourceful(options = {}) {
|
|
|
781
1208
|
return joi.joi.any().forbidden();
|
|
782
1209
|
}
|
|
783
1210
|
const baseValidator = this.$getPropertyBaseValidator(datatype, nullable, kind, writable);
|
|
1211
|
+
let retValidator = baseValidator;
|
|
784
1212
|
if (Array.isArray(validationScopes) && validationScopes.length > 0) {
|
|
785
1213
|
validationScopes.forEach((validationScope) => {
|
|
1214
|
+
/* istanbul ignore if -- @preserve */
|
|
1215
|
+
if (retValidator !== baseValidator) return;
|
|
786
1216
|
try {
|
|
787
1217
|
validationScope(baseValidator);
|
|
788
1218
|
} catch (err) {
|
|
@@ -800,12 +1230,13 @@ function withResourceful(options = {}) {
|
|
|
800
1230
|
case "pass":
|
|
801
1231
|
break;
|
|
802
1232
|
case "fail":
|
|
803
|
-
|
|
1233
|
+
retValidator = joi.joi.any().forbidden();
|
|
1234
|
+
break;
|
|
804
1235
|
}
|
|
805
1236
|
}
|
|
806
1237
|
});
|
|
807
1238
|
}
|
|
808
|
-
return
|
|
1239
|
+
return retValidator;
|
|
809
1240
|
}
|
|
810
1241
|
static $getPropertyBaseValidator(datatype, nullable, kind, writable = false) {
|
|
811
1242
|
if ("relationship" === kind || "computedAccessor" === kind && !writable) {
|
|
@@ -813,132 +1244,178 @@ function withResourceful(options = {}) {
|
|
|
813
1244
|
}
|
|
814
1245
|
nullable = nullable || "boolean" === typeof datatype.nullable && datatype.nullable;
|
|
815
1246
|
switch (true) {
|
|
816
|
-
case datatype
|
|
817
|
-
|
|
818
|
-
const r = joi.joi.string().min(d.minLength).max(d.maxLength);
|
|
819
|
-
if (d.pattern) {
|
|
820
|
-
r.pattern(new RegExp(d.pattern));
|
|
821
|
-
}
|
|
822
|
-
if (d.enum) {
|
|
823
|
-
r.valid(...d.enum);
|
|
824
|
-
}
|
|
1247
|
+
case decorator_utils.isResourcefulDateType(datatype): {
|
|
1248
|
+
let r = joi.joi.date().iso();
|
|
825
1249
|
if (nullable) {
|
|
826
|
-
r.
|
|
1250
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
827
1251
|
}
|
|
828
1252
|
return r;
|
|
829
1253
|
}
|
|
830
|
-
case datatype
|
|
831
|
-
|
|
1254
|
+
case decorator_utils.isResourcefulDateTimeType(datatype): {
|
|
1255
|
+
let r = joi.joi.date().iso();
|
|
832
1256
|
if (nullable) {
|
|
833
|
-
r.
|
|
1257
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
834
1258
|
}
|
|
835
1259
|
return r;
|
|
836
1260
|
}
|
|
837
|
-
case datatype
|
|
838
|
-
const
|
|
1261
|
+
case decorator_utils.isResourcefulBinaryType(datatype): {
|
|
1262
|
+
const d = datatype;
|
|
1263
|
+
const min2 = "number" === typeof d.minLength ? Math.abs(d.minLength) : 0;
|
|
1264
|
+
const max2 = "number" === typeof d.maxLength ? Math.abs(d.maxLength) : Number.MAX_SAFE_INTEGER;
|
|
1265
|
+
let r = joi.joi.string().min(min2).max(max2);
|
|
839
1266
|
if (nullable) {
|
|
840
|
-
r.
|
|
1267
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
841
1268
|
}
|
|
842
1269
|
return r;
|
|
843
1270
|
}
|
|
844
|
-
case datatype
|
|
1271
|
+
case decorator_utils.isResourcefulStringType(datatype): {
|
|
845
1272
|
const d = datatype;
|
|
846
|
-
|
|
847
|
-
if (
|
|
848
|
-
r.
|
|
1273
|
+
let r = joi.joi.string().min(d.minLength).max(d.maxLength);
|
|
1274
|
+
if (d.pattern) {
|
|
1275
|
+
r = r.concat(r.pattern(new RegExp(d.pattern)));
|
|
849
1276
|
}
|
|
850
|
-
if (d.
|
|
851
|
-
r.
|
|
1277
|
+
if (d.enum) {
|
|
1278
|
+
r = r.concat(joi.joi.string().valid(...d.enum));
|
|
852
1279
|
}
|
|
853
|
-
if (
|
|
854
|
-
r.
|
|
1280
|
+
if (nullable) {
|
|
1281
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
855
1282
|
}
|
|
856
1283
|
return r;
|
|
857
1284
|
}
|
|
858
|
-
case datatype
|
|
1285
|
+
case decorator_utils.isResourcefulIntegerType(datatype): {
|
|
859
1286
|
const d = datatype;
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
r.allow(null);
|
|
863
|
-
}
|
|
864
|
-
r.multiple(d.multipleOf);
|
|
1287
|
+
let r = joi.joi.number();
|
|
1288
|
+
r = r.concat(joi.joi.number().integer().multiple(d.multipleOf));
|
|
865
1289
|
if (d.exclusiveMaximum) {
|
|
866
|
-
r.less(d.maximum);
|
|
1290
|
+
r = r.concat(joi.joi.number().less(d.maximum));
|
|
867
1291
|
} else {
|
|
868
|
-
r.max(d.maximum);
|
|
1292
|
+
r = r.concat(joi.joi.number().max(d.maximum));
|
|
869
1293
|
}
|
|
870
1294
|
if (d.exclusiveMinimum) {
|
|
871
|
-
r.greater(d.minimum);
|
|
1295
|
+
r = r.concat(joi.joi.number().greater(d.minimum));
|
|
872
1296
|
} else {
|
|
873
|
-
r.min(d.minimum);
|
|
1297
|
+
r = r.concat(joi.joi.number().min(d.minimum));
|
|
1298
|
+
}
|
|
1299
|
+
if (nullable) {
|
|
1300
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
874
1301
|
}
|
|
875
1302
|
return r;
|
|
876
1303
|
}
|
|
877
|
-
case datatype
|
|
1304
|
+
case decorator_utils.isResourcefulBigintType(datatype): {
|
|
878
1305
|
const d = datatype;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1306
|
+
if ("undefined" === typeof d.minimum) {
|
|
1307
|
+
d.minimum = 0n;
|
|
1308
|
+
}
|
|
1309
|
+
if ("undefined" === typeof d.maximum) {
|
|
1310
|
+
d.maximum = BigInt(Number.MAX_SAFE_INTEGER);
|
|
882
1311
|
}
|
|
883
|
-
|
|
1312
|
+
if ("undefined" === typeof d.multipleOf) {
|
|
1313
|
+
d.multipleOf = 1n;
|
|
1314
|
+
}
|
|
1315
|
+
let b = joi.joi.bigint();
|
|
1316
|
+
let i = joi.joi.number().integer();
|
|
1317
|
+
b = b.concat(
|
|
1318
|
+
joi.joi.bigint().multiple("number" === typeof d.multipleOf ? BigInt(d.multipleOf) : d.multipleOf)
|
|
1319
|
+
);
|
|
1320
|
+
i = i.concat(
|
|
1321
|
+
joi.joi.number().multiple(
|
|
1322
|
+
"bigint" === typeof d.multipleOf ? Number.parseInt(d.multipleOf.toString()) : d.multipleOf
|
|
1323
|
+
)
|
|
1324
|
+
);
|
|
884
1325
|
if (d.exclusiveMaximum) {
|
|
885
|
-
|
|
1326
|
+
if (["number", "bigint"].includes(typeof d.maximum)) {
|
|
1327
|
+
b = b.concat(
|
|
1328
|
+
joi.joi.bigint().less("number" === typeof d.maximum ? BigInt(d.maximum) : d.maximum)
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
i = i.concat(
|
|
1332
|
+
joi.joi.number().less(
|
|
1333
|
+
"bigint" === typeof d.maximum ? Number.parseInt(d.maximum.toString()) : d.maximum
|
|
1334
|
+
)
|
|
1335
|
+
);
|
|
886
1336
|
} else {
|
|
887
|
-
|
|
1337
|
+
if (["number", "bigint"].includes(typeof d.maximum)) {
|
|
1338
|
+
b = b.concat(
|
|
1339
|
+
joi.joi.bigint().max("number" === typeof d.maximum ? BigInt(d.maximum) : d.maximum)
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
i = i.concat(
|
|
1343
|
+
joi.joi.number().max(
|
|
1344
|
+
"bigint" === typeof d.maximum ? Number.parseInt(d.maximum.toString()) : d.maximum
|
|
1345
|
+
)
|
|
1346
|
+
);
|
|
888
1347
|
}
|
|
889
1348
|
if (d.exclusiveMinimum) {
|
|
890
|
-
|
|
1349
|
+
b = b.concat(
|
|
1350
|
+
joi.joi.bigint().greater("number" === typeof d.minimum ? BigInt(d.minimum) : d.minimum)
|
|
1351
|
+
);
|
|
1352
|
+
i = i.concat(
|
|
1353
|
+
joi.joi.number().greater(
|
|
1354
|
+
"bigint" === typeof d.minimum ? Number.parseInt(d.minimum.toString()) : d.minimum
|
|
1355
|
+
)
|
|
1356
|
+
);
|
|
891
1357
|
} else {
|
|
892
|
-
|
|
1358
|
+
b = b.concat(
|
|
1359
|
+
joi.joi.bigint().min("number" === typeof d.minimum ? BigInt(d.minimum) : d.minimum)
|
|
1360
|
+
);
|
|
1361
|
+
i = i.concat(
|
|
1362
|
+
joi.joi.number().min(
|
|
1363
|
+
"bigint" === typeof d.minimum ? Number.parseInt(d.minimum.toString()) : d.minimum
|
|
1364
|
+
)
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
let r = joi.joi.alternatives(b, i);
|
|
1368
|
+
if (nullable) {
|
|
1369
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
893
1370
|
}
|
|
894
1371
|
return r;
|
|
895
1372
|
}
|
|
896
|
-
case datatype
|
|
1373
|
+
case decorator_utils.isResourcefulUnsignedIntegerType(datatype): {
|
|
897
1374
|
const d = datatype;
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
r.allow(null);
|
|
901
|
-
}
|
|
902
|
-
r.multiple(d.multipleOf);
|
|
1375
|
+
let r = joi.joi.number().unsafe();
|
|
1376
|
+
r = r.concat(joi.joi.number().integer().multiple(d.multipleOf));
|
|
903
1377
|
if (d.exclusiveMaximum) {
|
|
904
|
-
r.less(d.maximum);
|
|
1378
|
+
r = r.concat(joi.joi.number().less(d.maximum));
|
|
905
1379
|
} else {
|
|
906
|
-
r.max(d.maximum);
|
|
1380
|
+
r = r.concat(joi.joi.number().max(d.maximum));
|
|
907
1381
|
}
|
|
908
1382
|
if (d.exclusiveMinimum) {
|
|
909
|
-
r.greater(d.minimum);
|
|
1383
|
+
r = r.concat(joi.joi.number().greater(d.minimum));
|
|
910
1384
|
} else {
|
|
911
|
-
r.min(d.minimum);
|
|
1385
|
+
r = r.concat(joi.joi.number().min(d.minimum));
|
|
1386
|
+
}
|
|
1387
|
+
if (nullable) {
|
|
1388
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
912
1389
|
}
|
|
913
1390
|
return r;
|
|
914
1391
|
}
|
|
915
|
-
case datatype
|
|
1392
|
+
case decorator_utils.isResourcefulNumberType(datatype): {
|
|
916
1393
|
const d = datatype;
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
r.allow(null);
|
|
920
|
-
}
|
|
921
|
-
r.integer().multiple(d.multipleOf);
|
|
1394
|
+
let r = joi.joi.number();
|
|
1395
|
+
r = r.concat(joi.joi.number().multiple(d.multipleOf));
|
|
922
1396
|
if (d.exclusiveMaximum) {
|
|
923
|
-
r.less(d.maximum);
|
|
1397
|
+
r = r.concat(joi.joi.number().less(d.maximum));
|
|
924
1398
|
} else {
|
|
925
|
-
r.max(d.maximum);
|
|
1399
|
+
r = r.concat(joi.joi.number().max(d.maximum));
|
|
926
1400
|
}
|
|
927
1401
|
if (d.exclusiveMinimum) {
|
|
928
|
-
r.greater(d.minimum);
|
|
1402
|
+
r = r.concat(joi.joi.number().greater(d.minimum));
|
|
929
1403
|
} else {
|
|
930
|
-
r.min(d.minimum);
|
|
1404
|
+
r = r.concat(joi.joi.number().min(d.minimum));
|
|
1405
|
+
}
|
|
1406
|
+
if (nullable) {
|
|
1407
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
931
1408
|
}
|
|
932
1409
|
return r;
|
|
933
1410
|
}
|
|
934
|
-
case datatype
|
|
935
|
-
|
|
1411
|
+
case decorator_utils.isResourcefulBooleanType(datatype): {
|
|
1412
|
+
let r = joi.joi.boolean();
|
|
936
1413
|
if (nullable) {
|
|
937
|
-
r.
|
|
1414
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
938
1415
|
}
|
|
939
1416
|
return r;
|
|
940
1417
|
}
|
|
941
|
-
case datatype
|
|
1418
|
+
case decorator_utils.isResourcefulObjectType(datatype): {
|
|
942
1419
|
const d = datatype;
|
|
943
1420
|
const objectSchema = {};
|
|
944
1421
|
Object.entries(d.properties).forEach(([propKey, propDef]) => {
|
|
@@ -952,7 +1429,7 @@ function withResourceful(options = {}) {
|
|
|
952
1429
|
!(item.readOnly || false)
|
|
953
1430
|
)
|
|
954
1431
|
);
|
|
955
|
-
propValidator = joi.joi.alternatives(
|
|
1432
|
+
propValidator = joi.joi.alternatives(...alternatives);
|
|
956
1433
|
} else if ("allOf" in propDef) {
|
|
957
1434
|
const schemas = propDef.allOf.map(
|
|
958
1435
|
(item) => this.$getPropertyBaseValidator(
|
|
@@ -972,7 +1449,7 @@ function withResourceful(options = {}) {
|
|
|
972
1449
|
!(item.readOnly || false)
|
|
973
1450
|
)
|
|
974
1451
|
);
|
|
975
|
-
propValidator = joi.joi.alternatives(
|
|
1452
|
+
propValidator = joi.joi.alternatives(...alternatives);
|
|
976
1453
|
} else if ("not" in propDef) {
|
|
977
1454
|
const notSchemas = propDef.not.map(
|
|
978
1455
|
(item) => this.$getPropertyBaseValidator(
|
|
@@ -982,7 +1459,12 @@ function withResourceful(options = {}) {
|
|
|
982
1459
|
!(item.readOnly || false)
|
|
983
1460
|
)
|
|
984
1461
|
);
|
|
985
|
-
propValidator = joi.joi.any()
|
|
1462
|
+
propValidator = joi.joi.any();
|
|
1463
|
+
notSchemas.forEach((notSchema) => {
|
|
1464
|
+
propValidator = propValidator.concat(
|
|
1465
|
+
joi.joi.when(notSchema, { then: joi.joi.forbidden() })
|
|
1466
|
+
);
|
|
1467
|
+
});
|
|
986
1468
|
} else {
|
|
987
1469
|
propValidator = this.$getPropertyBaseValidator(
|
|
988
1470
|
propDef,
|
|
@@ -990,44 +1472,46 @@ function withResourceful(options = {}) {
|
|
|
990
1472
|
kind,
|
|
991
1473
|
!(propDef.readOnly || false)
|
|
992
1474
|
);
|
|
1475
|
+
if (Array.isArray(d.required) && d.required.includes(propKey)) {
|
|
1476
|
+
propValidator = propValidator.concat(joi.joi.any().required());
|
|
1477
|
+
}
|
|
993
1478
|
}
|
|
994
1479
|
if (d.required && d.required.includes(propKey)) {
|
|
995
1480
|
propValidator.required();
|
|
996
1481
|
}
|
|
997
1482
|
objectSchema[propKey] = propValidator;
|
|
998
1483
|
});
|
|
999
|
-
|
|
1000
|
-
if (nullable) {
|
|
1001
|
-
r.allow(null);
|
|
1002
|
-
}
|
|
1484
|
+
let r = joi.joi.object(objectSchema);
|
|
1003
1485
|
if (d.minProperties !== void 0) {
|
|
1004
|
-
r.min(d.minProperties);
|
|
1486
|
+
r = r.concat(joi.joi.object().min(d.minProperties));
|
|
1005
1487
|
}
|
|
1006
1488
|
if (d.maxProperties !== void 0) {
|
|
1007
|
-
r.max(d.maxProperties);
|
|
1489
|
+
r = r.concat(joi.joi.object().max(d.maxProperties));
|
|
1008
1490
|
}
|
|
1009
1491
|
if (decorator_utils.isObject(d.additionalProperties)) {
|
|
1010
1492
|
const additionalPropertiesType = d.additionalProperties;
|
|
1011
|
-
r.
|
|
1012
|
-
joi.joi.
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1493
|
+
r = r.concat(
|
|
1494
|
+
joi.joi.object().pattern(
|
|
1495
|
+
joi.joi.string(),
|
|
1496
|
+
this.$getPropertyBaseValidator(
|
|
1497
|
+
additionalPropertiesType,
|
|
1498
|
+
additionalPropertiesType.nullable || false,
|
|
1499
|
+
kind,
|
|
1500
|
+
!(additionalPropertiesType.readOnly || false)
|
|
1501
|
+
)
|
|
1018
1502
|
)
|
|
1019
1503
|
);
|
|
1020
1504
|
} else {
|
|
1021
|
-
r.unknown(true === d.additionalProperties);
|
|
1505
|
+
r = r.concat(joi.joi.object().unknown(true === d.additionalProperties));
|
|
1506
|
+
}
|
|
1507
|
+
if (nullable) {
|
|
1508
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
1022
1509
|
}
|
|
1023
1510
|
return r;
|
|
1024
1511
|
}
|
|
1025
|
-
case datatype
|
|
1512
|
+
case decorator_utils.isResourcefulArrayType(datatype): {
|
|
1026
1513
|
const d = datatype;
|
|
1027
|
-
|
|
1028
|
-
if (nullable) {
|
|
1029
|
-
r.allow(null);
|
|
1030
|
-
}
|
|
1514
|
+
let r = joi.joi.array();
|
|
1031
1515
|
let itemValidator;
|
|
1032
1516
|
if ("oneOf" in d.items) {
|
|
1033
1517
|
const alternatives = d.items.oneOf.map(
|
|
@@ -1038,7 +1522,7 @@ function withResourceful(options = {}) {
|
|
|
1038
1522
|
!(item.readOnly || false)
|
|
1039
1523
|
)
|
|
1040
1524
|
);
|
|
1041
|
-
itemValidator = joi.joi.alternatives(
|
|
1525
|
+
itemValidator = joi.joi.alternatives(...alternatives);
|
|
1042
1526
|
} else if ("allOf" in d.items) {
|
|
1043
1527
|
const schemas = d.items.allOf.map(
|
|
1044
1528
|
(item) => this.$getPropertyBaseValidator(
|
|
@@ -1058,7 +1542,7 @@ function withResourceful(options = {}) {
|
|
|
1058
1542
|
!(item.readOnly || false)
|
|
1059
1543
|
)
|
|
1060
1544
|
);
|
|
1061
|
-
itemValidator = joi.joi.alternatives(
|
|
1545
|
+
itemValidator = joi.joi.alternatives(...alternatives);
|
|
1062
1546
|
} else if ("not" in d.items) {
|
|
1063
1547
|
const notSchemas = d.items.not.map(
|
|
1064
1548
|
(item) => this.$getPropertyBaseValidator(
|
|
@@ -1068,7 +1552,10 @@ function withResourceful(options = {}) {
|
|
|
1068
1552
|
!(item.readOnly || false)
|
|
1069
1553
|
)
|
|
1070
1554
|
);
|
|
1071
|
-
itemValidator = joi.joi.any()
|
|
1555
|
+
itemValidator = joi.joi.any();
|
|
1556
|
+
notSchemas.forEach((notSchema) => {
|
|
1557
|
+
itemValidator = itemValidator.concat(joi.joi.when(notSchema, { then: joi.joi.forbidden() }));
|
|
1558
|
+
});
|
|
1072
1559
|
} else {
|
|
1073
1560
|
itemValidator = this.$getPropertyBaseValidator(
|
|
1074
1561
|
d.items,
|
|
@@ -1077,15 +1564,18 @@ function withResourceful(options = {}) {
|
|
|
1077
1564
|
!(d.items.readOnly || false)
|
|
1078
1565
|
);
|
|
1079
1566
|
}
|
|
1080
|
-
r.items(itemValidator);
|
|
1567
|
+
r = r.concat(joi.joi.array().items(itemValidator));
|
|
1081
1568
|
if (d.minItems !== void 0) {
|
|
1082
|
-
r.min(d.minItems);
|
|
1569
|
+
r = r.concat(joi.joi.array().min(d.minItems));
|
|
1083
1570
|
}
|
|
1084
1571
|
if (d.maxItems !== void 0) {
|
|
1085
|
-
r.max(d.maxItems);
|
|
1572
|
+
r = r.concat(joi.joi.array().max(d.maxItems));
|
|
1086
1573
|
}
|
|
1087
1574
|
if (d.uniqueItems) {
|
|
1088
|
-
r.unique();
|
|
1575
|
+
r = r.concat(joi.joi.array().unique());
|
|
1576
|
+
}
|
|
1577
|
+
if (nullable) {
|
|
1578
|
+
return joi.joi.alternatives(r, joi.joi.any().valid(null));
|
|
1089
1579
|
}
|
|
1090
1580
|
return r;
|
|
1091
1581
|
}
|
|
@@ -1135,151 +1625,14 @@ function withResourceful(options = {}) {
|
|
|
1135
1625
|
);
|
|
1136
1626
|
return results.every((result) => result === true);
|
|
1137
1627
|
}
|
|
1138
|
-
static async $asOpenApiSchemaObject(ctx, app) {
|
|
1628
|
+
static async $asOpenApiSchemaObject(ctx, app, operation = "read") {
|
|
1139
1629
|
const resourcefulModelMetaSchema = await this.$getAsResourcefulForContext(ctx, app);
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
title: resourcefulModelMetaSchema.name,
|
|
1143
|
-
description: resourcefulModelMetaSchema.description,
|
|
1144
|
-
externalDocs: resourcefulModelMetaSchema.externalDocs,
|
|
1145
|
-
example: resourcefulModelMetaSchema.example,
|
|
1146
|
-
properties: {}
|
|
1147
|
-
};
|
|
1148
|
-
Object.entries(resourcefulModelMetaSchema.properties).forEach(
|
|
1149
|
-
([propertyKey, propertyMeta]) => {
|
|
1150
|
-
switch (propertyMeta.kind) {
|
|
1151
|
-
case "column": {
|
|
1152
|
-
ret.properties[propertyKey] = this.$resourcefulModelColumnMetaToOpenApiSchema(
|
|
1153
|
-
propertyKey,
|
|
1154
|
-
propertyMeta
|
|
1155
|
-
);
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
case "computedAccessor": {
|
|
1159
|
-
ret.properties[propertyKey] = this.$resourcefulModelComputedAccessorMetaToOpenApiSchema(
|
|
1160
|
-
propertyKey,
|
|
1161
|
-
propertyMeta
|
|
1162
|
-
);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
case "relationship": {
|
|
1166
|
-
const relatedSchema = this.$resourcefulModelRelationshipMetaToOpenApiSchema(
|
|
1167
|
-
propertyKey,
|
|
1168
|
-
propertyMeta
|
|
1169
|
-
);
|
|
1170
|
-
if (relatedSchema) {
|
|
1171
|
-
ret.properties[propertyKey] = relatedSchema;
|
|
1172
|
-
}
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
);
|
|
1178
|
-
const requiredPropSet = /* @__PURE__ */ new Set();
|
|
1179
|
-
Object.entries(ret.properties).forEach(([propertyKey, schema]) => {
|
|
1180
|
-
if (!decorator_utils.isObject(schema) || "$ref" in schema || !("nullable" in schema) || schema.nullable)
|
|
1181
|
-
return;
|
|
1182
|
-
requiredPropSet.add(propertyKey);
|
|
1183
|
-
});
|
|
1184
|
-
ret.required = Array.from(requiredPropSet);
|
|
1185
|
-
return ret;
|
|
1186
|
-
}
|
|
1187
|
-
static $getDefaultValueFromPropertyMeta(propertyMeta) {
|
|
1188
|
-
const joiDescription = propertyMeta.validator.describe();
|
|
1189
|
-
if (decorator_utils.isObject(joiDescription) && "flags" in joiDescription && decorator_utils.isObject(joiDescription.flags) && "default" in joiDescription.flags) {
|
|
1190
|
-
return joiDescription.flags.default;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
static $resourcefulModelColumnMetaToOpenApiSchema(propertyKey, propertyMeta) {
|
|
1194
|
-
const ret = stripUndefinedValuesFromObject({
|
|
1195
|
-
items: propertyMeta.definition.items,
|
|
1196
|
-
title: getFieldKey(propertyKey, propertyMeta.lucidDefinitions) || propertyKey,
|
|
1197
|
-
description: propertyMeta.definition.description,
|
|
1198
|
-
format: propertyMeta.definition.format,
|
|
1199
|
-
default: this.$getDefaultValueFromPropertyMeta(propertyMeta),
|
|
1200
|
-
multipleOf: propertyMeta.definition.multipleOf,
|
|
1201
|
-
maximum: propertyMeta.definition.maximum,
|
|
1202
|
-
exclusiveMaximum: propertyMeta.definition.exclusiveMaximum,
|
|
1203
|
-
minimum: propertyMeta.definition.minimum,
|
|
1204
|
-
exclusiveMinimum: propertyMeta.definition.exclusiveMinimum,
|
|
1205
|
-
maxLength: propertyMeta.definition.maxLength,
|
|
1206
|
-
minLength: propertyMeta.definition.minLength,
|
|
1207
|
-
pattern: propertyMeta.definition.pattern,
|
|
1208
|
-
additionalProperties: propertyMeta.definition.additionalProperties,
|
|
1209
|
-
maxItems: propertyMeta.definition.maxItems,
|
|
1210
|
-
minItems: propertyMeta.definition.minItems,
|
|
1211
|
-
uniqueItems: propertyMeta.definition.uniqueItems,
|
|
1212
|
-
maxProperties: propertyMeta.definition.maxProperties,
|
|
1213
|
-
minProperties: propertyMeta.definition.minProperties,
|
|
1214
|
-
required: propertyMeta.definition.required,
|
|
1215
|
-
enum: propertyMeta.definition.enum,
|
|
1216
|
-
properties: propertyMeta.definition.properties,
|
|
1217
|
-
allOf: propertyMeta.definition.allOf,
|
|
1218
|
-
oneOf: propertyMeta.definition.oneOf,
|
|
1219
|
-
anyOf: propertyMeta.definition.anyOf,
|
|
1220
|
-
not: propertyMeta.definition.not,
|
|
1221
|
-
nullable: propertyMeta.definition.nullable,
|
|
1222
|
-
readOnly: propertyMeta.canRead && !propertyMeta.canWrite,
|
|
1223
|
-
writeOnly: !propertyMeta.canRead && propertyMeta.canWrite,
|
|
1224
|
-
externalDocs: propertyMeta.definition.externalDocs,
|
|
1225
|
-
example: propertyMeta.definition.example,
|
|
1226
|
-
...propertyMeta.definition.type
|
|
1227
|
-
});
|
|
1228
|
-
return ret;
|
|
1229
|
-
}
|
|
1230
|
-
static $resourcefulModelComputedAccessorMetaToOpenApiSchema(propertyKey, propertyMeta) {
|
|
1231
|
-
const ret = stripUndefinedValuesFromObject({
|
|
1232
|
-
items: propertyMeta.definition.items,
|
|
1233
|
-
title: getFieldKey(propertyKey, propertyMeta.lucidDefinitions) || propertyKey,
|
|
1234
|
-
description: propertyMeta.definition.description,
|
|
1235
|
-
format: propertyMeta.definition.format,
|
|
1236
|
-
default: this.$getDefaultValueFromPropertyMeta(propertyMeta),
|
|
1237
|
-
multipleOf: propertyMeta.definition.multipleOf,
|
|
1238
|
-
maximum: propertyMeta.definition.maximum,
|
|
1239
|
-
exclusiveMaximum: propertyMeta.definition.exclusiveMaximum,
|
|
1240
|
-
minimum: propertyMeta.definition.minimum,
|
|
1241
|
-
exclusiveMinimum: propertyMeta.definition.exclusiveMinimum,
|
|
1242
|
-
maxLength: propertyMeta.definition.maxLength,
|
|
1243
|
-
minLength: propertyMeta.definition.minLength,
|
|
1244
|
-
pattern: propertyMeta.definition.pattern,
|
|
1245
|
-
additionalProperties: propertyMeta.definition.additionalProperties,
|
|
1246
|
-
maxItems: propertyMeta.definition.maxItems,
|
|
1247
|
-
minItems: propertyMeta.definition.minItems,
|
|
1248
|
-
uniqueItems: propertyMeta.definition.uniqueItems,
|
|
1249
|
-
maxProperties: propertyMeta.definition.maxProperties,
|
|
1250
|
-
minProperties: propertyMeta.definition.minProperties,
|
|
1251
|
-
required: propertyMeta.definition.required,
|
|
1252
|
-
enum: propertyMeta.definition.enum,
|
|
1253
|
-
properties: propertyMeta.definition.properties,
|
|
1254
|
-
allOf: propertyMeta.definition.allOf,
|
|
1255
|
-
oneOf: propertyMeta.definition.oneOf,
|
|
1256
|
-
anyOf: propertyMeta.definition.anyOf,
|
|
1257
|
-
not: propertyMeta.definition.not,
|
|
1258
|
-
nullable: propertyMeta.definition.nullable,
|
|
1259
|
-
readOnly: propertyMeta.canRead && !propertyMeta.canWrite,
|
|
1260
|
-
writeOnly: !propertyMeta.canRead && propertyMeta.canWrite,
|
|
1261
|
-
externalDocs: propertyMeta.definition.externalDocs,
|
|
1262
|
-
example: propertyMeta.definition.example,
|
|
1263
|
-
...propertyMeta.definition.type
|
|
1264
|
-
});
|
|
1265
|
-
return ret;
|
|
1266
|
-
}
|
|
1267
|
-
static $resourcefulModelRelationshipMetaToOpenApiSchema(_propertyKey, propertyMeta) {
|
|
1268
|
-
const relatedModel = propertyMeta.relatedModel();
|
|
1269
|
-
if (!relatedModel) {
|
|
1270
|
-
return void 0;
|
|
1271
|
-
}
|
|
1272
|
-
if (!decorator_utils.isResourcefulModel(relatedModel)) {
|
|
1273
|
-
return void 0;
|
|
1274
|
-
}
|
|
1275
|
-
const ref2 = `#/components/schemas/${relatedModel.$resourcefulName}`;
|
|
1276
|
-
return {
|
|
1277
|
-
$ref: ref2
|
|
1278
|
-
};
|
|
1630
|
+
const service = new OpenApiSchemaService();
|
|
1631
|
+
return service.generateModelSchema(operation, resourcefulModelMetaSchema, this);
|
|
1279
1632
|
}
|
|
1280
1633
|
static async $resourcefulCheckAccess(config) {
|
|
1281
1634
|
const { ctx, app, operation, instance } = config;
|
|
1282
|
-
const
|
|
1635
|
+
const allowedSerializedMap = /* @__PURE__ */ new Map();
|
|
1283
1636
|
const allowedColumnsMap = /* @__PURE__ */ new Map();
|
|
1284
1637
|
const mixinAccessControlFilters = validatedOptions.accessControlFilters[operation];
|
|
1285
1638
|
if (mixinAccessControlFilters) {
|
|
@@ -1291,37 +1644,72 @@ function withResourceful(options = {}) {
|
|
|
1291
1644
|
return {
|
|
1292
1645
|
isForbidden: true,
|
|
1293
1646
|
message: "Access denied by Mixin ACLs.",
|
|
1294
|
-
|
|
1647
|
+
allowedSerializedMap: void 0
|
|
1295
1648
|
};
|
|
1296
1649
|
}
|
|
1297
1650
|
}
|
|
1298
|
-
if (operation ===
|
|
1299
|
-
return {
|
|
1651
|
+
if (operation === errors.CRUDOperationsEnum.DELETE) {
|
|
1652
|
+
return {
|
|
1653
|
+
isForbidden: false,
|
|
1654
|
+
allowedSerializedMap,
|
|
1655
|
+
allowedColumnsMap
|
|
1656
|
+
};
|
|
1300
1657
|
}
|
|
1301
1658
|
const columnsOptions = this.$resourcefulColumns.values();
|
|
1302
|
-
const aclOperation =
|
|
1303
|
-
const
|
|
1659
|
+
const aclOperation = errors.operationCRUDToACL(operation);
|
|
1660
|
+
const addColumnOptionToallowedSerializedMap = (propertyKey) => {
|
|
1304
1661
|
const serializedName = this.$keys.attributesToSerialized.resolve(propertyKey);
|
|
1305
1662
|
const columnName = this.$keys.serializedToColumns.resolve(serializedName);
|
|
1306
|
-
if (serializedName) {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1663
|
+
if (aclOperation === errors.ACLOperationsEnum.WRITE && serializedName === null) {
|
|
1664
|
+
if (columnName) {
|
|
1665
|
+
allowedSerializedMap.set(columnName, true);
|
|
1666
|
+
allowedColumnsMap.set(columnName, true);
|
|
1667
|
+
}
|
|
1668
|
+
} else if ("string" === typeof serializedName) {
|
|
1669
|
+
allowedSerializedMap.set(serializedName, true);
|
|
1670
|
+
if (columnName) {
|
|
1671
|
+
allowedColumnsMap.set(columnName, true);
|
|
1672
|
+
}
|
|
1311
1673
|
}
|
|
1312
1674
|
};
|
|
1313
1675
|
for (const columnOptions of columnsOptions) {
|
|
1314
|
-
const propertyACLFilters = aclOperation ===
|
|
1676
|
+
const propertyACLFilters = aclOperation === errors.ACLOperationsEnum.READ ? columnOptions.readAccessControlFilters : columnOptions.writeAccessControlFilters;
|
|
1315
1677
|
if (!propertyACLFilters) {
|
|
1316
|
-
|
|
1678
|
+
addColumnOptionToallowedSerializedMap(columnOptions.propertyKey);
|
|
1317
1679
|
continue;
|
|
1318
1680
|
}
|
|
1319
1681
|
const isAllowed = await this.$evaluatePropertyAccess(ctx, app, propertyACLFilters);
|
|
1320
1682
|
if (isAllowed) {
|
|
1321
|
-
|
|
1683
|
+
addColumnOptionToallowedSerializedMap(columnOptions.propertyKey);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (aclOperation === errors.ACLOperationsEnum.READ) {
|
|
1687
|
+
const computedAccessorsOptions = this.$resourcefulComputedAccessors.values();
|
|
1688
|
+
for (const computedAccessorOptions of computedAccessorsOptions) {
|
|
1689
|
+
const propertyACLFilters = computedAccessorOptions.readAccessControlFilters;
|
|
1690
|
+
if (!propertyACLFilters) {
|
|
1691
|
+
allowedSerializedMap.set(computedAccessorOptions.propertyKey, true);
|
|
1692
|
+
continue;
|
|
1693
|
+
}
|
|
1694
|
+
const isAllowed = await this.$evaluatePropertyAccess(ctx, app, propertyACLFilters);
|
|
1695
|
+
if (isAllowed) {
|
|
1696
|
+
allowedSerializedMap.set(computedAccessorOptions.propertyKey, true);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
const relationshipOptions = this.$resourcefulRelationships.values();
|
|
1700
|
+
for (const relationshipOption of relationshipOptions) {
|
|
1701
|
+
const propertyACLFilters = relationshipOption.readAccessControlFilters;
|
|
1702
|
+
if (!propertyACLFilters) {
|
|
1703
|
+
allowedSerializedMap.set(relationshipOption.propertyKey, true);
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const isAllowed = await this.$evaluatePropertyAccess(ctx, app, propertyACLFilters);
|
|
1707
|
+
if (isAllowed) {
|
|
1708
|
+
allowedSerializedMap.set(relationshipOption.propertyKey, true);
|
|
1709
|
+
}
|
|
1322
1710
|
}
|
|
1323
1711
|
}
|
|
1324
|
-
const hasNoAllowedFields = !
|
|
1712
|
+
const hasNoAllowedFields = !allowedSerializedMap.size;
|
|
1325
1713
|
if (hasNoAllowedFields) {
|
|
1326
1714
|
return {
|
|
1327
1715
|
isForbidden: true,
|
|
@@ -1330,7 +1718,7 @@ function withResourceful(options = {}) {
|
|
|
1330
1718
|
}
|
|
1331
1719
|
return {
|
|
1332
1720
|
isForbidden: false,
|
|
1333
|
-
|
|
1721
|
+
allowedSerializedMap,
|
|
1334
1722
|
allowedColumnsMap
|
|
1335
1723
|
};
|
|
1336
1724
|
}
|
|
@@ -1343,6 +1731,40 @@ function withResourceful(options = {}) {
|
|
|
1343
1731
|
});
|
|
1344
1732
|
return ret;
|
|
1345
1733
|
}
|
|
1734
|
+
static $loadPossibilitiesForResourcefulRecord() {
|
|
1735
|
+
const allRelationships = Array.from(this.$resourcefulRelationships.entries()).filter(([propertyKey]) => {
|
|
1736
|
+
const relation = this.$getRelation(propertyKey);
|
|
1737
|
+
return !relation ? false : true;
|
|
1738
|
+
}).map(([propertyKey]) => propertyKey);
|
|
1739
|
+
const possibleRelationships = Array.from(this.$resourcefulRelationships.entries()).filter(([propertyKey]) => {
|
|
1740
|
+
const relation = this.$getRelation(propertyKey);
|
|
1741
|
+
if (!relation) return false;
|
|
1742
|
+
if (relation.serializeAs === null) return false;
|
|
1743
|
+
return relation.type === "belongsTo" || relation.type === "hasOne";
|
|
1744
|
+
}).map(([propertyKey]) => propertyKey);
|
|
1745
|
+
const possibleColumns = Array.from(this.$resourcefulColumns.entries()).map(([propertyKey]) => propertyKey).filter((propertyKey) => {
|
|
1746
|
+
const lucidColumnOptions = this.$getColumn(propertyKey);
|
|
1747
|
+
if (!lucidColumnOptions) return false;
|
|
1748
|
+
if (lucidColumnOptions.serializeAs === null) return false;
|
|
1749
|
+
return lucidColumnOptions.serializeAs !== null;
|
|
1750
|
+
});
|
|
1751
|
+
const possibleComputed = Array.from(this.$resourcefulComputedAccessors.entries()).map(([propertyKey]) => propertyKey).filter((propertyKey) => {
|
|
1752
|
+
const lucidComputedOptions = this.$getComputed(propertyKey);
|
|
1753
|
+
if (!lucidComputedOptions) return false;
|
|
1754
|
+
if (lucidComputedOptions.serializeAs === null) return false;
|
|
1755
|
+
return lucidComputedOptions.serializeAs !== null;
|
|
1756
|
+
});
|
|
1757
|
+
const possibleFields = Array.from(
|
|
1758
|
+
/* @__PURE__ */ new Set([...possibleColumns, ...possibleComputed, ...possibleRelationships])
|
|
1759
|
+
).map((prop) => this.$keys.attributesToSerialized.get(prop) || prop).filter((s) => "string" === typeof s);
|
|
1760
|
+
return {
|
|
1761
|
+
allRelationships,
|
|
1762
|
+
possibleRelationships,
|
|
1763
|
+
possibleColumns,
|
|
1764
|
+
possibleComputed,
|
|
1765
|
+
possibleFields
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1346
1768
|
/**
|
|
1347
1769
|
* Performs paginated listing and searching of model records with comprehensive filtering.
|
|
1348
1770
|
*
|
|
@@ -1402,22 +1824,33 @@ function withResourceful(options = {}) {
|
|
|
1402
1824
|
* );
|
|
1403
1825
|
* ```
|
|
1404
1826
|
*/
|
|
1405
|
-
static $formatResourcefulRecord(record) {
|
|
1827
|
+
static $formatResourcefulRecord(record, possibleFields) {
|
|
1828
|
+
if (null === record || void 0 === record) {
|
|
1829
|
+
return record;
|
|
1830
|
+
}
|
|
1406
1831
|
const result = {};
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1832
|
+
const { possibleFields: allPossibleFields } = this.$loadPossibilitiesForResourcefulRecord();
|
|
1833
|
+
possibleFields = possibleFields.filter((field) => allPossibleFields.includes(field));
|
|
1834
|
+
possibleFields.forEach((field) => {
|
|
1835
|
+
const attribute = this.$keys.serializedToAttributes.get(field) || field;
|
|
1836
|
+
if (!attribute) return;
|
|
1837
|
+
const value = record[attribute];
|
|
1838
|
+
if (value !== void 0) {
|
|
1839
|
+
const resourcefulRelationshipOptions = this.$resourcefulRelationships.get(attribute);
|
|
1840
|
+
if (resourcefulRelationshipOptions) {
|
|
1841
|
+
const relatedModel = resourcefulRelationshipOptions.relatedModel();
|
|
1842
|
+
if ("function" === typeof relatedModel.$formatResourcefulRecord && "function" === typeof relatedModel.$loadPossibilitiesForResourcefulRecord) {
|
|
1843
|
+
const { possibleFields: relatedPossibleFields } = relatedModel.$loadPossibilitiesForResourcefulRecord();
|
|
1844
|
+
result[field] = relatedModel.$formatResourcefulRecord(value, relatedPossibleFields);
|
|
1845
|
+
} else if ("function" === typeof value.serialize) {
|
|
1846
|
+
result[field] = value.serialize();
|
|
1847
|
+
}
|
|
1848
|
+
} else {
|
|
1849
|
+
result[field] = value;
|
|
1850
|
+
}
|
|
1416
1851
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
}
|
|
1420
|
-
return result;
|
|
1852
|
+
});
|
|
1853
|
+
return errors.stripUndefinedValuesFromObject(result);
|
|
1421
1854
|
}
|
|
1422
1855
|
static async $validatePayloadWithValidationGetters(ctx, app, payload, validationGetters) {
|
|
1423
1856
|
const schemas = await Promise.all(validationGetters.map((getter) => getter(ctx, app)));
|
|
@@ -1449,19 +1882,22 @@ function withResourceful(options = {}) {
|
|
|
1449
1882
|
if (!primaryKey) {
|
|
1450
1883
|
throw new errors.E_MISSING_PRIMARY_KEY_EXCEPTION(this.$resourcefulName);
|
|
1451
1884
|
}
|
|
1452
|
-
if (!
|
|
1885
|
+
if (!errors.isString(filter)) {
|
|
1453
1886
|
filter = "";
|
|
1454
1887
|
}
|
|
1455
|
-
fields =
|
|
1456
|
-
const { isForbidden,
|
|
1888
|
+
fields = errors.prepareFields(fields, primaryKey);
|
|
1889
|
+
const { isForbidden, allowedSerializedMap, message } = await this.$resourcefulCheckAccess({
|
|
1457
1890
|
ctx,
|
|
1458
1891
|
app,
|
|
1459
|
-
operation:
|
|
1892
|
+
operation: errors.CRUDOperationsEnum.LIST
|
|
1460
1893
|
});
|
|
1461
1894
|
if (isForbidden) {
|
|
1462
1895
|
throw new errors.E_FORBIDDEN(message);
|
|
1463
1896
|
}
|
|
1464
|
-
const
|
|
1897
|
+
const { allRelationships, possibleRelationships, possibleColumns, possibleComputed } = this.$loadPossibilitiesForResourcefulRecord();
|
|
1898
|
+
const possibleFields = Array.from(allowedSerializedMap.keys()).filter(
|
|
1899
|
+
(f) => possibleColumns.includes(f) || possibleComputed.includes(f) || !allRelationships.includes(f) || possibleRelationships.includes(f)
|
|
1900
|
+
);
|
|
1465
1901
|
if (possibleFields.length === 0) {
|
|
1466
1902
|
throw new errors.E_INVALID_COLUMN_ACCESS("No fields available for access");
|
|
1467
1903
|
}
|
|
@@ -1491,7 +1927,8 @@ function withResourceful(options = {}) {
|
|
|
1491
1927
|
this.$keys.serializedToColumns,
|
|
1492
1928
|
primaryKey,
|
|
1493
1929
|
this.table,
|
|
1494
|
-
possibleFields
|
|
1930
|
+
possibleFields,
|
|
1931
|
+
this
|
|
1495
1932
|
);
|
|
1496
1933
|
for (const scopeCallback of [
|
|
1497
1934
|
...this.$resourcefulQueryScopeCallbacks.list || [],
|
|
@@ -1499,28 +1936,47 @@ function withResourceful(options = {}) {
|
|
|
1499
1936
|
]) {
|
|
1500
1937
|
await scopeCallback(ctx, app, query);
|
|
1501
1938
|
}
|
|
1502
|
-
const columnsToSelect = validatedMethodOptions.fields.filter((field) => allowedFieldsMap.has(field)).map((field) => this.$keys.serializedToColumns.get(field));
|
|
1503
1939
|
const countQuery = query.clone();
|
|
1504
1940
|
const recordsQuery = query.clone();
|
|
1941
|
+
const privateKeyColumn = this.$keys.attributesToColumns.get(primaryKey);
|
|
1505
1942
|
countQuery.count("*", "total");
|
|
1506
|
-
recordsQuery.select(
|
|
1943
|
+
recordsQuery.select(privateKeyColumn).forPage(validatedMethodOptions.page, validatedMethodOptions.perPage);
|
|
1507
1944
|
if (Array.isArray(sort) && sort.length > 0) {
|
|
1508
1945
|
sort.forEach(([field, direction]) => {
|
|
1509
|
-
|
|
1510
|
-
|
|
1946
|
+
const columnName = this.$keys.serializedToColumns.get(field);
|
|
1947
|
+
if (allowedSerializedMap.has(field) && columnName) {
|
|
1948
|
+
recordsQuery.orderBy(columnName, direction);
|
|
1511
1949
|
}
|
|
1512
1950
|
});
|
|
1513
1951
|
}
|
|
1514
1952
|
const countQueryQuery = countQuery.toQuery();
|
|
1515
1953
|
const recordsQueryQuery = recordsQuery.toQuery();
|
|
1516
|
-
const [countResults,
|
|
1954
|
+
const [countResults, rawRecordIds] = await Promise.all([countQuery, recordsQuery]);
|
|
1517
1955
|
const totalRaw = Number((_a2 = countResults == null ? void 0 : countResults[0]) == null ? void 0 : _a2.total);
|
|
1956
|
+
const rawRecordsUnsortedQuery = this.query().whereIn(
|
|
1957
|
+
privateKeyColumn,
|
|
1958
|
+
rawRecordIds.map((r) => r[privateKeyColumn])
|
|
1959
|
+
);
|
|
1960
|
+
const relationsToSelect = validatedMethodOptions.fields.filter(
|
|
1961
|
+
(field) => allowedSerializedMap.has(field) && possibleRelationships.includes(field)
|
|
1962
|
+
);
|
|
1963
|
+
relationsToSelect.forEach((prop) => {
|
|
1964
|
+
rawRecordsUnsortedQuery.preload(prop);
|
|
1965
|
+
});
|
|
1966
|
+
const rawRecordsUnsorted = await rawRecordsUnsortedQuery;
|
|
1967
|
+
const rawRecords = rawRecordsUnsorted.sort((a2, b) => {
|
|
1968
|
+
const aId = a2.$getAttribute(primaryKey);
|
|
1969
|
+
const bId = b.$getAttribute(primaryKey);
|
|
1970
|
+
const rawRecordIdResultsIndexA = rawRecordIds.findIndex((r) => r[primaryKey] === aId);
|
|
1971
|
+
const rawRecordIdResultsIndexB = rawRecordIds.findIndex((r) => r[primaryKey] === bId);
|
|
1972
|
+
return rawRecordIdResultsIndexA - rawRecordIdResultsIndexB;
|
|
1973
|
+
});
|
|
1518
1974
|
return {
|
|
1519
1975
|
total: Number.isNaN(totalRaw) ? 0 : totalRaw,
|
|
1520
1976
|
page: validatedMethodOptions.page,
|
|
1521
1977
|
perPage: validatedMethodOptions.perPage,
|
|
1522
1978
|
records: rawRecords.map(
|
|
1523
|
-
(record) => this.$formatResourcefulRecord(record)
|
|
1979
|
+
(record) => this.$formatResourcefulRecord(record, possibleFields)
|
|
1524
1980
|
),
|
|
1525
1981
|
countQuery: countQueryQuery,
|
|
1526
1982
|
recordsQuery: recordsQueryQuery
|
|
@@ -1563,27 +2019,211 @@ function withResourceful(options = {}) {
|
|
|
1563
2019
|
throw new errors.E_RECORD_NOT_FOUND_EXCEPTION();
|
|
1564
2020
|
}
|
|
1565
2021
|
const rid = recordRaw[this.primaryKey];
|
|
1566
|
-
const
|
|
2022
|
+
const recordQuery = this.query().where(this.primaryKey, rid);
|
|
2023
|
+
const { possibleRelationships, possibleFields } = this.$loadPossibilitiesForResourcefulRecord();
|
|
2024
|
+
possibleRelationships.forEach((prop) => {
|
|
2025
|
+
recordQuery.preload(prop);
|
|
2026
|
+
});
|
|
2027
|
+
const record = await recordQuery.first();
|
|
1567
2028
|
if (!record) {
|
|
1568
2029
|
throw new errors.E_RECORD_NOT_FOUND_EXCEPTION();
|
|
1569
2030
|
}
|
|
1570
|
-
const { isForbidden,
|
|
2031
|
+
const { isForbidden, allowedSerializedMap, message } = await this.$resourcefulCheckAccess({
|
|
1571
2032
|
ctx,
|
|
1572
2033
|
app,
|
|
1573
2034
|
instance: record,
|
|
1574
|
-
operation:
|
|
2035
|
+
operation: errors.CRUDOperationsEnum.READ
|
|
1575
2036
|
});
|
|
1576
2037
|
if (isForbidden) {
|
|
1577
2038
|
throw new errors.E_FORBIDDEN(message);
|
|
1578
2039
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
2040
|
+
return this.$formatResourcefulRecord(
|
|
2041
|
+
record,
|
|
2042
|
+
possibleFields.filter((f) => allowedSerializedMap.get(f) === true)
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Loads a specific relationship for a model record with pagination, filtering, and access control.
|
|
2047
|
+
*
|
|
2048
|
+
* This method provides paginated relationship loading by leveraging the related model's
|
|
2049
|
+
* $onResourcefulIndex method with automatically generated relationship constraints.
|
|
2050
|
+
* It supports full filtering, sorting, and pagination capabilities while maintaining
|
|
2051
|
+
* proper access control and scoping.
|
|
2052
|
+
*
|
|
2053
|
+
* The method automatically generates query scope hooks to constrain results to only
|
|
2054
|
+
* records related to the specified parent record, preventing unauthorized data access
|
|
2055
|
+
* and ensuring proper relationship boundaries are maintained.
|
|
2056
|
+
*
|
|
2057
|
+
* @param uid - The unique identifier of the parent record
|
|
2058
|
+
* @param relationshipKey - The name of the relationship property to load
|
|
2059
|
+
* @param filter - Lucene-style query string for filtering related records
|
|
2060
|
+
* @param page - The page number for pagination (must be ≥ 1)
|
|
2061
|
+
* @param perPage - Number of records per page (must be ≥ 1 and ≤ 100)
|
|
2062
|
+
* @param fields - Array of field names to include in the response from the related model
|
|
2063
|
+
* @param sort - Array of sort criteria as [field, direction] tuples
|
|
2064
|
+
* @param ctx - HTTP context containing request information and authentication
|
|
2065
|
+
* @param app - Application service instance for accessing app-level services
|
|
2066
|
+
* @param hooks - Optional array of additional query scope callbacks
|
|
2067
|
+
*
|
|
2068
|
+
* @returns Promise resolving to paginated relationship results with the same structure as $onResourcefulIndex
|
|
2069
|
+
*
|
|
2070
|
+
* @throws {E_MISSING_PRIMARY_KEY_EXCEPTION} When the model has no identifiable primary key
|
|
2071
|
+
* @throws {E_INVALID_RELATIONSHIP_EXCEPTION} When the specified relationship doesn't exist or isn't resourceful
|
|
2072
|
+
* @throws {E_FORBIDDEN} When access is denied by model-level or field-level ACL filters
|
|
2073
|
+
* @throws {E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION} When input validation fails
|
|
2074
|
+
*
|
|
2075
|
+
* @example
|
|
2076
|
+
* ```typescript
|
|
2077
|
+
* // Load user's posts with filtering and pagination
|
|
2078
|
+
* const userPosts = await Post.$onResourcefulReadRelationship(
|
|
2079
|
+
* 123, // user ID
|
|
2080
|
+
* 'posts', // relationship name
|
|
2081
|
+
* 'status:published', // filter
|
|
2082
|
+
* 1, // page
|
|
2083
|
+
* 10, // perPage
|
|
2084
|
+
* ['id', 'title'], // fields
|
|
2085
|
+
* [['createdAt', 'desc']], // sort
|
|
2086
|
+
* ctx,
|
|
2087
|
+
* app
|
|
2088
|
+
* );
|
|
2089
|
+
*
|
|
2090
|
+
* // Load user's skills (many-to-many)
|
|
2091
|
+
* const userSkills = await Skill.$onResourcefulReadRelationship(
|
|
2092
|
+
* 123,
|
|
2093
|
+
* 'skills',
|
|
2094
|
+
* null, // no filter
|
|
2095
|
+
* 1,
|
|
2096
|
+
* 50,
|
|
2097
|
+
* null, // all fields
|
|
2098
|
+
* null, // default sort
|
|
2099
|
+
* ctx,
|
|
2100
|
+
* app
|
|
2101
|
+
* );
|
|
2102
|
+
* ```
|
|
2103
|
+
*/
|
|
2104
|
+
static async $onResourcefulReadRelationship(uid, relationshipKey, filter, page, perPage, fields, sort, ctx, app, hooks = []) {
|
|
2105
|
+
const primaryKey = this.$getPrivateKeyAttribute();
|
|
2106
|
+
if (!primaryKey) {
|
|
2107
|
+
throw new errors.E_MISSING_PRIMARY_KEY_EXCEPTION(this.$resourcefulName);
|
|
2108
|
+
}
|
|
2109
|
+
const relationshipDefinition = this.$resourcefulRelationships.get(relationshipKey);
|
|
2110
|
+
if (!relationshipDefinition) {
|
|
2111
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2112
|
+
`Relationship '${relationshipKey}' not found on model '${this.$resourcefulName}'`
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
const lucidRelationshipDefinition = this.$getRelation(relationshipKey);
|
|
2116
|
+
if (!lucidRelationshipDefinition) {
|
|
2117
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2118
|
+
`Lucid relationship '${relationshipKey}' not found on model '${this.$resourcefulName}'`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
const RelatedModel = relationshipDefinition.relatedModel();
|
|
2122
|
+
if (typeof RelatedModel.$onResourcefulIndex !== "function") {
|
|
2123
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2124
|
+
`Related model for '${relationshipKey}' does not implement resourceful operations`
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
const relationshipHook = async (_hookCtx, _hookApp, query) => {
|
|
2128
|
+
const relationshipType = lucidRelationshipDefinition.type;
|
|
2129
|
+
switch (relationshipType) {
|
|
2130
|
+
case "hasMany":
|
|
2131
|
+
{
|
|
2132
|
+
const foreignKey = lucidRelationshipDefinition.foreignKey;
|
|
2133
|
+
if (!foreignKey) {
|
|
2134
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2135
|
+
`HasMany relationship '${relationshipKey}' missing foreignKey`
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
const foreignColumn = RelatedModel.$keys.attributesToColumns.get(foreignKey);
|
|
2139
|
+
if (!foreignColumn) {
|
|
2140
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2141
|
+
`Foreign key '${foreignKey}' not found in related model columns`
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
query.where(foreignColumn, uid);
|
|
2145
|
+
}
|
|
2146
|
+
break;
|
|
2147
|
+
case "manyToMany":
|
|
2148
|
+
{
|
|
2149
|
+
const pivotTable = lucidRelationshipDefinition.pivotTable;
|
|
2150
|
+
const localKey = lucidRelationshipDefinition.localKey;
|
|
2151
|
+
const relatedKey = lucidRelationshipDefinition.relatedKey;
|
|
2152
|
+
const pivotForeignKey = lucidRelationshipDefinition.pivotForeignKey;
|
|
2153
|
+
const pivotRelatedForeignKey = lucidRelationshipDefinition.pivotRelatedForeignKey;
|
|
2154
|
+
if (!pivotTable || !localKey || !relatedKey || !pivotForeignKey || !pivotRelatedForeignKey) {
|
|
2155
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2156
|
+
`ManyToMany relationship '${relationshipKey}' missing required pivot configuration`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
const relatedColumn = RelatedModel.$keys.attributesToColumns.get(relatedKey);
|
|
2160
|
+
if (!relatedColumn) {
|
|
2161
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2162
|
+
`Related key '${relatedKey}' not found in related model columns`
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
query.whereExists((subQuery) => {
|
|
2166
|
+
subQuery.from(pivotTable).select(1).whereColumn(
|
|
2167
|
+
`${pivotTable}.${pivotRelatedForeignKey}`,
|
|
2168
|
+
`${RelatedModel.table}.${relatedColumn}`
|
|
2169
|
+
).where(`${pivotTable}.${pivotForeignKey}`, uid);
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
break;
|
|
2173
|
+
case "hasManyThrough":
|
|
2174
|
+
{
|
|
2175
|
+
const throughModel = lucidRelationshipDefinition.options.throughModel ? lucidRelationshipDefinition.options.throughModel() : void 0;
|
|
2176
|
+
const foreignKey = lucidRelationshipDefinition.options.foreignKey || RelatedModel.primaryKey;
|
|
2177
|
+
const localKey = lucidRelationshipDefinition.options.localKey || this.primaryKey;
|
|
2178
|
+
const throughForeignKey = lucidRelationshipDefinition.options.throughForeignKey;
|
|
2179
|
+
const throughLocalKey = lucidRelationshipDefinition.options.throughLocalKey || (throughModel == null ? void 0 : throughModel.primaryKey);
|
|
2180
|
+
if (!throughModel || !foreignKey || !localKey || !throughForeignKey || !throughLocalKey) {
|
|
2181
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2182
|
+
`HasManyThrough relationship '${relationshipKey}' missing required through configuration`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
const foreignColumn = RelatedModel.$keys.attributesToColumns.get(foreignKey);
|
|
2186
|
+
const localColumn = this.$keys.attributesToColumns.get(localKey);
|
|
2187
|
+
const throughForeignColumn = RelatedModel.$keys.attributesToColumns.get(throughForeignKey);
|
|
2188
|
+
const throughLocalColumn = throughModel.$keys.attributesToColumns.get(throughLocalKey);
|
|
2189
|
+
if (!throughLocalColumn || !throughForeignColumn || !foreignColumn || !localColumn) {
|
|
2190
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2191
|
+
`HasManyThrough relationship '${relationshipKey}' has invalid key mappings`
|
|
2192
|
+
);
|
|
2193
|
+
}
|
|
2194
|
+
query.whereExists((subQuery) => {
|
|
2195
|
+
subQuery.from(throughModel.table).select(1).where(`${throughModel.table}.${throughLocalColumn}`, uid).whereColumn(
|
|
2196
|
+
`${throughModel.table}.${foreignColumn}`,
|
|
2197
|
+
`${RelatedModel.table}.${throughForeignColumn}`
|
|
2198
|
+
).limit(1);
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
break;
|
|
2202
|
+
default:
|
|
2203
|
+
throw new errors.E_INVALID_RELATIONSHIP_EXCEPTION(
|
|
2204
|
+
`Relationship type '${relationshipType}' is not supported for paginated loading`
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
const allHooks = [relationshipHook, ...hooks];
|
|
2209
|
+
try {
|
|
2210
|
+
return RelatedModel.$onResourcefulIndex(
|
|
2211
|
+
filter,
|
|
2212
|
+
page,
|
|
2213
|
+
perPage,
|
|
2214
|
+
fields,
|
|
2215
|
+
sort,
|
|
2216
|
+
ctx,
|
|
2217
|
+
app,
|
|
2218
|
+
allHooks
|
|
2219
|
+
);
|
|
2220
|
+
} catch (err) {
|
|
2221
|
+
if (err instanceof errors.E_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION) {
|
|
2222
|
+
throw new errors.E_INVALID_RESOUREFUL_READ_RELATIONSHIP_REQUEST_EXCEPTION(err);
|
|
2223
|
+
} else {
|
|
2224
|
+
throw err;
|
|
1584
2225
|
}
|
|
1585
2226
|
}
|
|
1586
|
-
return recordObjectWithAllowedFields;
|
|
1587
2227
|
}
|
|
1588
2228
|
/**
|
|
1589
2229
|
* Creates a new model record with payload validation and access control.
|
|
@@ -1624,23 +2264,31 @@ function withResourceful(options = {}) {
|
|
|
1624
2264
|
throw new errors.E_FORBIDDEN_PAYLOAD_EXCEPTION(payloadSchemaGettersError);
|
|
1625
2265
|
}
|
|
1626
2266
|
}
|
|
1627
|
-
const { isForbidden,
|
|
2267
|
+
const { isForbidden, allowedSerializedMap, message } = await this.$resourcefulCheckAccess({
|
|
1628
2268
|
ctx,
|
|
1629
2269
|
app,
|
|
1630
|
-
operation:
|
|
2270
|
+
operation: errors.CRUDOperationsEnum.CREATE
|
|
1631
2271
|
});
|
|
1632
2272
|
if (isForbidden) {
|
|
1633
2273
|
throw new errors.E_FORBIDDEN(message);
|
|
1634
2274
|
}
|
|
1635
2275
|
const preparedPayload = {};
|
|
1636
2276
|
for (const [key, value] of Object.entries(payload)) {
|
|
1637
|
-
|
|
2277
|
+
let attribute = this.$keys.serializedToAttributes.get(key);
|
|
1638
2278
|
const isValueNull = value === null || value === void 0;
|
|
1639
2279
|
if (!attribute) {
|
|
1640
|
-
|
|
2280
|
+
const columnExists = typeof this.$keys.attributesToColumns.get(key) !== "undefined";
|
|
2281
|
+
if (columnExists) {
|
|
2282
|
+
attribute = key;
|
|
2283
|
+
} else {
|
|
2284
|
+
continue;
|
|
2285
|
+
}
|
|
1641
2286
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
2287
|
+
const isAllowedBySerializedName = allowedSerializedMap.has(key);
|
|
2288
|
+
const isAllowedByColumnName = allowedSerializedMap.has(attribute);
|
|
2289
|
+
const isAllowed = isAllowedBySerializedName || isAllowedByColumnName;
|
|
2290
|
+
if (!isValueNull && !isAllowed) {
|
|
2291
|
+
throw new errors.E_FORBIDDEN(`User does not has write access to field ${key}.`);
|
|
1644
2292
|
}
|
|
1645
2293
|
preparedPayload[attribute] = value;
|
|
1646
2294
|
}
|
|
@@ -1700,23 +2348,31 @@ function withResourceful(options = {}) {
|
|
|
1700
2348
|
if (!record) {
|
|
1701
2349
|
throw new errors.E_RECORD_NOT_FOUND_EXCEPTION();
|
|
1702
2350
|
}
|
|
1703
|
-
const { isForbidden,
|
|
2351
|
+
const { isForbidden, allowedSerializedMap, message } = await this.$resourcefulCheckAccess({
|
|
1704
2352
|
ctx,
|
|
1705
2353
|
app,
|
|
1706
2354
|
instance: record,
|
|
1707
|
-
operation:
|
|
2355
|
+
operation: errors.CRUDOperationsEnum.UPDATE
|
|
1708
2356
|
});
|
|
1709
2357
|
if (isForbidden) {
|
|
1710
2358
|
throw new errors.E_FORBIDDEN(message);
|
|
1711
2359
|
}
|
|
1712
2360
|
const preparedPayload = {};
|
|
1713
|
-
for (const [
|
|
1714
|
-
|
|
2361
|
+
for (const [serializedKey, value] of Object.entries(payload)) {
|
|
2362
|
+
let attribute = this.$keys.serializedToAttributes.get(serializedKey);
|
|
1715
2363
|
const isValueNull = value === null || value === void 0;
|
|
1716
2364
|
if (!attribute) {
|
|
1717
|
-
|
|
2365
|
+
const columnExists = typeof this.$keys.attributesToColumns.get(serializedKey) !== "undefined";
|
|
2366
|
+
if (columnExists) {
|
|
2367
|
+
attribute = serializedKey;
|
|
2368
|
+
} else {
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
1718
2371
|
}
|
|
1719
|
-
|
|
2372
|
+
const isAllowedBySerializedName = allowedSerializedMap.has(serializedKey);
|
|
2373
|
+
const isAllowedByColumnName = allowedSerializedMap.has(attribute);
|
|
2374
|
+
const isAllowed = isAllowedBySerializedName || isAllowedByColumnName;
|
|
2375
|
+
if (!isValueNull && !isAllowed) {
|
|
1720
2376
|
throw new errors.E_FORBIDDEN(`User does not has write access to field ${attribute}.`);
|
|
1721
2377
|
}
|
|
1722
2378
|
preparedPayload[attribute] = value;
|
|
@@ -1762,18 +2418,88 @@ function withResourceful(options = {}) {
|
|
|
1762
2418
|
if (!rawRecord) {
|
|
1763
2419
|
throw new errors.E_RECORD_NOT_FOUND_EXCEPTION();
|
|
1764
2420
|
}
|
|
1765
|
-
const
|
|
2421
|
+
const { possibleFields } = this.$loadPossibilitiesForResourcefulRecord();
|
|
2422
|
+
const record = new this().merge(this.$formatResourcefulRecord(rawRecord, possibleFields));
|
|
1766
2423
|
const { isForbidden, message } = await this.$resourcefulCheckAccess({
|
|
1767
2424
|
ctx,
|
|
1768
2425
|
app,
|
|
1769
2426
|
instance: record,
|
|
1770
|
-
operation:
|
|
2427
|
+
operation: errors.CRUDOperationsEnum.DELETE
|
|
1771
2428
|
});
|
|
1772
2429
|
if (isForbidden) {
|
|
1773
2430
|
throw new errors.E_FORBIDDEN(message);
|
|
1774
2431
|
}
|
|
1775
2432
|
await record.delete();
|
|
1776
2433
|
}
|
|
2434
|
+
static async $onResourcefulBulkUpdate(filter, payload, ctx, app, hooks = {}) {
|
|
2435
|
+
const primaryKey = this.$getPrivateKeyAttribute();
|
|
2436
|
+
if (!primaryKey) {
|
|
2437
|
+
throw new errors.E_MISSING_PRIMARY_KEY_EXCEPTION(this.$resourcefulName);
|
|
2438
|
+
}
|
|
2439
|
+
const ctor = this;
|
|
2440
|
+
async function* resourcefulIndexIteratorForUpdate(f) {
|
|
2441
|
+
let page = 1;
|
|
2442
|
+
let pages = 0;
|
|
2443
|
+
const pk = ctor.$getPrivateKeyAttribute();
|
|
2444
|
+
if (!pk) return;
|
|
2445
|
+
const whileAbortController = new AbortController();
|
|
2446
|
+
while ((pages === 0 || page <= pages) && whileAbortController.signal.aborted === false) {
|
|
2447
|
+
try {
|
|
2448
|
+
const result = await ctor.$onResourcefulIndex(
|
|
2449
|
+
f,
|
|
2450
|
+
page,
|
|
2451
|
+
100,
|
|
2452
|
+
[pk],
|
|
2453
|
+
[[pk, "asc"]],
|
|
2454
|
+
ctx,
|
|
2455
|
+
app,
|
|
2456
|
+
hooks.queryScopeCallbacks || []
|
|
2457
|
+
);
|
|
2458
|
+
if (result.total === 0) {
|
|
2459
|
+
whileAbortController.abort();
|
|
2460
|
+
break;
|
|
2461
|
+
}
|
|
2462
|
+
pages = Math.ceil(result.total / result.perPage);
|
|
2463
|
+
page++;
|
|
2464
|
+
yield result.records;
|
|
2465
|
+
} catch (err) {
|
|
2466
|
+
whileAbortController.abort();
|
|
2467
|
+
if (err instanceof Error) {
|
|
2468
|
+
throw err;
|
|
2469
|
+
} else {
|
|
2470
|
+
throw new errors.E_BULK_UPDATE_SEARCH_UNKNOWN_EXCEPTION();
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
const uids = /* @__PURE__ */ new Set();
|
|
2476
|
+
for await (const records of resourcefulIndexIteratorForUpdate(filter)) {
|
|
2477
|
+
for (const record of records) {
|
|
2478
|
+
const uid = record[primaryKey];
|
|
2479
|
+
if (uid !== void 0 && uid !== null) {
|
|
2480
|
+
uids.add(uid);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
return await this.$onResourcefulBulkUpdateByUid(Array.from(uids), payload, ctx, app, hooks);
|
|
2485
|
+
}
|
|
2486
|
+
static async $onResourcefulBulkUpdateByUid(uids, payload, ctx, app, hooks, concurrency = 5) {
|
|
2487
|
+
const results = {};
|
|
2488
|
+
/* istanbul ignore next -- @preserve */
|
|
2489
|
+
await pMap(
|
|
2490
|
+
uids,
|
|
2491
|
+
async (uid) => {
|
|
2492
|
+
try {
|
|
2493
|
+
results[uid] = await this.$onResourcefulUpdate(uid, payload, ctx, app, hooks);
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
const e = err instanceof Error ? err : new Error("Unknown error during update");
|
|
2496
|
+
results[uid] = e;
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
{ concurrency }
|
|
2500
|
+
);
|
|
2501
|
+
return results;
|
|
2502
|
+
}
|
|
1777
2503
|
}
|
|
1778
2504
|
__publicField(ResourcefulModel, "$resourcefulColumns", resourcefulColumns);
|
|
1779
2505
|
__publicField(ResourcefulModel, "$resourcefulRelationships", resourcefulRelationships);
|
|
@@ -11857,10 +12583,12 @@ const resourcefulColumnOptionsSchema = joi.joi.object({
|
|
|
11857
12583
|
type: resourcefulDataTypeSchema.required()
|
|
11858
12584
|
});
|
|
11859
12585
|
const dataTypeColumnOptionsSchema = joi.joi.object({
|
|
11860
|
-
// Lucid ColumnOptions (
|
|
12586
|
+
// Lucid ColumnOptions (full) - now including 'prepare' and 'consume'
|
|
11861
12587
|
columnName: joi.joi.string().optional(),
|
|
11862
12588
|
serializeAs: joi.joi.alternatives().try(joi.joi.string(), joi.joi.valid(null)).optional(),
|
|
11863
12589
|
serialize: joi.joi.function().optional(),
|
|
12590
|
+
consume: joi.joi.function().optional(),
|
|
12591
|
+
prepare: joi.joi.function().optional(),
|
|
11864
12592
|
meta: joi.joi.object().optional(),
|
|
11865
12593
|
isPrimary: joi.joi.boolean().default(false),
|
|
11866
12594
|
hasDefaultValue: joi.joi.boolean().default(false),
|
|
@@ -12723,7 +13451,7 @@ function resourcefulHasManyThrough(model, options = {}) {
|
|
|
12723
13451
|
map.set(propertyKey, opts);
|
|
12724
13452
|
};
|
|
12725
13453
|
}
|
|
12726
|
-
const version = "0.1.0-master-
|
|
13454
|
+
const version = "0.1.0-master-3ec631a4";
|
|
12727
13455
|
exports.errors = errors.errors;
|
|
12728
13456
|
exports.definitions = definitions.definitions;
|
|
12729
13457
|
exports.resourcefulBelongsTo = resourcefulBelongsTo;
|