@nocobase/database 1.9.0-beta.8 → 2.0.0-alpha.2
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 +10 -0
- package/lib/collection.js +78 -4
- package/lib/database.d.ts +11 -2
- package/lib/database.js +83 -2
- package/lib/dialects/mysql-dialect.d.ts +2 -1
- package/lib/dialects/mysql-dialect.js +5 -2
- 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/inherited-sync-runner.js +12 -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 +1 -0
- package/lib/repository.js +5 -0
- 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/package.json +5 -4
package/lib/collection.d.ts
CHANGED
|
@@ -81,13 +81,22 @@ export declare class Collection<TModelAttributes extends {} = any, TCreationAttr
|
|
|
81
81
|
model: ModelStatic<Model>;
|
|
82
82
|
repository: Repository<TModelAttributes, TCreationAttributes>;
|
|
83
83
|
constructor(options: CollectionOptions, context: CollectionContext);
|
|
84
|
+
get underscored(): boolean;
|
|
84
85
|
get filterTargetKey(): string | string[];
|
|
85
86
|
get name(): string;
|
|
86
87
|
get origin(): string;
|
|
87
88
|
get titleField(): string;
|
|
89
|
+
get titleFieldInstance(): Field;
|
|
88
90
|
get db(): Database;
|
|
89
91
|
get treeParentField(): BelongsToField | null;
|
|
90
92
|
get treeChildrenField(): HasManyField | null;
|
|
93
|
+
validate(options: {
|
|
94
|
+
values: Record<string, any> | Record<string, any>[];
|
|
95
|
+
operation: 'create' | 'update';
|
|
96
|
+
context: {
|
|
97
|
+
t: Function;
|
|
98
|
+
};
|
|
99
|
+
}): void;
|
|
91
100
|
isMultiFilterTargetKey(): boolean;
|
|
92
101
|
tableName(): any;
|
|
93
102
|
/**
|
|
@@ -148,6 +157,7 @@ export declare class Collection<TModelAttributes extends {} = any, TCreationAttr
|
|
|
148
157
|
modelName: string;
|
|
149
158
|
sequelize: import("sequelize").Sequelize;
|
|
150
159
|
tableName: any;
|
|
160
|
+
underscored: boolean;
|
|
151
161
|
};
|
|
152
162
|
protected bindFieldEventListener(): void;
|
|
153
163
|
private checkOptions;
|
package/lib/collection.js
CHANGED
|
@@ -53,9 +53,11 @@ var import_events = require("events");
|
|
|
53
53
|
var import_lodash = __toESM(require("lodash"));
|
|
54
54
|
var import_safe_json_stringify = __toESM(require("safe-json-stringify"));
|
|
55
55
|
var import_sequelize = require("sequelize");
|
|
56
|
+
var import_fields = require("./fields");
|
|
56
57
|
var import_model = require("./model");
|
|
57
58
|
var import_repository = require("./repository");
|
|
58
59
|
var import_utils = require("./utils");
|
|
60
|
+
var import_field_validation = require("./utils/field-validation");
|
|
59
61
|
function EnsureAtomicity(target, propertyKey, descriptor) {
|
|
60
62
|
const originalMethod = descriptor.value;
|
|
61
63
|
descriptor.value = function(...args) {
|
|
@@ -109,6 +111,9 @@ const _Collection = class _Collection extends import_events.EventEmitter {
|
|
|
109
111
|
this.setRepository(options.repository);
|
|
110
112
|
this.setSortable(options.sortable);
|
|
111
113
|
}
|
|
114
|
+
get underscored() {
|
|
115
|
+
return this.options.underscored;
|
|
116
|
+
}
|
|
112
117
|
get filterTargetKey() {
|
|
113
118
|
var _a;
|
|
114
119
|
const targetKey = (_a = this.options) == null ? void 0 : _a.filterTargetKey;
|
|
@@ -135,6 +140,9 @@ const _Collection = class _Collection extends import_events.EventEmitter {
|
|
|
135
140
|
get titleField() {
|
|
136
141
|
return this.options.titleField || this.model.primaryKeyAttribute;
|
|
137
142
|
}
|
|
143
|
+
get titleFieldInstance() {
|
|
144
|
+
return this.getField(this.titleField);
|
|
145
|
+
}
|
|
138
146
|
get db() {
|
|
139
147
|
return this.context.database;
|
|
140
148
|
}
|
|
@@ -152,13 +160,78 @@ const _Collection = class _Collection extends import_events.EventEmitter {
|
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
}
|
|
163
|
+
validate(options) {
|
|
164
|
+
const { values: updateValues, context, operation } = options;
|
|
165
|
+
if (!updateValues) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const values = Array.isArray(updateValues) ? updateValues : [updateValues];
|
|
169
|
+
const { t } = context || { t: /* @__PURE__ */ __name((key, options2) => key, "t") };
|
|
170
|
+
const unwrapTplLabel = /* @__PURE__ */ __name((label) => {
|
|
171
|
+
if (typeof label !== "string") return label;
|
|
172
|
+
const m = label.match(/^[\s\t]*\{\{\s*t\(\s*(['"])(.*?)\1(?:\s*,[\s\S]*)?\)\s*\}\}[\s\t]*$/);
|
|
173
|
+
return m ? m[2] : label;
|
|
174
|
+
}, "unwrapTplLabel");
|
|
175
|
+
const helper = /* @__PURE__ */ __name((field, value) => {
|
|
176
|
+
var _a, _b, _c;
|
|
177
|
+
const val = value[field.name];
|
|
178
|
+
if (!field.options.validation) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const fieldLabel = unwrapTplLabel(((_a = field.options.uiSchema) == null ? void 0 : _a.title) || field.name);
|
|
182
|
+
if (field instanceof import_fields.RelationField) {
|
|
183
|
+
if ((_b = field.options) == null ? void 0 : _b.validation.rules) {
|
|
184
|
+
const isRequired = (_c = field.options) == null ? void 0 : _c.validation.rules.some((rule) => rule.name === "required");
|
|
185
|
+
if (isRequired && !val) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
t("{{#label}} is required", {
|
|
188
|
+
ns: "client",
|
|
189
|
+
"#label": `${t("Collection", { ns: "client" })}: ${this.name}, ${t("Field", { ns: "client" })}: ${t(
|
|
190
|
+
fieldLabel,
|
|
191
|
+
{ ns: "client" }
|
|
192
|
+
)}`
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const joiSchema = (0, import_field_validation.buildJoiSchema)(field.options.validation, {
|
|
200
|
+
label: `${t("Collection", { ns: "client" })}: ${this.name}, ${t("Field", { ns: "client" })}: ${t(fieldLabel, {
|
|
201
|
+
ns: "client"
|
|
202
|
+
})}`,
|
|
203
|
+
value: val
|
|
204
|
+
});
|
|
205
|
+
const { error } = joiSchema.validate(val, {
|
|
206
|
+
messages: (0, import_field_validation.getJoiErrorMessage)(t)
|
|
207
|
+
});
|
|
208
|
+
if (error) {
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}, "helper");
|
|
212
|
+
for (const value of values) {
|
|
213
|
+
if (operation === "create") {
|
|
214
|
+
for (const [, field] of this.fields) {
|
|
215
|
+
helper(field, value);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (operation === "update") {
|
|
219
|
+
for (const key of Object.keys(value)) {
|
|
220
|
+
const field = this.getField(key);
|
|
221
|
+
if (field) {
|
|
222
|
+
helper(field, value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
155
228
|
isMultiFilterTargetKey() {
|
|
156
229
|
return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1;
|
|
157
230
|
}
|
|
158
231
|
tableName() {
|
|
159
232
|
const { name, tableName } = this.options;
|
|
160
233
|
const tName = tableName || name;
|
|
161
|
-
return this.
|
|
234
|
+
return this.underscored ? (0, import_utils.snakeCase)(tName) : tName;
|
|
162
235
|
}
|
|
163
236
|
/**
|
|
164
237
|
* @internal
|
|
@@ -271,7 +344,7 @@ const _Collection = class _Collection extends import_events.EventEmitter {
|
|
|
271
344
|
return this.setField(name, options);
|
|
272
345
|
}
|
|
273
346
|
checkFieldType(name, options) {
|
|
274
|
-
if (!this.
|
|
347
|
+
if (!this.underscored) {
|
|
275
348
|
return;
|
|
276
349
|
}
|
|
277
350
|
const fieldName = options.field || (0, import_utils.snakeCase)(name);
|
|
@@ -416,7 +489,7 @@ const _Collection = class _Collection extends import_events.EventEmitter {
|
|
|
416
489
|
}
|
|
417
490
|
if (this.model.options.timestamps !== false) {
|
|
418
491
|
let timestampsFields = ["createdAt", "updatedAt", "deletedAt"];
|
|
419
|
-
if (this.
|
|
492
|
+
if (this.underscored) {
|
|
420
493
|
timestampsFields = timestampsFields.map((fieldName) => (0, import_utils.snakeCase)(fieldName));
|
|
421
494
|
}
|
|
422
495
|
if (timestampsFields.includes(field.columnName())) {
|
|
@@ -709,7 +782,8 @@ const _Collection = class _Collection extends import_events.EventEmitter {
|
|
|
709
782
|
...import_lodash.default.omit(this.options, ["name", "fields", "model", "targetKey"]),
|
|
710
783
|
modelName: name,
|
|
711
784
|
sequelize: this.context.database.sequelize,
|
|
712
|
-
tableName: this.tableName()
|
|
785
|
+
tableName: this.tableName(),
|
|
786
|
+
underscored: this.underscored
|
|
713
787
|
};
|
|
714
788
|
return attr;
|
|
715
789
|
}
|
package/lib/database.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { Collection, CollectionOptions, RepositoryType } from './collection';
|
|
|
17
17
|
import { CollectionFactory } from './collection-factory';
|
|
18
18
|
import { ImportFileExtension } from './collection-importer';
|
|
19
19
|
import DatabaseUtils from './database-utils';
|
|
20
|
+
import { BaseDialect } from './dialects/base-dialect';
|
|
20
21
|
import ReferencesMap from './features/references-map';
|
|
21
22
|
import { ArrayFieldRepository } from './field-repository/array-field-repository';
|
|
22
23
|
import * as FieldTypes from './fields';
|
|
@@ -31,7 +32,6 @@ import { RelationRepository } from './relation-repository/relation-repository';
|
|
|
31
32
|
import { Repository, TargetKey } from './repository';
|
|
32
33
|
import { AfterDefineCollectionListener, BeforeDefineCollectionListener, CreateListener, CreateWithAssociationsListener, DatabaseAfterDefineCollectionEventType, DatabaseAfterRemoveCollectionEventType, DatabaseBeforeDefineCollectionEventType, DatabaseBeforeRemoveCollectionEventType, DestroyListener, EventType, ModelCreateEventTypes, ModelCreateWithAssociationsEventTypes, ModelDestroyEventTypes, ModelSaveEventTypes, ModelSaveWithAssociationsEventTypes, ModelUpdateEventTypes, ModelUpdateWithAssociationsEventTypes, ModelValidateEventTypes, RemoveCollectionListener, SaveListener, SaveWithAssociationsListener, SyncListener, UpdateListener, UpdateWithAssociationsListener, ValidateListener } from './types';
|
|
33
34
|
import { BaseValueParser } from './value-parsers';
|
|
34
|
-
import { BaseDialect } from './dialects/base-dialect';
|
|
35
35
|
export type MergeOptions = merge.Options;
|
|
36
36
|
export interface PendingOptions {
|
|
37
37
|
field: RelationField;
|
|
@@ -67,6 +67,11 @@ export type AddMigrationsOptions = {
|
|
|
67
67
|
directory: string;
|
|
68
68
|
};
|
|
69
69
|
type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
|
|
70
|
+
type RunSQLOptions = {
|
|
71
|
+
filter?: Record<string, any>;
|
|
72
|
+
bind?: Record<string, any> | Array<any>;
|
|
73
|
+
type?: 'selectVar' | 'selectRow' | 'selectRows';
|
|
74
|
+
} & Transactionable;
|
|
70
75
|
export declare class Database extends EventEmitter implements AsyncEmitter {
|
|
71
76
|
static dialects: Map<string, typeof BaseDialect>;
|
|
72
77
|
sequelize: Sequelize;
|
|
@@ -118,7 +123,7 @@ export declare class Database extends EventEmitter implements AsyncEmitter {
|
|
|
118
123
|
/**
|
|
119
124
|
* @internal
|
|
120
125
|
*/
|
|
121
|
-
sequelizeOptions(options:
|
|
126
|
+
sequelizeOptions(options: DatabaseOptions): IDatabaseOptions;
|
|
122
127
|
/**
|
|
123
128
|
* @internal
|
|
124
129
|
*/
|
|
@@ -128,6 +133,7 @@ export declare class Database extends EventEmitter implements AsyncEmitter {
|
|
|
128
133
|
inDialect(...dialect: string[]): boolean;
|
|
129
134
|
isMySQLCompatibleDialect(): boolean;
|
|
130
135
|
isPostgresCompatibleDialect(): boolean;
|
|
136
|
+
wrapSequelizeRunForMySQL(): void;
|
|
131
137
|
/**
|
|
132
138
|
* Add collection to database
|
|
133
139
|
* @param options
|
|
@@ -207,6 +213,9 @@ export declare class Database extends EventEmitter implements AsyncEmitter {
|
|
|
207
213
|
extensions?: ImportFileExtension[];
|
|
208
214
|
}): Promise<Map<string, Collection>>;
|
|
209
215
|
private registerCollectionType;
|
|
216
|
+
quoteIdentifier(identifier: string): string;
|
|
217
|
+
quoteTable(tableName: string): string;
|
|
218
|
+
runSQL(sql: string, options?: RunSQLOptions): Promise<any>;
|
|
210
219
|
}
|
|
211
220
|
export declare function extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions): {
|
|
212
221
|
collectionOptions: CollectionOptions;
|
package/lib/database.js
CHANGED
|
@@ -58,6 +58,7 @@ var import_path = require("path");
|
|
|
58
58
|
var import_safe_json_stringify = __toESM(require("safe-json-stringify"));
|
|
59
59
|
var import_sequelize = require("sequelize");
|
|
60
60
|
var import_umzug = require("umzug");
|
|
61
|
+
var import_collection = require("./collection");
|
|
61
62
|
var import_collection_factory = require("./collection-factory");
|
|
62
63
|
var import_collection_importer = require("./collection-importer");
|
|
63
64
|
var import_database_utils = __toESM(require("./database-utils"));
|
|
@@ -176,6 +177,9 @@ const _Database = class _Database extends import_events.EventEmitter {
|
|
|
176
177
|
);
|
|
177
178
|
const sequelizeOptions = this.sequelizeOptions(this.options);
|
|
178
179
|
this.sequelize = new import_sequelize.Sequelize(sequelizeOptions);
|
|
180
|
+
if (options.dialect === "mysql") {
|
|
181
|
+
this.wrapSequelizeRunForMySQL();
|
|
182
|
+
}
|
|
179
183
|
this.queryInterface = (0, import_query_interface_builder.default)(this);
|
|
180
184
|
this.collections = /* @__PURE__ */ new Map();
|
|
181
185
|
this.modelHook = new import_model_hook.ModelHook(this);
|
|
@@ -397,14 +401,40 @@ const _Database = class _Database extends import_events.EventEmitter {
|
|
|
397
401
|
isPostgresCompatibleDialect() {
|
|
398
402
|
return this.inDialect("postgres");
|
|
399
403
|
}
|
|
404
|
+
/*
|
|
405
|
+
* https://github.com/sidorares/node-mysql2/issues/1239#issuecomment-766867699
|
|
406
|
+
* https://github.com/sidorares/node-mysql2/pull/1407#issuecomment-1325789581
|
|
407
|
+
* > I'm starting to think simple "always send (parameter.toString()) as VAR_STRING" unless the type is explicitly specified by user" might be actually the best behaviour
|
|
408
|
+
*/
|
|
409
|
+
wrapSequelizeRunForMySQL() {
|
|
410
|
+
const that = this;
|
|
411
|
+
const run = this.sequelize.dialect.Query.prototype.run;
|
|
412
|
+
this.sequelize.dialect.Query.prototype.run = function(sql, parameters) {
|
|
413
|
+
if (!/^update\s+/i.test(sql.trim()) || !(parameters == null ? void 0 : parameters.length)) {
|
|
414
|
+
return run.apply(this, [sql, parameters]);
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
parameters.forEach((p, index) => {
|
|
418
|
+
if (typeof p === "number") {
|
|
419
|
+
parameters[index] = p.toString();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
} catch (err) {
|
|
423
|
+
that.logger.error(err);
|
|
424
|
+
}
|
|
425
|
+
return run.apply(this, [sql, parameters]);
|
|
426
|
+
};
|
|
427
|
+
}
|
|
400
428
|
/**
|
|
401
429
|
* Add collection to database
|
|
402
430
|
* @param options
|
|
403
431
|
*/
|
|
404
432
|
collection(options) {
|
|
405
433
|
options = import_lodash.default.cloneDeep(options);
|
|
406
|
-
if (
|
|
407
|
-
options.underscored
|
|
434
|
+
if (typeof options.underscored !== "boolean") {
|
|
435
|
+
if (this.options.underscored) {
|
|
436
|
+
options.underscored = true;
|
|
437
|
+
}
|
|
408
438
|
}
|
|
409
439
|
this.logger.trace(`beforeDefineCollection: ${(0, import_safe_json_stringify.default)(options)}`, {
|
|
410
440
|
databaseInstanceId: this.instanceId
|
|
@@ -754,6 +784,57 @@ const _Database = class _Database extends import_events.EventEmitter {
|
|
|
754
784
|
}
|
|
755
785
|
});
|
|
756
786
|
}
|
|
787
|
+
// 如果需要手动引用标识符,可以添加辅助方法
|
|
788
|
+
quoteIdentifier(identifier) {
|
|
789
|
+
return this.sequelize.getQueryInterface().queryGenerator["quoteIdentifier"](identifier);
|
|
790
|
+
}
|
|
791
|
+
quoteTable(tableName) {
|
|
792
|
+
return this.sequelize.getQueryInterface().quoteIdentifiers(tableName);
|
|
793
|
+
}
|
|
794
|
+
async runSQL(sql, options = {}) {
|
|
795
|
+
const { filter, bind, type, transaction } = options;
|
|
796
|
+
let finalSQL = sql;
|
|
797
|
+
if (!finalSQL.replace(/\s+/g, " ").trim()) {
|
|
798
|
+
throw new Error("SQL cannot be empty");
|
|
799
|
+
}
|
|
800
|
+
if (filter) {
|
|
801
|
+
let where = {};
|
|
802
|
+
const tmpCollection = new import_collection.Collection({ name: "tmp", underscored: false }, { database: this });
|
|
803
|
+
const r = tmpCollection.repository;
|
|
804
|
+
where = r.buildQueryOptions({ filter }).where;
|
|
805
|
+
const queryGenerator = this.sequelize.getQueryInterface().queryGenerator;
|
|
806
|
+
const wSQL = queryGenerator.getWhereConditions(where, null, null, { bindParam: true });
|
|
807
|
+
if (wSQL) {
|
|
808
|
+
let normalizedSQL = sql.replace(/\s+/g, " ").trim();
|
|
809
|
+
if (normalizedSQL.endsWith(";")) {
|
|
810
|
+
normalizedSQL = normalizedSQL.slice(0, -1).trim();
|
|
811
|
+
}
|
|
812
|
+
finalSQL = `SELECT * FROM (${normalizedSQL}) AS tmp WHERE ${wSQL}`;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
this.logger.debug("runSQL", { finalSQL });
|
|
816
|
+
const result = await this.sequelize.query(finalSQL, { bind, transaction });
|
|
817
|
+
let data = result[0];
|
|
818
|
+
if (type === "selectVar") {
|
|
819
|
+
if (Array.isArray(data)) {
|
|
820
|
+
data = data[0];
|
|
821
|
+
}
|
|
822
|
+
return Object.values(data || {}).shift();
|
|
823
|
+
}
|
|
824
|
+
if (type === "selectRow") {
|
|
825
|
+
if (Array.isArray(data)) {
|
|
826
|
+
data = data[0];
|
|
827
|
+
}
|
|
828
|
+
return data || null;
|
|
829
|
+
}
|
|
830
|
+
if (type === "selectRows") {
|
|
831
|
+
if (!Array.isArray(data)) {
|
|
832
|
+
return [data].filter(Boolean);
|
|
833
|
+
}
|
|
834
|
+
return data;
|
|
835
|
+
}
|
|
836
|
+
return data;
|
|
837
|
+
}
|
|
757
838
|
};
|
|
758
839
|
__name(_Database, "Database");
|
|
759
840
|
__publicField(_Database, "dialects", /* @__PURE__ */ new Map());
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { BaseDialect } from './base-dialect';
|
|
10
|
+
import { DatabaseOptions } from '../database';
|
|
10
11
|
export declare class MysqlDialect extends BaseDialect {
|
|
11
12
|
static dialectName: string;
|
|
12
13
|
getVersionGuard(): {
|
|
@@ -14,5 +15,5 @@ export declare class MysqlDialect extends BaseDialect {
|
|
|
14
15
|
get: (v: string) => string;
|
|
15
16
|
version: string;
|
|
16
17
|
};
|
|
17
|
-
getSequelizeOptions(options:
|
|
18
|
+
getSequelizeOptions(options: DatabaseOptions): import("../database").IDatabaseOptions;
|
|
18
19
|
}
|
|
@@ -32,7 +32,6 @@ __export(mysql_dialect_exports, {
|
|
|
32
32
|
MysqlDialect: () => MysqlDialect
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(mysql_dialect_exports);
|
|
35
|
-
var import_utils = require("@nocobase/utils");
|
|
36
35
|
var import_base_dialect = require("./base-dialect");
|
|
37
36
|
const _MysqlDialect = class _MysqlDialect extends import_base_dialect.BaseDialect {
|
|
38
37
|
getVersionGuard() {
|
|
@@ -46,7 +45,11 @@ const _MysqlDialect = class _MysqlDialect extends import_base_dialect.BaseDialec
|
|
|
46
45
|
};
|
|
47
46
|
}
|
|
48
47
|
getSequelizeOptions(options) {
|
|
49
|
-
|
|
48
|
+
const dialectOptions = {
|
|
49
|
+
...options.dialectOptions || {},
|
|
50
|
+
multipleStatements: true
|
|
51
|
+
};
|
|
52
|
+
options.dialectOptions = dialectOptions;
|
|
50
53
|
return options;
|
|
51
54
|
}
|
|
52
55
|
};
|
package/lib/fields/field.d.ts
CHANGED
|
@@ -10,17 +10,39 @@ import { DataType, ModelAttributeColumnOptions, ModelIndexesOptions, SyncOptions
|
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
11
|
import { Database } from '../database';
|
|
12
12
|
import { ModelEventTypes } from '../types';
|
|
13
|
+
import { BasicType, BooleanSchema, NumberSchema, ObjectSchema, StringSchema } from 'joi';
|
|
13
14
|
export interface FieldContext {
|
|
14
15
|
database: Database;
|
|
15
16
|
collection: Collection;
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
type RuleSchemaMap = {
|
|
19
|
+
string: StringSchema;
|
|
20
|
+
boolean: BooleanSchema;
|
|
21
|
+
number: NumberSchema;
|
|
22
|
+
object: ObjectSchema;
|
|
23
|
+
};
|
|
24
|
+
export type FieldValidationRuleName<T extends BasicType> = T extends keyof RuleSchemaMap ? keyof RuleSchemaMap[T] : never;
|
|
25
|
+
export interface FieldValidationRule<T extends BasicType> {
|
|
26
|
+
key: string;
|
|
27
|
+
name: FieldValidationRuleName<T>;
|
|
28
|
+
args?: {
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
};
|
|
31
|
+
paramsType?: 'object';
|
|
32
|
+
}
|
|
33
|
+
export interface ValidationOptions<T extends BasicType = BasicType> {
|
|
34
|
+
type: T;
|
|
35
|
+
rules: FieldValidationRule<T>[];
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}
|
|
38
|
+
export interface BaseFieldOptions<T extends BasicType = BasicType> {
|
|
18
39
|
name?: string;
|
|
19
40
|
hidden?: boolean;
|
|
20
41
|
translation?: boolean;
|
|
42
|
+
validation?: ValidationOptions<T>;
|
|
21
43
|
[key: string]: any;
|
|
22
44
|
}
|
|
23
|
-
export interface BaseColumnFieldOptions extends BaseFieldOptions
|
|
45
|
+
export interface BaseColumnFieldOptions<T extends BasicType = BasicType> extends BaseFieldOptions<T>, Omit<ModelAttributeColumnOptions, 'type'> {
|
|
24
46
|
dataType?: DataType;
|
|
25
47
|
index?: boolean | ModelIndexesOptions;
|
|
26
48
|
}
|
|
@@ -50,3 +72,4 @@ export declare abstract class Field {
|
|
|
50
72
|
additionalSequelizeOptions(): {};
|
|
51
73
|
typeToString(): any;
|
|
52
74
|
}
|
|
75
|
+
export {};
|
package/lib/fields/index.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { UnixTimestampFieldOptions } from './unix-timestamp-field';
|
|
|
32
32
|
import { DateOnlyFieldOptions } from './date-only-field';
|
|
33
33
|
import { DatetimeNoTzFieldOptions } from './datetime-no-tz-field';
|
|
34
34
|
import { DatetimeTzFieldOptions } from './datetime-tz-field';
|
|
35
|
+
import { SnowflakeIdFieldOptions } from './snowflake-id-field';
|
|
35
36
|
export * from './array-field';
|
|
36
37
|
export * from './belongs-to-field';
|
|
37
38
|
export * from './belongs-to-many-field';
|
|
@@ -60,4 +61,5 @@ export * from './virtual-field';
|
|
|
60
61
|
export * from './nanoid-field';
|
|
61
62
|
export * from './encryption-field';
|
|
62
63
|
export * from './unix-timestamp-field';
|
|
63
|
-
export
|
|
64
|
+
export * from './snowflake-id-field';
|
|
65
|
+
export type FieldOptions = BaseFieldOptions | StringFieldOptions | IntegerFieldOptions | FloatFieldOptions | DecimalFieldOptions | DoubleFieldOptions | RealFieldOptions | JsonFieldOptions | JsonbFieldOptions | BooleanFieldOptions | RadioFieldOptions | TextFieldOptions | VirtualFieldOptions | ArrayFieldOptions | SetFieldOptions | TimeFieldOptions | DateFieldOptions | DatetimeTzFieldOptions | DatetimeNoTzFieldOptions | DateOnlyFieldOptions | UnixTimestampFieldOptions | UidFieldOptions | UUIDFieldOptions | NanoidFieldOptions | PasswordFieldOptions | ContextFieldOptions | BelongsToFieldOptions | HasOneFieldOptions | HasManyFieldOptions | BelongsToManyFieldOptions | EncryptionField | SnowflakeIdFieldOptions;
|
package/lib/fields/index.js
CHANGED
|
@@ -51,6 +51,7 @@ __reExport(fields_exports, require("./virtual-field"), module.exports);
|
|
|
51
51
|
__reExport(fields_exports, require("./nanoid-field"), module.exports);
|
|
52
52
|
__reExport(fields_exports, require("./encryption-field"), module.exports);
|
|
53
53
|
__reExport(fields_exports, require("./unix-timestamp-field"), module.exports);
|
|
54
|
+
__reExport(fields_exports, require("./snowflake-id-field"), module.exports);
|
|
54
55
|
// Annotate the CommonJS export names for ESM import in node:
|
|
55
56
|
0 && (module.exports = {
|
|
56
57
|
...require("./array-field"),
|
|
@@ -80,5 +81,6 @@ __reExport(fields_exports, require("./unix-timestamp-field"), module.exports);
|
|
|
80
81
|
...require("./virtual-field"),
|
|
81
82
|
...require("./nanoid-field"),
|
|
82
83
|
...require("./encryption-field"),
|
|
83
|
-
...require("./unix-timestamp-field")
|
|
84
|
+
...require("./unix-timestamp-field"),
|
|
85
|
+
...require("./snowflake-id-field")
|
|
84
86
|
});
|
|
@@ -13,7 +13,7 @@ export declare abstract class NumberField extends Field {
|
|
|
13
13
|
export declare class IntegerField extends NumberField {
|
|
14
14
|
get dataType(): DataTypes.IntegerDataTypeConstructor;
|
|
15
15
|
}
|
|
16
|
-
export interface IntegerFieldOptions extends BaseColumnFieldOptions {
|
|
16
|
+
export interface IntegerFieldOptions extends BaseColumnFieldOptions<'number'> {
|
|
17
17
|
type: 'integer';
|
|
18
18
|
}
|
|
19
19
|
export declare class BigIntField extends NumberField {
|
|
@@ -0,0 +1,29 @@
|
|
|
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 { DataTypes } from 'sequelize';
|
|
10
|
+
import { BaseColumnFieldOptions, Field } from './field';
|
|
11
|
+
interface Application {
|
|
12
|
+
snowflakeIdGenerator: {
|
|
13
|
+
generate(): number | BigInt;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class SnowflakeIdField extends Field {
|
|
17
|
+
private static app;
|
|
18
|
+
static setApp(app: Application): void;
|
|
19
|
+
get dataType(): DataTypes.BigIntDataTypeConstructor;
|
|
20
|
+
private setId;
|
|
21
|
+
init(): void;
|
|
22
|
+
bind(): void;
|
|
23
|
+
unbind(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface SnowflakeIdFieldOptions extends BaseColumnFieldOptions {
|
|
26
|
+
type: 'snowflakeId';
|
|
27
|
+
epoch?: number;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
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 __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
30
|
+
var snowflake_id_field_exports = {};
|
|
31
|
+
__export(snowflake_id_field_exports, {
|
|
32
|
+
SnowflakeIdField: () => SnowflakeIdField
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(snowflake_id_field_exports);
|
|
35
|
+
var import_sequelize = require("sequelize");
|
|
36
|
+
var import_field = require("./field");
|
|
37
|
+
const _SnowflakeIdField = class _SnowflakeIdField extends import_field.Field {
|
|
38
|
+
static setApp(app) {
|
|
39
|
+
this.app = app;
|
|
40
|
+
}
|
|
41
|
+
get dataType() {
|
|
42
|
+
return import_sequelize.DataTypes.BIGINT;
|
|
43
|
+
}
|
|
44
|
+
setId(name, instance) {
|
|
45
|
+
const value = instance.get(name);
|
|
46
|
+
if (!value) {
|
|
47
|
+
const generator = this.constructor.app.snowflakeIdGenerator;
|
|
48
|
+
instance.set(name, generator.generate());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
init() {
|
|
52
|
+
const { name } = this.options;
|
|
53
|
+
this.listener = (instance) => this.setId(name, instance);
|
|
54
|
+
this.bulkListener = async (instances) => {
|
|
55
|
+
for (const instance of instances) {
|
|
56
|
+
this.setId(name, instance);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
bind() {
|
|
61
|
+
super.bind();
|
|
62
|
+
this.on("beforeValidate", this.listener);
|
|
63
|
+
this.on("beforeSave", this.listener);
|
|
64
|
+
this.on("beforeBulkCreate", this.bulkListener);
|
|
65
|
+
}
|
|
66
|
+
unbind() {
|
|
67
|
+
super.unbind();
|
|
68
|
+
this.off("beforeValidate", this.listener);
|
|
69
|
+
this.off("beforeSave", this.listener);
|
|
70
|
+
this.off("beforeBulkCreate", this.bulkListener);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
__name(_SnowflakeIdField, "SnowflakeIdField");
|
|
74
|
+
__publicField(_SnowflakeIdField, "app");
|
|
75
|
+
let SnowflakeIdField = _SnowflakeIdField;
|
|
76
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
77
|
+
0 && (module.exports = {
|
|
78
|
+
SnowflakeIdField
|
|
79
|
+
});
|
|
@@ -14,7 +14,7 @@ export declare class StringField extends Field {
|
|
|
14
14
|
set(value: any): void;
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
export interface StringFieldOptions extends BaseColumnFieldOptions {
|
|
17
|
+
export interface StringFieldOptions extends BaseColumnFieldOptions<'string'> {
|
|
18
18
|
type: 'string';
|
|
19
19
|
length?: number;
|
|
20
20
|
trim?: boolean;
|
|
@@ -77,6 +77,11 @@ const _InheritedSyncRunner = class _InheritedSyncRunner {
|
|
|
77
77
|
let maxSequenceName;
|
|
78
78
|
if (childAttributes.id && childAttributes.id.autoIncrement) {
|
|
79
79
|
for (const parent of parents) {
|
|
80
|
+
const attributes2 = parent.model.getAttributes();
|
|
81
|
+
const parentIdField = attributes2["id"];
|
|
82
|
+
if (!(parentIdField == null ? void 0 : parentIdField.autoIncrement)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
80
85
|
const sequenceNameResult = await queryInterface.sequelize.query(
|
|
81
86
|
`SELECT column_default
|
|
82
87
|
FROM information_schema.columns
|
|
@@ -146,8 +151,14 @@ const _InheritedSyncRunner = class _InheritedSyncRunner {
|
|
|
146
151
|
const columns = await queryInterface.describeTable(tableName, options);
|
|
147
152
|
for (const attribute in childAttributes) {
|
|
148
153
|
const columnName = childAttributes[attribute].field;
|
|
154
|
+
const fieldName = childAttributes[attribute].fieldName;
|
|
149
155
|
if (!columns[columnName]) {
|
|
150
|
-
await queryInterface.addColumn(
|
|
156
|
+
await queryInterface.addColumn(
|
|
157
|
+
tableName,
|
|
158
|
+
columnName,
|
|
159
|
+
childAttributes[columnName] || childAttributes[fieldName],
|
|
160
|
+
options
|
|
161
|
+
);
|
|
151
162
|
}
|
|
152
163
|
}
|
|
153
164
|
}
|
|
@@ -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
|
@@ -209,6 +209,7 @@ export declare class Repository<TModelAttributes extends {} = any, TCreationAttr
|
|
|
209
209
|
*/
|
|
210
210
|
firstOrCreate(options: FirstOrCreateOptions): Promise<any>;
|
|
211
211
|
updateOrCreate(options: FirstOrCreateOptions): Promise<any>;
|
|
212
|
+
private validate;
|
|
212
213
|
/**
|
|
213
214
|
* Save instance to database
|
|
214
215
|
*
|
package/lib/repository.js
CHANGED
|
@@ -436,6 +436,9 @@ const _Repository = class _Repository {
|
|
|
436
436
|
}
|
|
437
437
|
return this.create({ values, transaction: transaction2, context, ...rest });
|
|
438
438
|
}
|
|
439
|
+
validate(options) {
|
|
440
|
+
this.collection.validate(options);
|
|
441
|
+
}
|
|
439
442
|
async create(options) {
|
|
440
443
|
if (Array.isArray(options.values)) {
|
|
441
444
|
return this.createMany({
|
|
@@ -450,6 +453,7 @@ const _Repository = class _Repository {
|
|
|
450
453
|
underscored: this.collection.options.underscored
|
|
451
454
|
});
|
|
452
455
|
const values = this.model.callSetters(guard.sanitize(options.values || {}), options);
|
|
456
|
+
this.validate({ values, context: options.context, operation: "create" });
|
|
453
457
|
const instance = await this.model.create(values, {
|
|
454
458
|
...options,
|
|
455
459
|
transaction: transaction2
|
|
@@ -494,6 +498,7 @@ const _Repository = class _Repository {
|
|
|
494
498
|
const transaction2 = await this.getTransaction(options);
|
|
495
499
|
const guard = import_update_guard.UpdateGuard.fromOptions(this.model, { ...options, underscored: this.collection.options.underscored });
|
|
496
500
|
const values = this.model.callSetters(guard.sanitize(options.values || {}), options);
|
|
501
|
+
this.validate({ values, context: options.context, operation: "update" });
|
|
497
502
|
if (options.individualHooks === false) {
|
|
498
503
|
const { model: Model2 } = this.collection;
|
|
499
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
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/database",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-alpha.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
7
7
|
"license": "AGPL-3.0",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@nocobase/logger": "
|
|
10
|
-
"@nocobase/utils": "
|
|
9
|
+
"@nocobase/logger": "2.0.0-alpha.2",
|
|
10
|
+
"@nocobase/utils": "2.0.0-alpha.2",
|
|
11
11
|
"async-mutex": "^0.3.2",
|
|
12
12
|
"chalk": "^4.1.1",
|
|
13
13
|
"cron-parser": "4.4.0",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"flat": "^5.0.2",
|
|
19
19
|
"glob": "^7.1.6",
|
|
20
20
|
"graphlib": "^2.1.8",
|
|
21
|
+
"joi": "^17.13.3",
|
|
21
22
|
"lodash": "^4.17.21",
|
|
22
23
|
"mathjs": "^10.6.1",
|
|
23
24
|
"nanoid": "^3.3.11",
|
|
@@ -38,5 +39,5 @@
|
|
|
38
39
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
|
39
40
|
"directory": "packages/database"
|
|
40
41
|
},
|
|
41
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "1322f486b248bef53ed8c8f42f0a39dfd02125fd"
|
|
42
43
|
}
|