@tachybase/module-collection 1.6.24 → 1.6.26

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.
Files changed (110) hide show
  1. package/dist/externalVersion.js +2 -2
  2. package/{LICENSE → dist/node_modules/node-sql-parser/LICENSE} +3 -3
  3. package/dist/node_modules/node-sql-parser/ast/postgresql.ts +2063 -0
  4. package/dist/node_modules/node-sql-parser/build/athena.d.ts +1 -0
  5. package/dist/node_modules/node-sql-parser/build/athena.js +2 -0
  6. package/dist/node_modules/node-sql-parser/build/bigquery.d.ts +1 -0
  7. package/dist/node_modules/node-sql-parser/build/bigquery.js +2 -0
  8. package/dist/node_modules/node-sql-parser/build/db2.d.ts +1 -0
  9. package/dist/node_modules/node-sql-parser/build/db2.js +2 -0
  10. package/dist/node_modules/node-sql-parser/build/flinksql.d.ts +1 -0
  11. package/dist/node_modules/node-sql-parser/build/flinksql.js +2 -0
  12. package/dist/node_modules/node-sql-parser/build/hive.d.ts +1 -0
  13. package/dist/node_modules/node-sql-parser/build/hive.js +2 -0
  14. package/dist/node_modules/node-sql-parser/build/mariadb.d.ts +1 -0
  15. package/dist/node_modules/node-sql-parser/build/mariadb.js +2 -0
  16. package/dist/node_modules/node-sql-parser/build/mysql.d.ts +1 -0
  17. package/dist/node_modules/node-sql-parser/build/mysql.js +2 -0
  18. package/dist/node_modules/node-sql-parser/build/noql.d.ts +1 -0
  19. package/dist/node_modules/node-sql-parser/build/noql.js +2 -0
  20. package/dist/node_modules/node-sql-parser/build/postgresql.d.ts +1 -0
  21. package/dist/node_modules/node-sql-parser/build/postgresql.js +2 -0
  22. package/dist/node_modules/node-sql-parser/build/redshift.d.ts +1 -0
  23. package/dist/node_modules/node-sql-parser/build/redshift.js +2 -0
  24. package/dist/node_modules/node-sql-parser/build/snowflake.d.ts +1 -0
  25. package/dist/node_modules/node-sql-parser/build/snowflake.js +2 -0
  26. package/dist/node_modules/node-sql-parser/build/sqlite.d.ts +1 -0
  27. package/dist/node_modules/node-sql-parser/build/sqlite.js +2 -0
  28. package/dist/node_modules/node-sql-parser/build/transactsql.d.ts +1 -0
  29. package/dist/node_modules/node-sql-parser/build/transactsql.js +2 -0
  30. package/dist/node_modules/node-sql-parser/build/trino.d.ts +1 -0
  31. package/dist/node_modules/node-sql-parser/build/trino.js +2 -0
  32. package/dist/node_modules/node-sql-parser/index.d.ts +1 -0
  33. package/dist/node_modules/node-sql-parser/index.js +1 -0
  34. package/dist/node_modules/node-sql-parser/lib/aggregation.js +42 -0
  35. package/dist/node_modules/node-sql-parser/lib/alter.js +211 -0
  36. package/dist/node_modules/node-sql-parser/lib/analyze.js +40 -0
  37. package/dist/node_modules/node-sql-parser/lib/array-struct.js +60 -0
  38. package/dist/node_modules/node-sql-parser/lib/assign.js +33 -0
  39. package/dist/node_modules/node-sql-parser/lib/binary.js +48 -0
  40. package/dist/node_modules/node-sql-parser/lib/case.js +39 -0
  41. package/dist/node_modules/node-sql-parser/lib/collate.js +37 -0
  42. package/dist/node_modules/node-sql-parser/lib/column.js +259 -0
  43. package/dist/node_modules/node-sql-parser/lib/command.js +306 -0
  44. package/dist/node_modules/node-sql-parser/lib/comment.js +53 -0
  45. package/dist/node_modules/node-sql-parser/lib/constrain.js +49 -0
  46. package/dist/node_modules/node-sql-parser/lib/create.js +496 -0
  47. package/dist/node_modules/node-sql-parser/lib/delete.js +44 -0
  48. package/dist/node_modules/node-sql-parser/lib/exec.js +37 -0
  49. package/dist/node_modules/node-sql-parser/lib/explain.js +27 -0
  50. package/dist/node_modules/node-sql-parser/lib/expr.js +147 -0
  51. package/dist/node_modules/node-sql-parser/lib/func.js +231 -0
  52. package/dist/node_modules/node-sql-parser/lib/index-definition.js +106 -0
  53. package/dist/node_modules/node-sql-parser/lib/insert.js +112 -0
  54. package/dist/node_modules/node-sql-parser/lib/interval.js +29 -0
  55. package/dist/node_modules/node-sql-parser/lib/json.js +36 -0
  56. package/dist/node_modules/node-sql-parser/lib/limit.js +45 -0
  57. package/dist/node_modules/node-sql-parser/lib/over.js +39 -0
  58. package/dist/node_modules/node-sql-parser/lib/parser.all.js +36 -0
  59. package/dist/node_modules/node-sql-parser/lib/parser.js +88 -0
  60. package/dist/node_modules/node-sql-parser/lib/parser.single.js +23 -0
  61. package/dist/node_modules/node-sql-parser/lib/proc.js +39 -0
  62. package/dist/node_modules/node-sql-parser/lib/select.js +138 -0
  63. package/dist/node_modules/node-sql-parser/lib/show.js +89 -0
  64. package/dist/node_modules/node-sql-parser/lib/sql.js +43 -0
  65. package/dist/node_modules/node-sql-parser/lib/tables.js +277 -0
  66. package/dist/node_modules/node-sql-parser/lib/transaction.js +32 -0
  67. package/dist/node_modules/node-sql-parser/lib/union.js +83 -0
  68. package/dist/node_modules/node-sql-parser/lib/update.js +62 -0
  69. package/dist/node_modules/node-sql-parser/lib/util.js +458 -0
  70. package/dist/node_modules/node-sql-parser/lib/window.js +84 -0
  71. package/dist/node_modules/node-sql-parser/lib/with.js +37 -0
  72. package/dist/node_modules/node-sql-parser/package.json +1 -0
  73. package/dist/node_modules/node-sql-parser/types.d.ts +555 -0
  74. package/dist/node_modules/node-sql-parser/umd/athena.umd.d.ts +1 -0
  75. package/dist/node_modules/node-sql-parser/umd/athena.umd.js +2 -0
  76. package/dist/node_modules/node-sql-parser/umd/bigquery.umd.d.ts +1 -0
  77. package/dist/node_modules/node-sql-parser/umd/bigquery.umd.js +2 -0
  78. package/dist/node_modules/node-sql-parser/umd/db2.umd.d.ts +1 -0
  79. package/dist/node_modules/node-sql-parser/umd/db2.umd.js +2 -0
  80. package/dist/node_modules/node-sql-parser/umd/flinksql.umd.d.ts +1 -0
  81. package/dist/node_modules/node-sql-parser/umd/flinksql.umd.js +2 -0
  82. package/dist/node_modules/node-sql-parser/umd/hive.umd.d.ts +1 -0
  83. package/dist/node_modules/node-sql-parser/umd/hive.umd.js +2 -0
  84. package/dist/node_modules/node-sql-parser/umd/index.umd.d.ts +1 -0
  85. package/dist/node_modules/node-sql-parser/umd/index.umd.js +2 -0
  86. package/dist/node_modules/node-sql-parser/umd/mariadb.umd.d.ts +1 -0
  87. package/dist/node_modules/node-sql-parser/umd/mariadb.umd.js +2 -0
  88. package/dist/node_modules/node-sql-parser/umd/mysql.umd.d.ts +1 -0
  89. package/dist/node_modules/node-sql-parser/umd/mysql.umd.js +2 -0
  90. package/dist/node_modules/node-sql-parser/umd/noql.umd.d.ts +1 -0
  91. package/dist/node_modules/node-sql-parser/umd/noql.umd.js +2 -0
  92. package/dist/node_modules/node-sql-parser/umd/postgresql.umd.d.ts +1 -0
  93. package/dist/node_modules/node-sql-parser/umd/postgresql.umd.js +2 -0
  94. package/dist/node_modules/node-sql-parser/umd/redshift.umd.d.ts +1 -0
  95. package/dist/node_modules/node-sql-parser/umd/redshift.umd.js +2 -0
  96. package/dist/node_modules/node-sql-parser/umd/snowflake.umd.d.ts +1 -0
  97. package/dist/node_modules/node-sql-parser/umd/snowflake.umd.js +2 -0
  98. package/dist/node_modules/node-sql-parser/umd/sqlite.umd.d.ts +1 -0
  99. package/dist/node_modules/node-sql-parser/umd/sqlite.umd.js +2 -0
  100. package/dist/node_modules/node-sql-parser/umd/transactsql.umd.d.ts +1 -0
  101. package/dist/node_modules/node-sql-parser/umd/transactsql.umd.js +2 -0
  102. package/dist/node_modules/node-sql-parser/umd/trino.umd.d.ts +1 -0
  103. package/dist/node_modules/node-sql-parser/umd/trino.umd.js +2 -0
  104. package/dist/server/repositories/collection-repository.d.ts +3 -0
  105. package/dist/server/repositories/collection-repository.js +7 -1
  106. package/dist/server/resourcers/sql.d.ts +28 -1
  107. package/dist/server/resourcers/sql.js +121 -4
  108. package/dist/server/resourcers/views.d.ts +14 -0
  109. package/dist/server/server.d.ts +3 -0
  110. package/package.json +6 -4
@@ -3,6 +3,9 @@ interface LoadOptions {
3
3
  filter?: any;
4
4
  skipExist?: boolean;
5
5
  }
6
+ /**
7
+ * Provides the collection repository helper for this module.
8
+ */
6
9
  export declare class CollectionRepository extends Repository {
7
10
  private app;
8
11
  setApp(app: any): void;
@@ -44,6 +44,7 @@ class CollectionRepository extends import_server.Repository {
44
44
  const graph = new graphlib.Graph();
45
45
  const nameMap = {};
46
46
  const viewCollections = [];
47
+ const db2cmCollections = [];
47
48
  const customCollections = [];
48
49
  for (const instance of instances) {
49
50
  graph.setNode(instance.get("name"));
@@ -71,9 +72,14 @@ class CollectionRepository extends import_server.Repository {
71
72
  for (const instanceName of sortedNames) {
72
73
  if (!nameMap[instanceName]) continue;
73
74
  const skipField = (() => {
75
+ var _a2;
74
76
  if (viewCollections.includes(instanceName)) {
75
77
  return true;
76
78
  }
79
+ const collectionSource = nameMap[instanceName].get("from") ?? ((_a2 = nameMap[instanceName].get("options")) == null ? void 0 : _a2.from);
80
+ if (collectionSource === "db2cm" && this.database.hasCollection(instanceName)) {
81
+ return true;
82
+ }
77
83
  const fields = nameMap[instanceName].get("fields");
78
84
  return fields.filter((field) => field["type"] === "belongsTo" || field["type"] === "belongsToMany").map((field) => field.get("name"));
79
85
  })();
@@ -88,7 +94,7 @@ class CollectionRepository extends import_server.Repository {
88
94
  (_a = this.app) == null ? void 0 : _a.setMaintainingMessage(`load ${instanceName} collection`);
89
95
  await nameMap[instanceName].load({ skipField });
90
96
  }
91
- for (const viewCollectionName of viewCollections) {
97
+ for (const viewCollectionName of /* @__PURE__ */ new Set([...viewCollections, ...db2cmCollections])) {
92
98
  process.env.DEBUG_LOAD_COLLECTION_FIELDS && this.database.logger.debug(`load collection fields`, {
93
99
  submodule: "CollectionRepository",
94
100
  method: "load",
@@ -1,4 +1,31 @@
1
- import { Context, Next } from '@tego/server';
1
+ import { Context, Next, SQLModel } from '@tego/server';
2
+ /**
3
+ * Normalizes SQL by parsing and re-serializing it, removing comments and literal formatting noise.
4
+ */
5
+ export declare function stripSqlCommentsAndLiterals(sql: string): string;
6
+ /**
7
+ * Validates that preview SQL contains exactly one read-only SELECT/CTE statement.
8
+ */
9
+ export declare function isReadOnlyPreviewSql(sql: string): boolean;
10
+ /**
11
+ * Executes SQL preview reads inside a read-only transaction where the dialect supports it.
12
+ */
13
+ export declare function runReadOnlyPreviewQuery(ctx: Context, model: typeof SQLModel): Promise<SQLModel[]>;
14
+ /**
15
+ * SQL collection resource.
16
+ *
17
+ * Tenant isolation boundary:
18
+ * - `execute` runs raw SQL with no tenant filtering. The SQL is only validated
19
+ * to be a SELECT/CTE query (non-mutating for preview purposes), but there is
20
+ * no framework-level tenant scoping.
21
+ * - `setFields` and `update` are configuration operations (not data access),
22
+ * and are gated by the default ACL deny-all for non-admin roles.
23
+ * - No `tenantId` is injected into queries. The tenant resource guard does not
24
+ * apply to this resource because SQL collections do not have a `tenancy` option.
25
+ * - If a tenant-scoped view of data is needed, users should construct their SQL
26
+ * to include the appropriate `WHERE tenantId = ...` clause manually, or use
27
+ * a general collection with proper tenancy configuration.
28
+ */
2
29
  declare const _default: {
3
30
  name: string;
4
31
  actions: {
@@ -17,10 +17,36 @@ var __copyProps = (to, from, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
  var sql_exports = {};
19
19
  __export(sql_exports, {
20
- default: () => sql_default
20
+ default: () => sql_default,
21
+ isReadOnlyPreviewSql: () => isReadOnlyPreviewSql,
22
+ runReadOnlyPreviewQuery: () => runReadOnlyPreviewQuery,
23
+ stripSqlCommentsAndLiterals: () => stripSqlCommentsAndLiterals
21
24
  });
22
25
  module.exports = __toCommonJS(sql_exports);
23
26
  var import_server = require("@tego/server");
27
+ var import_node_sql_parser = require("node-sql-parser");
28
+ function assertCollectionConfigPermission(ctx) {
29
+ var _a, _b, _c;
30
+ const roleName = (_a = ctx.state) == null ? void 0 : _a.currentRole;
31
+ if (!roleName) {
32
+ ctx.throw(403, "SQL collection configuration requires a valid role");
33
+ }
34
+ if (roleName === "root") {
35
+ return;
36
+ }
37
+ const acl = ((_b = ctx.app) == null ? void 0 : _b.acl) || ((_c = ctx.tego) == null ? void 0 : _c.acl);
38
+ if (!acl) {
39
+ ctx.throw(403, "SQL collection configuration requires the pm.database-connections.collections permission");
40
+ }
41
+ const aclRole = acl.getRole(roleName);
42
+ if (!aclRole) {
43
+ ctx.throw(403, "SQL collection configuration requires the pm.database-connections.collections permission");
44
+ }
45
+ const { allowed } = aclRole.effectiveSnippets();
46
+ if (!allowed.includes("pm.database-connections.collections")) {
47
+ ctx.throw(403, "SQL collection configuration requires the pm.database-connections.collections permission");
48
+ }
49
+ }
24
50
  const updateCollection = async (ctx, transaction) => {
25
51
  const { filterByTk, values } = ctx.action.params;
26
52
  const repo = ctx.db.getRepository("collections");
@@ -46,6 +72,89 @@ const updateCollection = async (ctx, transaction) => {
46
72
  });
47
73
  return { collection, upRes };
48
74
  };
75
+ const sqlParser = new import_node_sql_parser.Parser();
76
+ const SQL_PARSE_DIALECTS = ["postgresql", "mysql", "sqlite"];
77
+ function stripSqlCommentsAndLiterals(sql) {
78
+ const parsed = parseSql(sql);
79
+ if (!parsed) {
80
+ return "";
81
+ }
82
+ return sqlParser.sqlify(parsed.ast, { database: parsed.database });
83
+ }
84
+ function parseSql(sql) {
85
+ for (const database of SQL_PARSE_DIALECTS) {
86
+ try {
87
+ return {
88
+ ast: sqlParser.astify(sql, { database }),
89
+ database
90
+ };
91
+ } catch {
92
+ }
93
+ }
94
+ return null;
95
+ }
96
+ function getWithStatement(stmt) {
97
+ if (!stmt) {
98
+ return null;
99
+ }
100
+ return "ast" in stmt && stmt.ast ? stmt.ast : stmt;
101
+ }
102
+ function isReadOnlyStatement(ast) {
103
+ var _a, _b;
104
+ const statement = ast;
105
+ if (statement.type !== "select") {
106
+ return false;
107
+ }
108
+ if ((_a = statement.into) == null ? void 0 : _a.position) {
109
+ return false;
110
+ }
111
+ if ((_b = statement.with) == null ? void 0 : _b.some((item) => !getWithStatement(item.stmt) || !isReadOnlyStatement(getWithStatement(item.stmt)))) {
112
+ return false;
113
+ }
114
+ if (statement._next && !isReadOnlyStatement(statement._next)) {
115
+ return false;
116
+ }
117
+ return true;
118
+ }
119
+ function isReadOnlyPreviewSql(sql) {
120
+ const parsed = parseSql(sql);
121
+ if (!parsed) {
122
+ return false;
123
+ }
124
+ const statements = Array.isArray(parsed.ast) ? parsed.ast : [parsed.ast];
125
+ if (statements.length !== 1) {
126
+ return false;
127
+ }
128
+ return isReadOnlyStatement(statements[0]);
129
+ }
130
+ function getDialect(ctx) {
131
+ var _a, _b, _c;
132
+ return ((_b = (_a = ctx.db.sequelize).getDialect) == null ? void 0 : _b.call(_a)) || ((_c = ctx.db.options) == null ? void 0 : _c.dialect);
133
+ }
134
+ async function applyReadOnlyTransactionGuard(ctx, transaction) {
135
+ const dialect = getDialect(ctx);
136
+ if (dialect === "postgres") {
137
+ await ctx.db.sequelize.query("SET TRANSACTION READ ONLY", { transaction });
138
+ }
139
+ }
140
+ function getPreviewQueryOptions(transaction) {
141
+ return {
142
+ // The result is for preview only, add limit clause to avoid too many results.
143
+ attributes: [(0, import_server.literal)("*")],
144
+ limit: 5,
145
+ raw: true,
146
+ ...transaction ? { transaction } : {}
147
+ };
148
+ }
149
+ async function runReadOnlyPreviewQuery(ctx, model) {
150
+ if (getDialect(ctx) === "sqlite") {
151
+ return await model.findAll(getPreviewQueryOptions());
152
+ }
153
+ return ctx.db.sequelize.transaction({ readOnly: true }, async (transaction) => {
154
+ await applyReadOnlyTransactionGuard(ctx, transaction);
155
+ return await model.findAll(getPreviewQueryOptions(transaction));
156
+ });
157
+ }
49
158
  var sql_default = {
50
159
  name: "sqlCollection",
51
160
  actions: {
@@ -53,16 +162,16 @@ var sql_default = {
53
162
  let {
54
163
  values: { sql }
55
164
  } = ctx.action.params;
56
- sql = sql.trim().split(";").shift();
165
+ sql = sql.trim();
57
166
  if (!sql) {
58
167
  ctx.throw(400, ctx.t("SQL is empty"));
59
168
  }
60
- if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
169
+ if (!isReadOnlyPreviewSql(sql)) {
61
170
  ctx.throw(400, ctx.t("Only select query allowed"));
62
171
  }
63
172
  const tmpCollection = new import_server.SqlCollection({ name: "tmp", sql }, { database: ctx.db });
64
173
  const model = tmpCollection.model;
65
- const data = await model.findAll({ attributes: ["*"], limit: 5, raw: true });
174
+ const data = await runReadOnlyPreviewQuery(ctx, model);
66
175
  let fields = {};
67
176
  try {
68
177
  fields = model.inferFields();
@@ -75,6 +184,7 @@ var sql_default = {
75
184
  await next();
76
185
  },
77
186
  setFields: async (ctx, next) => {
187
+ assertCollectionConfigPermission(ctx);
78
188
  const transaction = await ctx.tego.db.sequelize.transaction();
79
189
  try {
80
190
  const {
@@ -91,6 +201,7 @@ var sql_default = {
91
201
  await next();
92
202
  },
93
203
  update: async (ctx, next) => {
204
+ assertCollectionConfigPermission(ctx);
94
205
  const transaction = await ctx.tego.db.sequelize.transaction();
95
206
  try {
96
207
  const { upRes } = await updateCollection(ctx, transaction);
@@ -106,3 +217,9 @@ var sql_default = {
106
217
  }
107
218
  }
108
219
  };
220
+ // Annotate the CommonJS export names for ESM import in node:
221
+ 0 && (module.exports = {
222
+ isReadOnlyPreviewSql,
223
+ runReadOnlyPreviewQuery,
224
+ stripSqlCommentsAndLiterals
225
+ });
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Database view resource.
3
+ *
4
+ * Tenant isolation boundary:
5
+ * - `query` executes raw `SELECT * FROM <view> LIMIT/OFFSET` with no tenant filtering.
6
+ * The database view itself is responsible for any row-level security.
7
+ * - `get` and `list` are metadata operations (field inference, view listing),
8
+ * not data access, and are gated by the default ACL deny-all for non-admin roles.
9
+ * - No `tenantId` is injected. The tenant resource guard does not apply because
10
+ * view collections do not have a `tenancy` option.
11
+ * - If tenant isolation is required at the view level, it must be implemented
12
+ * via database-level row security policies (e.g. PostgreSQL RLS) on the
13
+ * underlying view.
14
+ */
1
15
  declare const _default: {
2
16
  name: string;
3
17
  actions: {
@@ -1,4 +1,7 @@
1
1
  import { Filter, Plugin } from '@tego/server';
2
+ /**
3
+ * Registers the collection manager plugin plugin integration.
4
+ */
2
5
  export declare class CollectionManagerPlugin extends Plugin {
3
6
  schema: string;
4
7
  private loadFilter;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tachybase/module-collection",
3
3
  "displayName": "Data source: Main",
4
- "version": "1.6.24",
4
+ "version": "1.6.26",
5
5
  "description": "TachyBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.",
6
6
  "keywords": [
7
7
  "Data sources"
@@ -17,7 +17,9 @@
17
17
  },
18
18
  "license": "Apache-2.0",
19
19
  "main": "./dist/server/index.js",
20
- "dependencies": {},
20
+ "dependencies": {
21
+ "node-sql-parser": "5.3.10"
22
+ },
21
23
  "devDependencies": {
22
24
  "@hapi/topo": "^6.0.2",
23
25
  "@tachybase/test": "1.6.16",
@@ -28,8 +30,8 @@
28
30
  "lodash": "4.17.21",
29
31
  "sequelize": "6.37.5",
30
32
  "toposort": "^2.0.2",
31
- "@tachybase/client": "1.6.24",
32
- "@tachybase/module-error-handler": "1.6.24"
33
+ "@tachybase/client": "1.6.26",
34
+ "@tachybase/module-error-handler": "1.6.26"
33
35
  },
34
36
  "description.zh-CN": "TachyBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。",
35
37
  "displayName.zh-CN": "数据源:主数据库",