@nocobase/database 2.3.0-alpha.1 → 2.3.0-beta.10

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/database.js CHANGED
@@ -354,7 +354,7 @@ const _Database = class _Database extends import_events.EventEmitter {
354
354
  if (index.fields) {
355
355
  index.fields = index.fields.map((field) => {
356
356
  if (field.name) {
357
- return { name: (0, import_utils3.snakeCase)(field.name), ...field };
357
+ return { ...field, name: (0, import_utils3.snakeCase)(field.name) };
358
358
  }
359
359
  return (0, import_utils3.snakeCase)(field);
360
360
  });
@@ -772,7 +772,10 @@ const _Database = class _Database extends import_events.EventEmitter {
772
772
  },
773
773
  async onDump(dumper, collection) {
774
774
  try {
775
- const viewDef = await collection.db.queryInterface.viewDef(collection.getTableNameWithSchemaAsString());
775
+ const viewDef = await collection.db.queryInterface.viewDef({
776
+ viewName: collection.model.tableName,
777
+ schema: collection.db.inDialect("postgres") ? collection.collectionSchema() : void 0
778
+ });
776
779
  dumper.writeSQLContent(`view-${collection.name}`, {
777
780
  sql: [
778
781
  `DROP VIEW IF EXISTS ${collection.getTableNameWithSchemaAsString()}`,
@@ -50,6 +50,23 @@ const pushAttribute = /* @__PURE__ */ __name((node, attribute) => {
50
50
  node.attributes.push(attribute);
51
51
  }
52
52
  }, "pushAttribute");
53
+ const getRootModelOrderFields = /* @__PURE__ */ __name((node) => {
54
+ if (!import_lodash.default.isArray(node.order)) {
55
+ return [];
56
+ }
57
+ const fields = [];
58
+ for (const orderItem of node.order) {
59
+ if (!import_lodash.default.isArray(orderItem) || orderItem.length !== 2 || !import_lodash.default.isString(orderItem[0])) {
60
+ continue;
61
+ }
62
+ const fieldName = orderItem[0];
63
+ const attribute = node.model.rawAttributes[fieldName] || Object.values(node.model.rawAttributes).find((item) => item.field === fieldName);
64
+ if (attribute) {
65
+ fields.push(attribute.field || fieldName);
66
+ }
67
+ }
68
+ return fields;
69
+ }, "getRootModelOrderFields");
53
70
  const EagerLoadingNodeProto = {
54
71
  afterBuild(db) {
55
72
  const collection = db.modelCollection.get(this.model);
@@ -218,11 +235,15 @@ const _EagerLoadingTree = class _EagerLoadingTree {
218
235
  if (!primaryKeyField) {
219
236
  throw new Error(`Model ${node.model.name} does not have primary key`);
220
237
  }
238
+ const group = [`${node.model.name}.${primaryKeyField}`];
239
+ if (this.db.inDialect("mssql") || this.db.isMySQLCompatibleDialect()) {
240
+ group.push(...getRootModelOrderFields(node).map((field) => `${node.model.name}.${field}`));
241
+ }
221
242
  const ids2 = (await node.model.findAll({
222
243
  ...this.rootQueryOptions,
223
244
  includeIgnoreAttributes: false,
224
245
  attributes: [primaryKeyField],
225
- group: `${node.model.name}.${primaryKeyField}`,
246
+ group: import_lodash.default.uniq(group),
226
247
  transaction,
227
248
  include: (0, import_utils.processIncludes)(includeForFilter, node.model),
228
249
  raw: true
@@ -128,6 +128,8 @@ const _BelongsToManyField = class _BelongsToManyField extends import_relation_fi
128
128
  let Through;
129
129
  if (database.hasCollection(through)) {
130
130
  Through = database.getCollection(through);
131
+ Through.options.sourceCollectionName ??= this.collection.name;
132
+ Through.options.targetCollectionName ??= this.target;
131
133
  } else {
132
134
  const throughCollectionOptions = {
133
135
  name: through,
@@ -41,7 +41,8 @@ __export(field_exports, {
41
41
  });
42
42
  module.exports = __toCommonJS(field_exports);
43
43
  var import_lodash = __toESM(require("lodash"));
44
- var import_utils = require("../utils");
44
+ var import_utils = require("@nocobase/utils");
45
+ var import_utils2 = require("../utils");
45
46
  const _Field = class _Field {
46
47
  options;
47
48
  context;
@@ -94,7 +95,7 @@ const _Field = class _Field {
94
95
  return this.options.field;
95
96
  }
96
97
  if (this.database.options.underscored) {
97
- return (0, import_utils.snakeCase)(this.name);
98
+ return (0, import_utils2.snakeCase)(this.name);
98
99
  }
99
100
  return this.name;
100
101
  }
@@ -156,6 +157,23 @@ const _Field = class _Field {
156
157
  Object.assign(opts, { type: this.database.sequelize.normalizeDataType(this.dataType) });
157
158
  }
158
159
  Object.assign(opts, this.additionalSequelizeOptions());
160
+ if (this.options.interface === "richText") {
161
+ const { name } = this;
162
+ const originalSetter = opts.set;
163
+ const sanitizeValue = /* @__PURE__ */ __name((value) => typeof value === "string" ? (0, import_utils.sanitizeRichTextHtml)(value) : value, "sanitizeValue");
164
+ opts.set = function(value) {
165
+ if (typeof originalSetter === "function") {
166
+ originalSetter.call(this, sanitizeValue(value));
167
+ const currentValue = this.getDataValue(name);
168
+ const sanitizedValue = sanitizeValue(currentValue);
169
+ if (!Object.is(sanitizedValue, currentValue)) {
170
+ this.setDataValue(name, sanitizedValue);
171
+ }
172
+ return;
173
+ }
174
+ this.setDataValue(name, sanitizeValue(value));
175
+ };
176
+ }
159
177
  return opts;
160
178
  }
161
179
  additionalSequelizeOptions() {
package/lib/model.js CHANGED
@@ -43,6 +43,7 @@ __export(model_exports, {
43
43
  });
44
44
  module.exports = __toCommonJS(model_exports);
45
45
  var import_lodash = __toESM(require("lodash"));
46
+ var import_utils = require("@nocobase/utils");
46
47
  var import_sequelize = require("sequelize");
47
48
  var import_sync_runner = require("./sync-runner");
48
49
  const _ = import_lodash.default;
@@ -65,6 +66,9 @@ const _Model = class _Model extends import_sequelize.Model {
65
66
  } else {
66
67
  result[key] = values[key];
67
68
  }
69
+ if ((field == null ? void 0 : field.options.interface) === "richText" && typeof result[key] === "string") {
70
+ result[key] = (0, import_utils.sanitizeRichTextHtml)(result[key]);
71
+ }
68
72
  }
69
73
  return result;
70
74
  }
@@ -32,7 +32,11 @@ __export(string_exports, {
32
32
  module.exports = __toCommonJS(string_exports);
33
33
  var import_sequelize = require("sequelize");
34
34
  var import_utils = require("./utils");
35
- function escapeLike(value) {
35
+ function escapeLike(value, ctx) {
36
+ if (ctx.db.sequelize.getDialect() === "mssql") {
37
+ const escapedValue = value.replaceAll("[", "[[]");
38
+ return escapedValue.replace(/[%_]/g, (character) => `[${character}]`);
39
+ }
36
40
  return value.replace(/[_%]/g, "\\$&");
37
41
  }
38
42
  __name(escapeLike, "escapeLike");
@@ -85,13 +89,13 @@ var string_default = {
85
89
  }
86
90
  if (Array.isArray(value)) {
87
91
  const conditions = value.map(
88
- (item) => getFieldExpression(`%${escapeLike(item)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE")
92
+ (item) => getFieldExpression(`%${escapeLike(item, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE")
89
93
  );
90
94
  return {
91
95
  [import_sequelize.Op.or]: conditions
92
96
  };
93
97
  }
94
- return getFieldExpression(`%${escapeLike(value)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE");
98
+ return getFieldExpression(`%${escapeLike(value, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE");
95
99
  },
96
100
  $notIncludes(value, ctx) {
97
101
  if (value === null) {
@@ -101,56 +105,56 @@ var string_default = {
101
105
  }
102
106
  if (Array.isArray(value)) {
103
107
  const conditions = value.map(
104
- (item) => getFieldExpression(`%${escapeLike(item)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE")
108
+ (item) => getFieldExpression(`%${escapeLike(item, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE")
105
109
  );
106
110
  return {
107
111
  [import_sequelize.Op.and]: conditions
108
112
  };
109
113
  }
110
- return getFieldExpression(`%${escapeLike(value)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE");
114
+ return getFieldExpression(`%${escapeLike(value, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE");
111
115
  },
112
116
  $startsWith(value, ctx) {
113
117
  if (Array.isArray(value)) {
114
118
  const conditions = value.map(
115
- (item) => getFieldExpression(`${escapeLike(item)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE")
119
+ (item) => getFieldExpression(`${escapeLike(item, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE")
116
120
  );
117
121
  return {
118
122
  [import_sequelize.Op.or]: conditions
119
123
  };
120
124
  }
121
- return getFieldExpression(`${escapeLike(value)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE");
125
+ return getFieldExpression(`${escapeLike(value, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE");
122
126
  },
123
127
  $notStartsWith(value, ctx) {
124
128
  if (Array.isArray(value)) {
125
129
  const conditions = value.map(
126
- (item) => getFieldExpression(`${escapeLike(item)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE")
130
+ (item) => getFieldExpression(`${escapeLike(item, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE")
127
131
  );
128
132
  return {
129
133
  [import_sequelize.Op.and]: conditions
130
134
  };
131
135
  }
132
- return getFieldExpression(`${escapeLike(value)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE");
136
+ return getFieldExpression(`${escapeLike(value, ctx)}%`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE");
133
137
  },
134
138
  $endWith(value, ctx) {
135
139
  if (Array.isArray(value)) {
136
140
  const conditions = value.map(
137
- (item) => getFieldExpression(`%${escapeLike(item)}`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE")
141
+ (item) => getFieldExpression(`%${escapeLike(item, ctx)}`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE")
138
142
  );
139
143
  return {
140
144
  [import_sequelize.Op.or]: conditions
141
145
  };
142
146
  }
143
- return getFieldExpression(`%${escapeLike(value)}`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE");
147
+ return getFieldExpression(`%${escapeLike(value, ctx)}`, ctx, (0, import_utils.isPg)(ctx) ? "ILIKE" : "LIKE");
144
148
  },
145
149
  $notEndWith(value, ctx) {
146
150
  if (Array.isArray(value)) {
147
151
  const conditions = value.map(
148
- (item) => getFieldExpression(`%${escapeLike(item)}`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE")
152
+ (item) => getFieldExpression(`%${escapeLike(item, ctx)}`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE")
149
153
  );
150
154
  return {
151
155
  [import_sequelize.Op.and]: conditions
152
156
  };
153
157
  }
154
- return getFieldExpression(`%${escapeLike(value)}`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE");
158
+ return getFieldExpression(`%${escapeLike(value, ctx)}`, ctx, (0, import_utils.isPg)(ctx) ? "NOT ILIKE" : "NOT LIKE");
155
159
  }
156
160
  };
@@ -24,7 +24,10 @@ export default class MysqlQueryInterface extends QueryInterface {
24
24
  };
25
25
  }>;
26
26
  parseSQL(sql: string): any;
27
- viewDef(viewName: string): Promise<string>;
27
+ viewDef(options: {
28
+ viewName: string;
29
+ schema?: string;
30
+ }): Promise<string>;
28
31
  showTableDefinition(tableInfo: TableInfo): Promise<any>;
29
32
  getAutoIncrementInfo(options: {
30
33
  tableInfo: TableInfo;
@@ -67,7 +67,7 @@ const _MysqlQueryInterface = class _MysqlQueryInterface extends import_query_int
67
67
  }
68
68
  async viewColumnUsage(options) {
69
69
  try {
70
- const { ast } = this.parseSQL(await this.viewDef(options.viewName));
70
+ const { ast } = this.parseSQL(await this.viewDef(options));
71
71
  const columns = ast.columns;
72
72
  const results = [];
73
73
  for (const column of columns) {
@@ -90,8 +90,13 @@ const _MysqlQueryInterface = class _MysqlQueryInterface extends import_query_int
90
90
  parseSQL(sql) {
91
91
  return import_sql_parser.default.parse(sql);
92
92
  }
93
- async viewDef(viewName) {
94
- const viewDefinition = await this.db.sequelize.query(`SHOW CREATE VIEW ${viewName}`, { type: "SELECT" });
93
+ async viewDef(options) {
94
+ const tableName = options.schema ? {
95
+ schema: options.schema,
96
+ tableName: options.viewName
97
+ } : options.viewName;
98
+ const quotedViewName = this.db.utils.quoteTable(tableName);
99
+ const viewDefinition = await this.db.sequelize.query(`SHOW CREATE VIEW ${quotedViewName}`, { type: "SELECT" });
95
100
  const createView = viewDefinition[0]["Create View"];
96
101
  const regex = /(?<=AS\s)([\s\S]*)/i;
97
102
  const match = createView.match(regex);
@@ -30,9 +30,15 @@ export default class PostgresQueryInterface extends QueryInterface {
30
30
  listViews(options?: {
31
31
  schema?: string;
32
32
  }): Promise<[unknown[], unknown]>;
33
- viewDef(viewName: string): Promise<string>;
33
+ viewDef(options: {
34
+ viewName: string;
35
+ schema?: string;
36
+ }): Promise<string>;
34
37
  parseSQL(sql: string): any;
35
- viewColumnUsage(options: any): Promise<{
38
+ viewColumnUsage(options: {
39
+ viewName: string;
40
+ schema?: string;
41
+ }): Promise<{
36
42
  [view_column_name: string]: {
37
43
  column_name: string;
38
44
  table_name: string;
@@ -113,26 +113,33 @@ const _PostgresQueryInterface = class _PostgresQueryInterface extends import_que
113
113
  async listViews(options) {
114
114
  var _a;
115
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
- ` : `
116
+ const sql = `
122
117
  SELECT viewname as name, definition, schemaname as schema
123
118
  FROM pg_views
124
- WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
119
+ WHERE schemaname = :targetSchema
125
120
  ORDER BY viewname;
126
121
  `;
127
- return await this.db.sequelize.query(sql, { type: "SELECT" });
122
+ return await this.db.sequelize.query(sql, {
123
+ replacements: {
124
+ targetSchema
125
+ },
126
+ type: "SELECT"
127
+ });
128
128
  }
129
- async viewDef(viewName) {
130
- const [schema, name] = viewName.split(".");
129
+ async viewDef(options) {
130
+ const { viewName } = options;
131
+ const schema = options.schema || this.db.options.schema || "public";
131
132
  const viewDefQuery = await this.db.sequelize.query(
132
133
  `
133
- select pg_get_viewdef(format('%I.%I', '${schema}', '${name}')::regclass, true) as definition
134
+ select pg_get_viewdef(format('%I.%I', :schema, :viewName)::regclass, true) as definition
134
135
  `,
135
- { type: "SELECT" }
136
+ {
137
+ replacements: {
138
+ schema,
139
+ viewName
140
+ },
141
+ type: "SELECT"
142
+ }
136
143
  );
137
144
  return import_lodash.default.trim(viewDefQuery[0]["definition"]);
138
145
  }
@@ -142,15 +149,22 @@ const _PostgresQueryInterface = class _PostgresQueryInterface extends import_que
142
149
  });
143
150
  }
144
151
  async viewColumnUsage(options) {
145
- const { viewName, schema = "public" } = options;
152
+ const { viewName } = options;
153
+ const schema = options.schema || this.db.options.schema || "public";
146
154
  const sql = `
147
155
  SELECT *
148
156
  FROM information_schema.view_column_usage
149
- WHERE view_schema = '${schema}'
150
- AND view_name = '${viewName}';
157
+ WHERE view_schema = :schema
158
+ AND view_name = :viewName;
151
159
  `;
152
- const columnUsages = await this.db.sequelize.query(sql, { type: "SELECT" });
153
- const def = await this.viewDef(`${schema}.${viewName}`);
160
+ const columnUsages = await this.db.sequelize.query(sql, {
161
+ replacements: {
162
+ schema,
163
+ viewName
164
+ },
165
+ type: "SELECT"
166
+ });
167
+ const def = await this.viewDef({ schema, viewName });
154
168
  try {
155
169
  const { ast } = this.parseSQL(def);
156
170
  const columns = ast[0].columns;
@@ -21,7 +21,10 @@ export default abstract class QueryInterface {
21
21
  abstract listViews(options?: {
22
22
  schema?: string;
23
23
  }): any;
24
- abstract viewDef(viewName: string): Promise<string>;
24
+ abstract viewDef(options: {
25
+ viewName: string;
26
+ schema?: string;
27
+ }): Promise<string>;
25
28
  abstract viewColumnUsage(options: {
26
29
  viewName: string;
27
30
  schema?: string;
@@ -24,7 +24,10 @@ export default class SqliteQueryInterface extends QueryInterface {
24
24
  };
25
25
  }>;
26
26
  parseSQL(sql: string): any;
27
- viewDef(viewName: string): Promise<string>;
27
+ viewDef(options: {
28
+ viewName: string;
29
+ schema?: string;
30
+ }): Promise<string>;
28
31
  showTableDefinition(tableInfo: TableInfo): Promise<any>;
29
32
  getAutoIncrementInfo(options: {
30
33
  tableInfo: TableInfo;
@@ -70,7 +70,7 @@ const _SqliteQueryInterface = class _SqliteQueryInterface extends import_query_i
70
70
  }
71
71
  async viewColumnUsage(options) {
72
72
  try {
73
- const { ast } = this.parseSQL(await this.viewDef(options.viewName));
73
+ const { ast } = this.parseSQL(await this.viewDef(options));
74
74
  const columns = ast.columns;
75
75
  const results = [];
76
76
  for (const column of columns) {
@@ -93,12 +93,15 @@ const _SqliteQueryInterface = class _SqliteQueryInterface extends import_query_i
93
93
  parseSQL(sql) {
94
94
  return import_sql_parser.default.parse(sql);
95
95
  }
96
- async viewDef(viewName) {
96
+ async viewDef(options) {
97
97
  const viewDefinition = await this.db.sequelize.query(
98
98
  `SELECT sql
99
99
  FROM sqlite_master
100
- WHERE name = '${viewName}' AND type = 'view'`,
100
+ WHERE name = :viewName AND type = 'view'`,
101
101
  {
102
+ replacements: {
103
+ viewName: options.viewName
104
+ },
102
105
  type: "SELECT"
103
106
  }
104
107
  );
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@nocobase/database",
3
- "version": "2.3.0-alpha.1",
3
+ "version": "2.3.0-beta.10",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
7
7
  "license": "Apache-2.0",
8
8
  "dependencies": {
9
- "@nocobase/logger": "2.3.0-alpha.1",
10
- "@nocobase/utils": "2.3.0-alpha.1",
9
+ "@nocobase/logger": "2.3.0-beta.10",
10
+ "@nocobase/utils": "2.3.0-beta.10",
11
11
  "async-mutex": "^0.3.2",
12
12
  "chalk": "^4.1.1",
13
13
  "cron-parser": "4.4.0",
@@ -38,5 +38,5 @@
38
38
  "url": "git+https://github.com/nocobase/nocobase.git",
39
39
  "directory": "packages/database"
40
40
  },
41
- "gitHead": "2377df8ceb12549149017f7f14a61207bf6e49a2"
41
+ "gitHead": "7aa3dce6cf3201752882f7012c72997898015d89"
42
42
  }