@nocobase/database 1.9.0-beta.1 → 1.9.0-beta.11
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/lib/collection.d.ts +7 -0
- package/lib/collection.js +67 -0
- package/lib/cursor-builder.js +4 -3
- package/lib/database.d.ts +2 -1
- package/lib/database.js +31 -3
- package/lib/dialects/mysql-dialect.d.ts +2 -1
- package/lib/dialects/mysql-dialect.js +5 -2
- package/lib/eager-loading/eager-loading-tree.js +2 -20
- package/lib/fields/field.d.ts +25 -2
- package/lib/fields/index.d.ts +3 -1
- package/lib/fields/index.js +3 -1
- package/lib/fields/number-field.d.ts +1 -1
- package/lib/fields/snowflake-id-field.d.ts +29 -0
- package/lib/fields/snowflake-id-field.js +79 -0
- package/lib/fields/string-field.d.ts +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +8 -0
- package/lib/inherited-sync-runner.js +5 -0
- package/lib/interfaces/number-interface.d.ts +1 -0
- package/lib/interfaces/number-interface.js +28 -0
- package/lib/magic-attribute-model.js +3 -0
- package/lib/operators/empty.d.ts +1 -4
- package/lib/operators/index.d.ts +1 -2
- package/lib/options-parser.js +1 -1
- package/lib/query-interface/postgres-query-interface.d.ts +3 -1
- package/lib/query-interface/postgres-query-interface.js +9 -2
- package/lib/query-interface/query-interface.d.ts +3 -1
- package/lib/relation-repository/belongs-to-many-repository.js +5 -0
- package/lib/relation-repository/relation-repository.js +1 -0
- package/lib/repository.d.ts +3 -0
- package/lib/repository.js +87 -2
- package/lib/update-associations.js +25 -0
- package/lib/utils/field-validation.d.ts +59 -0
- package/lib/utils/field-validation.js +143 -0
- package/lib/utils/filter-include.d.ts +55 -0
- package/lib/utils/filter-include.js +118 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +22 -0
- package/lib/view/view-inference.js +1 -0
- package/package.json +5 -4
|
@@ -110,8 +110,15 @@ const _PostgresQueryInterface = class _PostgresQueryInterface extends import_que
|
|
|
110
110
|
const results = await this.db.sequelize.query(sql, { type: "SELECT", transaction });
|
|
111
111
|
return results[0]["exists"];
|
|
112
112
|
}
|
|
113
|
-
async listViews() {
|
|
114
|
-
|
|
113
|
+
async listViews(options) {
|
|
114
|
+
var _a;
|
|
115
|
+
const targetSchema = (options == null ? void 0 : options.schema) || ((_a = this.db.options) == null ? void 0 : _a.schema) || "public";
|
|
116
|
+
const sql = targetSchema ? `
|
|
117
|
+
SELECT viewname as name, definition, schemaname as schema
|
|
118
|
+
FROM pg_views
|
|
119
|
+
WHERE schemaname = '${targetSchema}'
|
|
120
|
+
ORDER BY viewname;
|
|
121
|
+
` : `
|
|
115
122
|
SELECT viewname as name, definition, schemaname as schema
|
|
116
123
|
FROM pg_views
|
|
117
124
|
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
|
|
@@ -18,7 +18,9 @@ export default abstract class QueryInterface {
|
|
|
18
18
|
sequelizeQueryInterface: SequelizeQueryInterface;
|
|
19
19
|
protected constructor(db: Database);
|
|
20
20
|
abstract collectionTableExists(collection: Collection, options?: Transactionable): Promise<boolean>;
|
|
21
|
-
abstract listViews(
|
|
21
|
+
abstract listViews(options?: {
|
|
22
|
+
schema?: string;
|
|
23
|
+
}): any;
|
|
22
24
|
abstract viewDef(viewName: string): Promise<string>;
|
|
23
25
|
abstract viewColumnUsage(options: {
|
|
24
26
|
viewName: string;
|
|
@@ -86,6 +86,11 @@ const _BelongsToManyRepository = class _BelongsToManyRepository extends import_m
|
|
|
86
86
|
through: values[this.throughName()],
|
|
87
87
|
transaction: transaction2
|
|
88
88
|
};
|
|
89
|
+
this.collection.validate({
|
|
90
|
+
values,
|
|
91
|
+
operation: "create",
|
|
92
|
+
context: options.context
|
|
93
|
+
});
|
|
89
94
|
const instance = await sourceModel[createAccessor](values, createOptions);
|
|
90
95
|
await (0, import_update_associations.updateAssociations)(instance, values, { ...options, transaction: transaction2 });
|
|
91
96
|
return instance;
|
|
@@ -178,6 +178,7 @@ const _RelationRepository = class _RelationRepository {
|
|
|
178
178
|
const values = options.values;
|
|
179
179
|
const transaction2 = await this.getTransaction(options);
|
|
180
180
|
const sourceModel = await this.getSourceModel(transaction2);
|
|
181
|
+
this.collection.validate({ values, context: options.context, operation: "create" });
|
|
181
182
|
const instance = await sourceModel[createAccessor](guard.sanitize(options.values), { ...options, transaction: transaction2 });
|
|
182
183
|
await (0, import_update_associations.updateAssociations)(instance, values, { ...options, transaction: transaction2 });
|
|
183
184
|
if (options.hooks !== false) {
|
package/lib/repository.d.ts
CHANGED
|
@@ -154,6 +154,8 @@ export declare class Repository<TModelAttributes extends {} = any, TCreationAttr
|
|
|
154
154
|
* return count by filter
|
|
155
155
|
*/
|
|
156
156
|
count(countOptions?: CountOptions): Promise<number>;
|
|
157
|
+
getEstimatedRowCount(): Promise<number>;
|
|
158
|
+
private getOracleSchema;
|
|
157
159
|
aggregate(options: AggregateOptions & {
|
|
158
160
|
optionsTransformer?: (options: any) => any;
|
|
159
161
|
}): Promise<any>;
|
|
@@ -207,6 +209,7 @@ export declare class Repository<TModelAttributes extends {} = any, TCreationAttr
|
|
|
207
209
|
*/
|
|
208
210
|
firstOrCreate(options: FirstOrCreateOptions): Promise<any>;
|
|
209
211
|
updateOrCreate(options: FirstOrCreateOptions): Promise<any>;
|
|
212
|
+
private validate;
|
|
210
213
|
/**
|
|
211
214
|
* Save instance to database
|
|
212
215
|
*
|
package/lib/repository.js
CHANGED
|
@@ -72,6 +72,7 @@ var import_hasone_repository = require("./relation-repository/hasone-repository"
|
|
|
72
72
|
var import_update_associations = require("./update-associations");
|
|
73
73
|
var import_update_guard = require("./update-guard");
|
|
74
74
|
var import_filter_utils = require("./utils/filter-utils");
|
|
75
|
+
var import_utils2 = require("./utils");
|
|
75
76
|
var import_sequelize2 = require("sequelize");
|
|
76
77
|
const debug = require("debug")("noco-database");
|
|
77
78
|
const transaction = (0, import_transaction_decorator.transactionWrapperBuilder)(function() {
|
|
@@ -134,7 +135,6 @@ const _Repository = class _Repository {
|
|
|
134
135
|
* return count by filter
|
|
135
136
|
*/
|
|
136
137
|
async count(countOptions) {
|
|
137
|
-
var _a;
|
|
138
138
|
let options = countOptions ? import_lodash.default.clone(countOptions) : {};
|
|
139
139
|
const transaction2 = await this.getTransaction(options);
|
|
140
140
|
if (countOptions == null ? void 0 : countOptions.filter) {
|
|
@@ -155,7 +155,9 @@ const _Repository = class _Repository {
|
|
|
155
155
|
...options,
|
|
156
156
|
distinct: Boolean(this.collection.model.primaryKeyAttribute) && !this.collection.isMultiFilterTargetKey()
|
|
157
157
|
};
|
|
158
|
-
if ((
|
|
158
|
+
if (Array.isArray(queryOptions.include) && queryOptions.include.length > 0) {
|
|
159
|
+
queryOptions.include = (0, import_utils2.processIncludes)(queryOptions.include, this.collection.model);
|
|
160
|
+
} else {
|
|
159
161
|
delete queryOptions.include;
|
|
160
162
|
}
|
|
161
163
|
return await this.collection.model.count({
|
|
@@ -163,6 +165,81 @@ const _Repository = class _Repository {
|
|
|
163
165
|
transaction: transaction2
|
|
164
166
|
});
|
|
165
167
|
}
|
|
168
|
+
async getEstimatedRowCount() {
|
|
169
|
+
var _a, _b, _c, _d;
|
|
170
|
+
if (import_lodash2.default.isFunction(this.collection["isSql"]) && this.collection["isSql"]()) {
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
if (import_lodash2.default.isFunction(this.collection["isView"]) && this.collection["isView"]()) {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
const tableName = this.collection.tableName();
|
|
177
|
+
try {
|
|
178
|
+
if (this.database.isMySQLCompatibleDialect()) {
|
|
179
|
+
const results = await this.database.sequelize.query(
|
|
180
|
+
`
|
|
181
|
+
SELECT table_rows FROM information_schema.tables
|
|
182
|
+
WHERE table_schema = DATABASE()
|
|
183
|
+
AND table_name = ?
|
|
184
|
+
`,
|
|
185
|
+
{ replacements: [tableName], type: import_sequelize.QueryTypes.SELECT }
|
|
186
|
+
);
|
|
187
|
+
return Number(((_a = results == null ? void 0 : results[0]) == null ? void 0 : _a[this.database.inDialect("mysql") ? "TABLE_ROWS" : "table_rows"]) ?? 0);
|
|
188
|
+
}
|
|
189
|
+
if (this.database.isPostgresCompatibleDialect()) {
|
|
190
|
+
const results = await this.database.sequelize.query(
|
|
191
|
+
`
|
|
192
|
+
SELECT reltuples::BIGINT AS estimate
|
|
193
|
+
FROM pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid
|
|
194
|
+
WHERE c.relname = ? AND n.nspname = ?;
|
|
195
|
+
`,
|
|
196
|
+
{ replacements: [tableName, this.collection.collectionSchema()], type: import_sequelize.QueryTypes.SELECT, logging: true }
|
|
197
|
+
);
|
|
198
|
+
return Number(((_b = results == null ? void 0 : results[0]) == null ? void 0 : _b.estimate) ?? 0);
|
|
199
|
+
}
|
|
200
|
+
if (this.database.sequelize.getDialect() === "mssql") {
|
|
201
|
+
const results = await this.database.sequelize.query(
|
|
202
|
+
`
|
|
203
|
+
SELECT SUM(row_count) AS estimate
|
|
204
|
+
FROM sys.dm_db_partition_stats
|
|
205
|
+
WHERE object_id = OBJECT_ID(?) AND (index_id = 0 OR index_id = 1)
|
|
206
|
+
`,
|
|
207
|
+
{ replacements: [tableName], type: import_sequelize.QueryTypes.SELECT }
|
|
208
|
+
);
|
|
209
|
+
return Number(((_c = results == null ? void 0 : results[0]) == null ? void 0 : _c.estimate) ?? 0);
|
|
210
|
+
}
|
|
211
|
+
if (this.database.sequelize.getDialect() === "oracle") {
|
|
212
|
+
const tableName2 = this.collection.name.toUpperCase();
|
|
213
|
+
const schemaName = (await this.getOracleSchema()).toUpperCase();
|
|
214
|
+
await this.database.sequelize.query(`BEGIN DBMS_STATS.GATHER_TABLE_STATS(:schema, :table); END;`, {
|
|
215
|
+
replacements: { schema: schemaName, table: tableName2 },
|
|
216
|
+
type: import_sequelize.QueryTypes.RAW
|
|
217
|
+
});
|
|
218
|
+
const results = await this.database.sequelize.query(
|
|
219
|
+
`
|
|
220
|
+
SELECT NUM_ROWS AS "estimate"
|
|
221
|
+
FROM ALL_TABLES
|
|
222
|
+
WHERE TABLE_NAME = :table AND OWNER = :schema
|
|
223
|
+
`,
|
|
224
|
+
{
|
|
225
|
+
replacements: { table: tableName2, schema: schemaName },
|
|
226
|
+
type: import_sequelize.QueryTypes.SELECT
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
return Number(((_d = results == null ? void 0 : results[0]) == null ? void 0 : _d.estimate) ?? 0);
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
this.database.logger.error(`Failed to get estimated row count for ${this.collection.name}:`, error);
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
async getOracleSchema() {
|
|
238
|
+
const [result] = await this.database.sequelize.query(`SELECT USER FROM DUAL`, {
|
|
239
|
+
type: import_sequelize.QueryTypes.SELECT
|
|
240
|
+
});
|
|
241
|
+
return (result == null ? void 0 : result["USER"]) ?? "";
|
|
242
|
+
}
|
|
166
243
|
async aggregate(options) {
|
|
167
244
|
var _a;
|
|
168
245
|
const { method, field } = options;
|
|
@@ -268,6 +345,9 @@ const _Repository = class _Repository {
|
|
|
268
345
|
subQuery: false,
|
|
269
346
|
...this.buildQueryOptions(options)
|
|
270
347
|
};
|
|
348
|
+
if (!import_lodash2.default.isUndefined(opts.limit)) {
|
|
349
|
+
opts.limit = Number(opts.limit);
|
|
350
|
+
}
|
|
271
351
|
let rows;
|
|
272
352
|
if (opts.include && opts.include.length > 0) {
|
|
273
353
|
const eagerLoadingTree = import_eager_loading_tree.EagerLoadingTree.buildFromSequelizeOptions({
|
|
@@ -356,6 +436,9 @@ const _Repository = class _Repository {
|
|
|
356
436
|
}
|
|
357
437
|
return this.create({ values, transaction: transaction2, context, ...rest });
|
|
358
438
|
}
|
|
439
|
+
validate(options) {
|
|
440
|
+
this.collection.validate(options);
|
|
441
|
+
}
|
|
359
442
|
async create(options) {
|
|
360
443
|
if (Array.isArray(options.values)) {
|
|
361
444
|
return this.createMany({
|
|
@@ -370,6 +453,7 @@ const _Repository = class _Repository {
|
|
|
370
453
|
underscored: this.collection.options.underscored
|
|
371
454
|
});
|
|
372
455
|
const values = this.model.callSetters(guard.sanitize(options.values || {}), options);
|
|
456
|
+
this.validate({ values, context: options.context, operation: "create" });
|
|
373
457
|
const instance = await this.model.create(values, {
|
|
374
458
|
...options,
|
|
375
459
|
transaction: transaction2
|
|
@@ -414,6 +498,7 @@ const _Repository = class _Repository {
|
|
|
414
498
|
const transaction2 = await this.getTransaction(options);
|
|
415
499
|
const guard = import_update_guard.UpdateGuard.fromOptions(this.model, { ...options, underscored: this.collection.options.underscored });
|
|
416
500
|
const values = this.model.callSetters(guard.sanitize(options.values || {}), options);
|
|
501
|
+
this.validate({ values, context: options.context, operation: "update" });
|
|
417
502
|
if (options.individualHooks === false) {
|
|
418
503
|
const { model: Model2 } = this.collection;
|
|
419
504
|
const primaryKeyField = Model2.primaryKeyField || Model2.primaryKeyAttribute;
|
|
@@ -79,6 +79,11 @@ async function updateModelByValues(instance, values, options) {
|
|
|
79
79
|
guard.setAssociationKeysToBeUpdate(options.updateAssociationValues);
|
|
80
80
|
values = guard.sanitize(values);
|
|
81
81
|
}
|
|
82
|
+
instance.constructor.collection.validate({
|
|
83
|
+
values,
|
|
84
|
+
operation: "update",
|
|
85
|
+
context: options == null ? void 0 : options.context
|
|
86
|
+
});
|
|
82
87
|
await instance.update(values, options);
|
|
83
88
|
await updateAssociations(instance, values, options);
|
|
84
89
|
}
|
|
@@ -266,6 +271,11 @@ async function updateSingleAssociation(model, key, value, options = {}) {
|
|
|
266
271
|
return true;
|
|
267
272
|
}
|
|
268
273
|
}
|
|
274
|
+
association.target.collection.validate({
|
|
275
|
+
values: value,
|
|
276
|
+
context: options.context,
|
|
277
|
+
operation: "create"
|
|
278
|
+
});
|
|
269
279
|
const instance = await model[createAccessor](value, { context, transaction });
|
|
270
280
|
await updateAssociations(instance, value, {
|
|
271
281
|
...options,
|
|
@@ -356,6 +366,11 @@ async function updateMultipleAssociation(model, key, value, options = {}) {
|
|
|
356
366
|
throw new Error(`${targetKey} field value is empty`);
|
|
357
367
|
}
|
|
358
368
|
if ((0, import_utils.isUndefinedOrNull)(item[targetKey])) {
|
|
369
|
+
association.target.collection.validate({
|
|
370
|
+
values: item,
|
|
371
|
+
context: options.context,
|
|
372
|
+
operation: "create"
|
|
373
|
+
});
|
|
359
374
|
const instance = await model[createAccessor](item, accessorOptions);
|
|
360
375
|
await updateAssociations(instance, item, {
|
|
361
376
|
...options,
|
|
@@ -373,6 +388,11 @@ async function updateMultipleAssociation(model, key, value, options = {}) {
|
|
|
373
388
|
transaction
|
|
374
389
|
});
|
|
375
390
|
if (!instance) {
|
|
391
|
+
association.target.collection.validate({
|
|
392
|
+
values: item,
|
|
393
|
+
context: options.context,
|
|
394
|
+
operation: "create"
|
|
395
|
+
});
|
|
376
396
|
instance = await model[createAccessor](item, accessorOptions);
|
|
377
397
|
await updateAssociations(instance, item, {
|
|
378
398
|
...options,
|
|
@@ -392,6 +412,11 @@ async function updateMultipleAssociation(model, key, value, options = {}) {
|
|
|
392
412
|
if (association.associationType === "HasMany") {
|
|
393
413
|
delete item[association.foreignKey];
|
|
394
414
|
}
|
|
415
|
+
association.target.collection.validate({
|
|
416
|
+
values: item,
|
|
417
|
+
context: options.context,
|
|
418
|
+
operation: "update"
|
|
419
|
+
});
|
|
395
420
|
await instance.update(item, { ...options, transaction });
|
|
396
421
|
}
|
|
397
422
|
await updateAssociations(instance, item, {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { AnySchema } from 'joi';
|
|
10
|
+
import { ValidationOptions } from '../fields';
|
|
11
|
+
export declare function buildJoiSchema(validation: ValidationOptions, options: {
|
|
12
|
+
label?: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}): AnySchema;
|
|
15
|
+
export declare function getJoiErrorMessage(t: Function): {
|
|
16
|
+
'string.base': any;
|
|
17
|
+
'string.empty': any;
|
|
18
|
+
'string.min': any;
|
|
19
|
+
'string.max': any;
|
|
20
|
+
'string.length': any;
|
|
21
|
+
'string.alphanum': any;
|
|
22
|
+
'string.token': any;
|
|
23
|
+
'string.regex': any;
|
|
24
|
+
'string.email': any;
|
|
25
|
+
'string.uri': any;
|
|
26
|
+
'string.uriCustomScheme': any;
|
|
27
|
+
'string.isoDate': any;
|
|
28
|
+
'string.guid': any;
|
|
29
|
+
'string.hex': any;
|
|
30
|
+
'string.hostname': any;
|
|
31
|
+
'string.lowercase': any;
|
|
32
|
+
'string.uppercase': any;
|
|
33
|
+
'string.trim': any;
|
|
34
|
+
'string.creditCard': any;
|
|
35
|
+
'string.pattern.base': any;
|
|
36
|
+
'string.pattern.name': any;
|
|
37
|
+
'string.pattern.invert.base': any;
|
|
38
|
+
'string.pattern.invert.name': any;
|
|
39
|
+
'any.required': any;
|
|
40
|
+
'number.base': any;
|
|
41
|
+
'number.min': any;
|
|
42
|
+
'number.max': any;
|
|
43
|
+
'number.less': any;
|
|
44
|
+
'number.greater': any;
|
|
45
|
+
'number.float': any;
|
|
46
|
+
'number.integer': any;
|
|
47
|
+
'number.negative': any;
|
|
48
|
+
'number.positive': any;
|
|
49
|
+
'number.precision': any;
|
|
50
|
+
'number.multiple': any;
|
|
51
|
+
'number.port': any;
|
|
52
|
+
'number.unsafe': any;
|
|
53
|
+
'date.base': any;
|
|
54
|
+
'date.format': any;
|
|
55
|
+
'date.greater': any;
|
|
56
|
+
'date.less': any;
|
|
57
|
+
'date.max': any;
|
|
58
|
+
'date.min': any;
|
|
59
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
38
|
+
var field_validation_exports = {};
|
|
39
|
+
__export(field_validation_exports, {
|
|
40
|
+
buildJoiSchema: () => buildJoiSchema,
|
|
41
|
+
getJoiErrorMessage: () => getJoiErrorMessage
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(field_validation_exports);
|
|
44
|
+
var import_joi = __toESM(require("joi"));
|
|
45
|
+
var import_lodash = __toESM(require("lodash"));
|
|
46
|
+
function buildJoiSchema(validation, options) {
|
|
47
|
+
const { type, rules } = validation;
|
|
48
|
+
const { label, value } = options;
|
|
49
|
+
if (!type || typeof type !== "string" || !(type in import_joi.default)) {
|
|
50
|
+
throw new Error(`Invalid validation type: "${type}". Type must be a valid Joi schema type.`);
|
|
51
|
+
}
|
|
52
|
+
let schema = import_joi.default[type]();
|
|
53
|
+
const isRequired = rules.some((rule) => rule.name === "required");
|
|
54
|
+
if (isRequired) {
|
|
55
|
+
schema = schema.empty(null);
|
|
56
|
+
} else {
|
|
57
|
+
schema = schema.allow(null, "");
|
|
58
|
+
if ([null, ""].includes(value)) return schema;
|
|
59
|
+
}
|
|
60
|
+
if (rules) {
|
|
61
|
+
rules.forEach((rule) => {
|
|
62
|
+
if (!import_lodash.default.isEmpty(rule.args)) {
|
|
63
|
+
if (rule.name === "pattern" && !import_lodash.default.isRegExp(rule.args.regex)) {
|
|
64
|
+
const lastSlash = rule.args.regex.lastIndexOf("/");
|
|
65
|
+
const isRegExpStr = rule.args.regex.startsWith("/") && lastSlash > 0;
|
|
66
|
+
if (isRegExpStr) {
|
|
67
|
+
rule.args.regex = rule.args.regex.slice(1, lastSlash);
|
|
68
|
+
}
|
|
69
|
+
rule.args.regex = new RegExp(rule.args.regex);
|
|
70
|
+
}
|
|
71
|
+
schema = rule.paramsType === "object" ? schema[rule.name](rule.args) : schema[rule.name](...Object.values(rule.args));
|
|
72
|
+
} else {
|
|
73
|
+
schema = schema[rule.name]();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (label) {
|
|
78
|
+
schema = schema.label(label);
|
|
79
|
+
}
|
|
80
|
+
return schema;
|
|
81
|
+
}
|
|
82
|
+
__name(buildJoiSchema, "buildJoiSchema");
|
|
83
|
+
function getJoiErrorMessage(t) {
|
|
84
|
+
const tOptions = { ns: "client" };
|
|
85
|
+
const JoiErrorMessages = {
|
|
86
|
+
"string.base": t("{{#label}} must be a string", tOptions),
|
|
87
|
+
"string.empty": t("{{#label}} is not allowed to be empty", tOptions),
|
|
88
|
+
"string.min": t("{{#label}} length must be at least {{#limit}} characters long", tOptions),
|
|
89
|
+
"string.max": t("{{#label}} length must be less than or equal to {{#limit}} characters long", tOptions),
|
|
90
|
+
"string.length": t("{{#label}} length must be {{#limit}} characters long", tOptions),
|
|
91
|
+
"string.alphanum": t("{{#label}} must only contain alpha-numeric characters", tOptions),
|
|
92
|
+
"string.token": t("{{#label}} must only contain alpha-numeric and underscore characters", tOptions),
|
|
93
|
+
"string.regex": t("{{#label}} with value {{#value}} fails to match the required pattern", tOptions),
|
|
94
|
+
"string.email": t("{{#label}} email address doesn\u2019t meet the required format", tOptions),
|
|
95
|
+
"string.uri": t("{{#label}} must be a valid uri", tOptions),
|
|
96
|
+
"string.uriCustomScheme": t(
|
|
97
|
+
"{{#label}} must be a valid uri with a scheme matching the {{#scheme}} pattern",
|
|
98
|
+
tOptions
|
|
99
|
+
),
|
|
100
|
+
"string.isoDate": t("{{#label}} must be a valid ISO 8601 date", tOptions),
|
|
101
|
+
"string.guid": t("{{#label}} must be a valid UUID", tOptions),
|
|
102
|
+
"string.hex": t("{{#label}} must only contain hexadecimal characters", tOptions),
|
|
103
|
+
"string.hostname": t("{{#label}} must be a valid hostname", tOptions),
|
|
104
|
+
"string.lowercase": t("{{#label}} must only contain lowercase characters", tOptions),
|
|
105
|
+
"string.uppercase": t("{{#label}} must only contain uppercase characters", tOptions),
|
|
106
|
+
"string.trim": t("{{#label}} must not have leading or trailing whitespace", tOptions),
|
|
107
|
+
"string.creditCard": t("{{#label}} must be a credit card", tOptions),
|
|
108
|
+
"string.pattern.base": t('{{#label}} with value "{{#value}}" fails to match the required pattern', tOptions),
|
|
109
|
+
"string.pattern.name": t('{{#label}} with value "{{#value}}" fails to match the {{#name}} pattern', tOptions),
|
|
110
|
+
"string.pattern.invert.base": t('{{#label}} with value "{{#value}}" matches the inverted pattern', tOptions),
|
|
111
|
+
"string.pattern.invert.name": t(
|
|
112
|
+
'{{#label}} with value "{{#value}}" matches the inverted {{#name}} pattern',
|
|
113
|
+
tOptions
|
|
114
|
+
),
|
|
115
|
+
"any.required": t("{{#label}} is required", tOptions),
|
|
116
|
+
"number.base": t("{{#label}} must be a number", tOptions),
|
|
117
|
+
"number.min": t("{{#label}} must be greater than or equal to {{#limit}}", tOptions),
|
|
118
|
+
"number.max": t("{{#label}} must be less than or equal to {{#limit}}", tOptions),
|
|
119
|
+
"number.less": t("{{#label}} must be less than {{#limit}}", tOptions),
|
|
120
|
+
"number.greater": t("{{#label}} must be greater than {{#limit}}", tOptions),
|
|
121
|
+
"number.float": t("{{#label}} must be a float or double", tOptions),
|
|
122
|
+
"number.integer": t("{{#label}} must be an integer", tOptions),
|
|
123
|
+
"number.negative": t("{{#label}} must be a negative number", tOptions),
|
|
124
|
+
"number.positive": t("{{#label}} must be a positive number", tOptions),
|
|
125
|
+
"number.precision": t("{{#label}} must not have more than {{#limit}} decimal places", tOptions),
|
|
126
|
+
"number.multiple": t("{{#label}} must be a multiple of {{#multiple}}", tOptions),
|
|
127
|
+
"number.port": t("{{#label}} must be a valid port", tOptions),
|
|
128
|
+
"number.unsafe": t("{{#label}} must be a safe number", tOptions),
|
|
129
|
+
"date.base": t("{{#label}} must be a valid date", tOptions),
|
|
130
|
+
"date.format": t("{{#label}} must be in {{#format}} format", tOptions),
|
|
131
|
+
"date.greater": t("{{#label}} must be greater than {{#limit}}", tOptions),
|
|
132
|
+
"date.less": t("{{#label}} must be less than {{#limit}}", tOptions),
|
|
133
|
+
"date.max": t("{{#label}} must be less than or equal to {{#limit}}", tOptions),
|
|
134
|
+
"date.min": t("{{#label}} must be greater than or equal to {{#limit}}", tOptions)
|
|
135
|
+
};
|
|
136
|
+
return JoiErrorMessages;
|
|
137
|
+
}
|
|
138
|
+
__name(getJoiErrorMessage, "getJoiErrorMessage");
|
|
139
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
140
|
+
0 && (module.exports = {
|
|
141
|
+
buildJoiSchema,
|
|
142
|
+
getJoiErrorMessage
|
|
143
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Merge a flat list of include descriptors by association (alias) key.
|
|
11
|
+
* - Nodes with the same association are merged into one.
|
|
12
|
+
* - `required` is OR-ed (true if any source is true).
|
|
13
|
+
* - Child includes are recursively merged with the same rules.
|
|
14
|
+
* - Empty `include` arrays are removed to keep the payload minimal.
|
|
15
|
+
*
|
|
16
|
+
* Notes:
|
|
17
|
+
* - Association keys are normalized via snake_case to ensure consistent merging.
|
|
18
|
+
* - This function is idempotent and order-insensitive for equivalent input sets.
|
|
19
|
+
*
|
|
20
|
+
* Usage example (input and output):
|
|
21
|
+
*
|
|
22
|
+
* const includesA = [
|
|
23
|
+
* { association: 'posts', required: true, include: [{ association: 'comments' }] },
|
|
24
|
+
* { association: 'profile' },
|
|
25
|
+
* ];
|
|
26
|
+
*
|
|
27
|
+
* const includesB = [
|
|
28
|
+
* { association: 'posts', include: [
|
|
29
|
+
* { association: 'comments', required: true },
|
|
30
|
+
* { association: 'tags' },
|
|
31
|
+
* ]
|
|
32
|
+
* },
|
|
33
|
+
* { association: 'roles', required: true },
|
|
34
|
+
* ];
|
|
35
|
+
*
|
|
36
|
+
* const merged = mergeIncludes([...includesA, ...includesB]);
|
|
37
|
+
*
|
|
38
|
+
* Result:
|
|
39
|
+
* [
|
|
40
|
+
* {
|
|
41
|
+
* association: 'posts',
|
|
42
|
+
* required: true,
|
|
43
|
+
* include: [
|
|
44
|
+
* { association: 'comments', required: true },
|
|
45
|
+
* { association: 'tags' },
|
|
46
|
+
* ],
|
|
47
|
+
* },
|
|
48
|
+
* { association: 'profile' },
|
|
49
|
+
* { association: 'roles', required: true },
|
|
50
|
+
* ]
|
|
51
|
+
*/
|
|
52
|
+
export declare const mergeIncludes: (includes?: any[]) => any[];
|
|
53
|
+
export declare const filterIncludes: (where: any, includes: any, options: {
|
|
54
|
+
underscored: boolean;
|
|
55
|
+
}) => any[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var filter_include_exports = {};
|
|
29
|
+
__export(filter_include_exports, {
|
|
30
|
+
filterIncludes: () => filterIncludes,
|
|
31
|
+
mergeIncludes: () => mergeIncludes
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(filter_include_exports);
|
|
34
|
+
var import_utils = require("../utils");
|
|
35
|
+
const collectAssociationPathsFromWhere = /* @__PURE__ */ __name((where) => {
|
|
36
|
+
const aliasPaths = /* @__PURE__ */ new Set();
|
|
37
|
+
const traverse = /* @__PURE__ */ __name((value) => {
|
|
38
|
+
if (value == null) return;
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
for (const item of value) traverse(item);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "object") {
|
|
44
|
+
for (const [rawKey, child] of Object.entries(value)) {
|
|
45
|
+
if (typeof rawKey === "string" && rawKey.startsWith("$") && rawKey.endsWith("$")) {
|
|
46
|
+
const inner = rawKey.slice(1, -1);
|
|
47
|
+
const segments = inner.split(".");
|
|
48
|
+
if (segments.length > 1) {
|
|
49
|
+
aliasPaths.add(segments.slice(0, -1).join("."));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
traverse(child);
|
|
53
|
+
}
|
|
54
|
+
for (const sym of Object.getOwnPropertySymbols(value)) {
|
|
55
|
+
traverse(value[sym]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, "traverse");
|
|
59
|
+
traverse(where);
|
|
60
|
+
return Array.from(aliasPaths);
|
|
61
|
+
}, "collectAssociationPathsFromWhere");
|
|
62
|
+
const pruneIncludeTreeByPaths = /* @__PURE__ */ __name((includes = [], requiredPathsRaw, options) => {
|
|
63
|
+
const normalizedPaths = options.underscored ? requiredPathsRaw.map(
|
|
64
|
+
(p) => p.split(".").map((s) => (0, import_utils.snakeCase)(s)).join(".")
|
|
65
|
+
) : requiredPathsRaw;
|
|
66
|
+
if (!(includes == null ? void 0 : includes.length) || !(normalizedPaths == null ? void 0 : normalizedPaths.length)) return [];
|
|
67
|
+
const pruned = [];
|
|
68
|
+
for (const inc of includes) {
|
|
69
|
+
const association = inc == null ? void 0 : inc.association;
|
|
70
|
+
if (!association) continue;
|
|
71
|
+
const assocKey = options.underscored ? (0, import_utils.snakeCase)(String(association)) : String(association);
|
|
72
|
+
const matched = normalizedPaths.filter((p) => p === assocKey || p.startsWith(assocKey + "."));
|
|
73
|
+
if (!matched.length) continue;
|
|
74
|
+
const childRemainders = matched.map((p) => p === assocKey ? null : p.slice(assocKey.length + 1)).filter(Boolean);
|
|
75
|
+
const children = pruneIncludeTreeByPaths(inc.include ?? [], childRemainders, options);
|
|
76
|
+
const copy = { ...inc };
|
|
77
|
+
if (children.length) {
|
|
78
|
+
copy.include = children;
|
|
79
|
+
} else if ("include" in copy) {
|
|
80
|
+
delete copy.include;
|
|
81
|
+
}
|
|
82
|
+
pruned.push(copy);
|
|
83
|
+
}
|
|
84
|
+
return pruned;
|
|
85
|
+
}, "pruneIncludeTreeByPaths");
|
|
86
|
+
const mergeIncludes = /* @__PURE__ */ __name((includes = []) => {
|
|
87
|
+
const byAssociation = /* @__PURE__ */ new Map();
|
|
88
|
+
const mergeAll = /* @__PURE__ */ __name((list = []) => {
|
|
89
|
+
for (const inc of list) {
|
|
90
|
+
const association = inc == null ? void 0 : inc.association;
|
|
91
|
+
if (!association) continue;
|
|
92
|
+
const key = (0, import_utils.snakeCase)(String(association));
|
|
93
|
+
if (!byAssociation.has(key)) {
|
|
94
|
+
byAssociation.set(key, { ...inc, include: void 0 });
|
|
95
|
+
}
|
|
96
|
+
const target = byAssociation.get(key);
|
|
97
|
+
if (inc.required) target.required = true;
|
|
98
|
+
const mergedChildren = mergeIncludes([...target.include ?? [], ...inc.include ?? []]);
|
|
99
|
+
if (mergedChildren.length) {
|
|
100
|
+
target.include = mergedChildren;
|
|
101
|
+
} else if ("include" in target) {
|
|
102
|
+
delete target.include;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}, "mergeAll");
|
|
106
|
+
mergeAll(includes);
|
|
107
|
+
return Array.from(byAssociation.values());
|
|
108
|
+
}, "mergeIncludes");
|
|
109
|
+
const filterIncludes = /* @__PURE__ */ __name((where, includes, options) => {
|
|
110
|
+
const path = collectAssociationPathsFromWhere(where);
|
|
111
|
+
const result = pruneIncludeTreeByPaths(includes, path, options);
|
|
112
|
+
return result;
|
|
113
|
+
}, "filterIncludes");
|
|
114
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
115
|
+
0 && (module.exports = {
|
|
116
|
+
filterIncludes,
|
|
117
|
+
mergeIncludes
|
|
118
|
+
});
|
package/lib/utils.d.ts
CHANGED
|
@@ -16,3 +16,4 @@ export declare function percent2float(value: string): number;
|
|
|
16
16
|
export declare function isUndefinedOrNull(value: any): boolean;
|
|
17
17
|
export declare function isStringOrNumber(value: any): boolean;
|
|
18
18
|
export declare function getKeysByPrefix(keys: string[], prefix: string): string[];
|
|
19
|
+
export declare function processIncludes(includes: any[], model: any, parentAs?: string): any[];
|