@nocobase/database 0.14.0-alpha.2 → 0.14.0-alpha.4

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.
@@ -32,6 +32,7 @@ __export(collection_importer_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(collection_importer_exports);
34
34
  var import_path = __toESM(require("path"));
35
+ var import_fs = require("fs");
35
36
  var import_promises = require("fs/promises");
36
37
  var import_lodash = require("lodash");
37
38
  var import_utils = require("@nocobase/utils");
@@ -46,6 +47,9 @@ const _ImporterReader = class _ImporterReader {
46
47
  this.extensions = new Set(extensions);
47
48
  }
48
49
  async read() {
50
+ if (!(0, import_fs.existsSync)(this.directory)) {
51
+ return [];
52
+ }
49
53
  const files = await (0, import_promises.readdir)(this.directory, {
50
54
  encoding: "utf-8"
51
55
  });
package/lib/database.d.ts CHANGED
@@ -142,7 +142,7 @@ export declare class Database extends EventEmitter implements AsyncEmitter {
142
142
  isSqliteMemory(): boolean;
143
143
  auth(options?: Omit<QueryOptions, 'retry'> & {
144
144
  retry?: number | Pick<QueryOptions, 'retry'>;
145
- }): Promise<any>;
145
+ }): Promise<void>;
146
146
  prepare(): Promise<void>;
147
147
  reconnect(): Promise<void>;
148
148
  closed(): any;
package/lib/database.js CHANGED
@@ -61,6 +61,7 @@ var import_query_interface_builder = __toESM(require("./query-interface/query-in
61
61
  var import_utils2 = require("./utils");
62
62
  var import_value_parsers = require("./value-parsers");
63
63
  var import_view_collection = require("./view-collection");
64
+ var import_exponential_backoff = require("exponential-backoff");
64
65
  const DialectVersionAccessors = {
65
66
  sqlite: {
66
67
  sql: "select sqlite_version() as version",
@@ -205,7 +206,6 @@ const _Database = class _Database extends import_events.EventEmitter {
205
206
  migrations: this.migrations.callback(),
206
207
  context,
207
208
  storage: new import_umzug.SequelizeStorage({
208
- modelName: `${this.options.tablePrefix || ""}migrations`,
209
209
  ...migratorOptions.storage,
210
210
  sequelize: this.sequelize
211
211
  })
@@ -525,25 +525,30 @@ const _Database = class _Database extends import_events.EventEmitter {
525
525
  }
526
526
  async auth(options = {}) {
527
527
  const { retry = 10, ...others } = options;
528
- const delay = /* @__PURE__ */ __name((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)), "delay");
529
- let count = 1;
528
+ const startingDelay = 50;
529
+ const timeMultiple = 2;
530
+ let attemptNumber = 1;
530
531
  const authenticate = /* @__PURE__ */ __name(async () => {
531
532
  try {
532
533
  await this.sequelize.authenticate(others);
533
534
  console.log("Connection has been established successfully.");
534
- return true;
535
535
  } catch (error) {
536
- console.log(`Unable to connect to the database: ${error.message}`);
537
- if (count >= retry) {
538
- throw new Error("Connection failed, please check your database connection credentials and try again.");
539
- }
540
- console.log("reconnecting...", count);
541
- ++count;
542
- await delay(500);
543
- return await authenticate();
536
+ console.log(`Attempt ${attemptNumber}/${retry}: Unable to connect to the database: ${error.message}`);
537
+ const nextDelay = startingDelay * Math.pow(timeMultiple, attemptNumber - 1);
538
+ console.log(`Will retry in ${nextDelay}ms...`);
539
+ attemptNumber++;
540
+ throw error;
544
541
  }
545
542
  }, "authenticate");
546
- return await authenticate();
543
+ try {
544
+ await (0, import_exponential_backoff.backOff)(authenticate, {
545
+ numOfAttempts: retry,
546
+ startingDelay,
547
+ timeMultiple
548
+ });
549
+ } catch (error) {
550
+ throw new Error("Connection failed, please check your database connection credentials and try again.");
551
+ }
547
552
  }
548
553
  async prepare() {
549
554
  if (this.inDialect("postgres") && this.options.schema && this.options.schema != "public") {
@@ -0,0 +1,2 @@
1
+ import { IDatabaseOptions } from './database';
2
+ export declare function parseDatabaseOptionsFromEnv(): Promise<IDatabaseOptions>;
package/lib/helpers.js ADDED
@@ -0,0 +1,120 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var helpers_exports = {};
30
+ __export(helpers_exports, {
31
+ parseDatabaseOptionsFromEnv: () => parseDatabaseOptionsFromEnv
32
+ });
33
+ module.exports = __toCommonJS(helpers_exports);
34
+ var import_fs = __toESM(require("fs"));
35
+ function getEnvValue(key, defaultValue) {
36
+ return process.env[key] || defaultValue;
37
+ }
38
+ __name(getEnvValue, "getEnvValue");
39
+ function isFilePath(value) {
40
+ return import_fs.default.promises.stat(value).then((stats) => stats.isFile()).catch((err) => {
41
+ if (err.code === "ENOENT") {
42
+ return false;
43
+ }
44
+ throw err;
45
+ });
46
+ }
47
+ __name(isFilePath, "isFilePath");
48
+ function getValueOrFileContent(envVarName) {
49
+ const value = getEnvValue(envVarName);
50
+ if (!value) {
51
+ return Promise.resolve(null);
52
+ }
53
+ return isFilePath(value).then((isFile) => {
54
+ if (isFile) {
55
+ return import_fs.default.promises.readFile(value, "utf8");
56
+ }
57
+ return value;
58
+ }).catch((error) => {
59
+ console.error(`Failed to read file content for environment variable ${envVarName}.`);
60
+ throw error;
61
+ });
62
+ }
63
+ __name(getValueOrFileContent, "getValueOrFileContent");
64
+ function extractSSLOptionsFromEnv() {
65
+ return Promise.all([
66
+ getValueOrFileContent("DB_DIALECT_OPTIONS_SSL_MODE"),
67
+ getValueOrFileContent("DB_DIALECT_OPTIONS_SSL_CA"),
68
+ getValueOrFileContent("DB_DIALECT_OPTIONS_SSL_KEY"),
69
+ getValueOrFileContent("DB_DIALECT_OPTIONS_SSL_CERT"),
70
+ getValueOrFileContent("DB_DIALECT_OPTIONS_SSL_REJECT_UNAUTHORIZED")
71
+ ]).then(([mode, ca, key, cert, rejectUnauthorized]) => {
72
+ const sslOptions = {};
73
+ if (mode)
74
+ sslOptions["mode"] = mode;
75
+ if (ca)
76
+ sslOptions["ca"] = ca;
77
+ if (key)
78
+ sslOptions["key"] = key;
79
+ if (cert)
80
+ sslOptions["cert"] = cert;
81
+ if (rejectUnauthorized)
82
+ sslOptions["rejectUnauthorized"] = rejectUnauthorized === "true";
83
+ return sslOptions;
84
+ });
85
+ }
86
+ __name(extractSSLOptionsFromEnv, "extractSSLOptionsFromEnv");
87
+ async function parseDatabaseOptionsFromEnv() {
88
+ const databaseOptions = {
89
+ logging: process.env.DB_LOGGING == "on" ? customLogger : false,
90
+ dialect: process.env.DB_DIALECT,
91
+ storage: process.env.DB_STORAGE,
92
+ username: process.env.DB_USER,
93
+ password: process.env.DB_PASSWORD,
94
+ database: process.env.DB_DATABASE,
95
+ host: process.env.DB_HOST,
96
+ port: process.env.DB_PORT,
97
+ timezone: process.env.DB_TIMEZONE,
98
+ tablePrefix: process.env.DB_TABLE_PREFIX,
99
+ schema: process.env.DB_SCHEMA,
100
+ underscored: process.env.DB_UNDERSCORED === "true"
101
+ };
102
+ const sslOptions = await extractSSLOptionsFromEnv();
103
+ if (Object.keys(sslOptions).length) {
104
+ databaseOptions.dialectOptions = databaseOptions.dialectOptions || {};
105
+ databaseOptions.dialectOptions["ssl"] = sslOptions;
106
+ }
107
+ return databaseOptions;
108
+ }
109
+ __name(parseDatabaseOptionsFromEnv, "parseDatabaseOptionsFromEnv");
110
+ function customLogger(queryString, queryObject) {
111
+ console.log(queryString);
112
+ if (queryObject == null ? void 0 : queryObject.bind) {
113
+ console.log(queryObject.bind);
114
+ }
115
+ }
116
+ __name(customLogger, "customLogger");
117
+ // Annotate the CommonJS export names for ESM import in node:
118
+ 0 && (module.exports = {
119
+ parseDatabaseOptionsFromEnv
120
+ });
package/lib/index.d.ts CHANGED
@@ -24,3 +24,4 @@ export { snakeCase } from './utils';
24
24
  export * from './value-parsers';
25
25
  export * from './view-collection';
26
26
  export * from './view/view-inference';
27
+ export * from './helpers';
package/lib/index.js CHANGED
@@ -75,6 +75,7 @@ var import_utils = require("./utils");
75
75
  __reExport(src_exports, require("./value-parsers"), module.exports);
76
76
  __reExport(src_exports, require("./view-collection"), module.exports);
77
77
  __reExport(src_exports, require("./view/view-inference"), module.exports);
78
+ __reExport(src_exports, require("./helpers"), module.exports);
78
79
  // Annotate the CommonJS export names for ESM import in node:
79
80
  0 && (module.exports = {
80
81
  BaseError,
@@ -116,5 +117,6 @@ __reExport(src_exports, require("./view/view-inference"), module.exports);
116
117
  ...require("./update-associations"),
117
118
  ...require("./value-parsers"),
118
119
  ...require("./view-collection"),
119
- ...require("./view/view-inference")
120
+ ...require("./view/view-inference"),
121
+ ...require("./helpers")
120
122
  });
@@ -268,9 +268,6 @@ async function updateMultipleAssociation(model, key, value, options = {}) {
268
268
  if (!association) {
269
269
  return false;
270
270
  }
271
- if (association.through && association.through.model.options.view) {
272
- return false;
273
- }
274
271
  if (!["undefined", "string", "number", "object"].includes(typeof value)) {
275
272
  return false;
276
273
  }
@@ -18,6 +18,7 @@ export declare class UpdateGuard {
18
18
  setAssociationKeysToBeUpdate(associationKeysToBeUpdate: AssociationKeysToBeUpdate): void;
19
19
  setWhiteList(whiteList: WhiteList): void;
20
20
  setBlackList(blackList: BlackList): void;
21
+ checkValues(values: any): void;
21
22
  /**
22
23
  * Sanitize values by whitelist blacklist
23
24
  * @param values
@@ -71,6 +71,35 @@ const _UpdateGuard = class _UpdateGuard {
71
71
  setBlackList(blackList) {
72
72
  this.blackList = blackList;
73
73
  }
74
+ checkValues(values) {
75
+ const dfs = /* @__PURE__ */ __name((values2, model) => {
76
+ const associations = model.associations;
77
+ const belongsToManyThroughNames = [];
78
+ const associationValueKeys = Object.keys(associations).filter((key) => {
79
+ return Object.keys(values2).includes(key);
80
+ });
81
+ const belongsToManyValueKeys = associationValueKeys.filter((key) => {
82
+ return associations[key].associationType === "BelongsToMany";
83
+ });
84
+ const hasManyValueKeys = associationValueKeys.filter((key) => {
85
+ return associations[key].associationType === "HasMany";
86
+ });
87
+ for (const belongsToManyKey of belongsToManyValueKeys) {
88
+ const association = associations[belongsToManyKey];
89
+ const through = association.through.model;
90
+ belongsToManyThroughNames.push(through.name);
91
+ }
92
+ for (const hasManyKey of hasManyValueKeys) {
93
+ const association = associations[hasManyKey];
94
+ if (belongsToManyThroughNames.includes(association.target.name)) {
95
+ throw new Error(
96
+ `HasMany association ${hasManyKey} cannot be used with BelongsToMany association ${association.target.name} with same through model`
97
+ );
98
+ }
99
+ }
100
+ }, "dfs");
101
+ dfs(values, this.model);
102
+ }
74
103
  /**
75
104
  * Sanitize values by whitelist blacklist
76
105
  * @param values
@@ -80,6 +109,7 @@ const _UpdateGuard = class _UpdateGuard {
80
109
  if (!this.model) {
81
110
  throw new Error("please set model first");
82
111
  }
112
+ this.checkValues(values);
83
113
  const associations = this.model.associations;
84
114
  const associationsValues = import_lodash.default.pick(values, Object.keys(associations));
85
115
  const listOfAssociation = /* @__PURE__ */ __name((list, association) => {
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@nocobase/database",
3
- "version": "0.14.0-alpha.2",
3
+ "version": "0.14.0-alpha.4",
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": "0.14.0-alpha.2",
10
- "@nocobase/utils": "0.14.0-alpha.2",
9
+ "@nocobase/logger": "0.14.0-alpha.4",
10
+ "@nocobase/utils": "0.14.0-alpha.4",
11
11
  "async-mutex": "^0.3.2",
12
12
  "cron-parser": "4.4.0",
13
13
  "dayjs": "^1.11.8",
14
14
  "deepmerge": "^4.2.2",
15
15
  "excel-date-to-js": "^1.1.5",
16
+ "exponential-backoff": "^3.1.1",
16
17
  "flat": "^5.0.2",
17
18
  "glob": "^7.1.6",
18
19
  "graphlib": "^2.1.8",
@@ -29,5 +30,5 @@
29
30
  "url": "git+https://github.com/nocobase/nocobase.git",
30
31
  "directory": "packages/database"
31
32
  },
32
- "gitHead": "ea841d4a1e8c37aa77b4fbae6a6a4937e2aa3c0a"
33
+ "gitHead": "e2aab7863bbdfd76afc7164272b275e868ac59c0"
33
34
  }