@tachybase/plugin-action-export 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.
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
2
  "react": "18.3.1",
3
- "@tachybase/client": "1.6.24",
3
+ "@tachybase/client": "1.6.26",
4
4
  "@tachybase/schema": "1.6.16",
5
5
  "@tego/client": "1.6.16",
6
6
  "react-i18next": "16.2.1",
@@ -1 +1 @@
1
- {"name":"node-xlsx","author":"Olivier Louvignes <olivier@mg-crea.com>","version":"0.24.0","description":"NodeJS Excel files parser & builder","type":"module","main":"./dist/index.cjs","exports":{".":{"require":"./dist/index.cjs","import":"./dist/index.js","types":"./dist/index.d.ts"}},"bin":{"node-xlsx":"./dist/bin/cli.js"},"files":["dist"],"scripts":{"start":"npm run spec -- --watch","build":"tsup --entry src/index.ts --format cjs,esm --sourcemap --dts --clean --entry src/bin/cli.ts","lint":"eslint src/ test/","prettycheck":"prettier --check src/ test/","prettify":"prettier --write src/ test/","typecheck":"tsc --noEmit","spec":"DEBUG=node-xlsx* vitest --run","watch":"DEBUG=node-xlsx* vitest --watch","test":"npm run lint && npm run prettycheck && npm run typecheck && npm run spec","prepublishOnly":"npm run build"},"repository":"github:mgcrea/node-xlsx","license":"Apache-2.0","dependencies":{"xlsx":"https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"},"devDependencies":{"@mgcrea/eslint-config-node":"^0.10.0","@tsconfig/node-lts":"^20.1.1","@types/node":"^20.12.7","eslint":"^8.57.0","prettier":"^3.2.5","prettier-plugin-organize-imports":"^3.2.4","tsup":"^8.0.2","typescript":"^5.4.5","vite-tsconfig-paths":"^4.3.2","vitest":"^1.5.0"},"engines":{"node":">=10.0.0"},"keywords":["excel","parser","builder","xlsx","xls"],"_lastModified":"2026-07-09T11:40:13.733Z"}
1
+ {"name":"node-xlsx","author":"Olivier Louvignes <olivier@mg-crea.com>","version":"0.24.0","description":"NodeJS Excel files parser & builder","type":"module","main":"./dist/index.cjs","exports":{".":{"require":"./dist/index.cjs","import":"./dist/index.js","types":"./dist/index.d.ts"}},"bin":{"node-xlsx":"./dist/bin/cli.js"},"files":["dist"],"scripts":{"start":"npm run spec -- --watch","build":"tsup --entry src/index.ts --format cjs,esm --sourcemap --dts --clean --entry src/bin/cli.ts","lint":"eslint src/ test/","prettycheck":"prettier --check src/ test/","prettify":"prettier --write src/ test/","typecheck":"tsc --noEmit","spec":"DEBUG=node-xlsx* vitest --run","watch":"DEBUG=node-xlsx* vitest --watch","test":"npm run lint && npm run prettycheck && npm run typecheck && npm run spec","prepublishOnly":"npm run build"},"repository":"github:mgcrea/node-xlsx","license":"Apache-2.0","dependencies":{"xlsx":"https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"},"devDependencies":{"@mgcrea/eslint-config-node":"^0.10.0","@tsconfig/node-lts":"^20.1.1","@types/node":"^20.12.7","eslint":"^8.57.0","prettier":"^3.2.5","prettier-plugin-organize-imports":"^3.2.4","tsup":"^8.0.2","typescript":"^5.4.5","vite-tsconfig-paths":"^4.3.2","vitest":"^1.5.0"},"engines":{"node":">=10.0.0"},"keywords":["excel","parser","builder","xlsx","xls"],"_lastModified":"2026-08-11T06:21:06.688Z"}
@@ -1,2 +1,5 @@
1
1
  import { Context, Next } from '@tego/server';
2
+ /**
3
+ * Handles the export xlsx resource action.
4
+ */
2
5
  export declare function exportXlsx(ctx: Context, next: Next): Promise<any>;
@@ -36,28 +36,61 @@ var import__ = __toESM(require(".."));
36
36
  var import_constants = require("../constants");
37
37
  var import_renders = __toESM(require("../renders"));
38
38
  var import_utils = require("../utils");
39
+ function uniqueTenantIds(ids) {
40
+ return Array.from(new Set(ids || []));
41
+ }
42
+ function getExportTenantContext(ctx) {
43
+ const currentTenantId = (0, import_utils.getExportTenantId)(ctx);
44
+ if (currentTenantId === null || currentTenantId === void 0) {
45
+ return;
46
+ }
47
+ return {
48
+ currentTenant: ctx.state.currentTenant || { id: currentTenantId },
49
+ currentTenantId,
50
+ currentTenantDescendantIds: uniqueTenantIds(ctx.state.currentTenantDescendantIds),
51
+ currentTenancyMode: ctx.state.currentTenancyMode,
52
+ currentLegacyDataTenantIds: uniqueTenantIds(ctx.state.currentLegacyDataTenantIds)
53
+ };
54
+ }
39
55
  async function exportXlsx(ctx, next) {
40
- var _a, _b, _c;
41
- const { title, filter, sort, fields, except } = ctx.action.params;
56
+ var _a, _b, _c, _d, _e;
57
+ const { filter, sort, fields, except } = ctx.action.params;
58
+ const title = ctx.action.params.title ?? ((_a = ctx.action.params.values) == null ? void 0 : _a.title);
42
59
  const { resourceName, resourceOf } = ctx.action;
43
- let columns = ((_a = ctx.action.params.values) == null ? void 0 : _a.columns) || ((_b = ctx.action.params) == null ? void 0 : _b.columns);
44
- if (typeof columns === "string") {
45
- columns = JSON.parse(columns);
60
+ const currentTenantId = (0, import_utils.getExportTenantId)(ctx);
61
+ const tenantContext = getExportTenantContext(ctx);
62
+ let columns;
63
+ try {
64
+ columns = (0, import_utils.normalizeExportColumns)(((_b = ctx.action.params.values) == null ? void 0 : _b.columns) ?? ((_c = ctx.action.params) == null ? void 0 : _c.columns));
65
+ } catch (error) {
66
+ if (error instanceof import_utils.ExportColumnsError) {
67
+ ctx.throw(400, error.message);
68
+ return;
69
+ }
70
+ throw error;
46
71
  }
47
72
  const repository = ctx.db.getRepository(resourceName, resourceOf);
48
73
  const collection = repository.collection;
49
- columns = columns == null ? void 0 : columns.filter((col) => {
50
- var _a2;
51
- return collection.hasField(col.dataIndex[0]) && ((_a2 = col == null ? void 0 : col.dataIndex) == null ? void 0 : _a2.length) > 0;
52
- });
74
+ columns = (0, import_utils.filterExportColumnsByCollection)(columns, collection);
53
75
  const appends = (0, import_utils.columns2Appends)(columns, ctx);
54
76
  const count = await repository.count({
55
77
  filter,
56
78
  context: ctx
57
79
  });
80
+ if (count >= import_constants.BULK_EXPORT_THRESHOLD && ctx.state.currentTenancyMode) {
81
+ (0, import_utils.emitSecurityViolation)(ctx, {
82
+ type: "tenant_bulk_export_alert",
83
+ userId: (_d = ctx.state.currentUser) == null ? void 0 : _d.id,
84
+ actorUserId: ctx.state.actorUserId,
85
+ tenantId: currentTenantId,
86
+ collectionName: resourceName,
87
+ action: "export",
88
+ details: { rowCount: count, threshold: import_constants.BULK_EXPORT_THRESHOLD }
89
+ });
90
+ }
58
91
  if (count > import_constants.EXPORT_LENGTH_MAX) {
59
92
  const app = ctx.tego;
60
- if (!((_c = app.worker) == null ? void 0 : _c.available)) {
93
+ if (!((_e = app.worker) == null ? void 0 : _e.available)) {
61
94
  ctx.throw(400, `Too many records to export: ${count} > ${import_constants.EXPORT_LENGTH_MAX}`);
62
95
  }
63
96
  let fileWithPath;
@@ -78,6 +111,8 @@ async function exportXlsx(ctx, next) {
78
111
  resourceName,
79
112
  resourceOf,
80
113
  appends,
114
+ currentTenantId,
115
+ tenantContext,
81
116
  timezone: ctx.get("X-Timezone")
82
117
  }
83
118
  });
@@ -127,7 +162,7 @@ async function exportXlsx(ctx, next) {
127
162
  ctx.set({
128
163
  "Content-Type": "application/octet-stream",
129
164
  // to avoid "invalid character" error in header (RFC)
130
- "Content-Disposition": `attachment; filename=${encodeURI(title)}.xlsx`
165
+ "Content-Disposition": `attachment; filename=${encodeURI((0, import_utils.buildExportDownloadName)(title, currentTenantId))}.xlsx`
131
166
  });
132
167
  await next();
133
168
  }
@@ -1,2 +1,3 @@
1
1
  export declare const EXPORT_LENGTH_MAX: number;
2
2
  export declare const EXPORT_WORKER_PAGESIZE: number;
3
+ export declare const BULK_EXPORT_THRESHOLD: number;
@@ -17,14 +17,21 @@ var __copyProps = (to, from, except, desc) => {
17
17
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
18
  var constants_exports = {};
19
19
  __export(constants_exports, {
20
+ BULK_EXPORT_THRESHOLD: () => BULK_EXPORT_THRESHOLD,
20
21
  EXPORT_LENGTH_MAX: () => EXPORT_LENGTH_MAX,
21
22
  EXPORT_WORKER_PAGESIZE: () => EXPORT_WORKER_PAGESIZE
22
23
  });
23
24
  module.exports = __toCommonJS(constants_exports);
24
25
  const EXPORT_LENGTH_MAX = +process.env.EXPORT_LENGTH_MAX || 2e3;
25
26
  const EXPORT_WORKER_PAGESIZE = +process.env.EXPORT_WORKER_PAGESIZE || 1e3;
27
+ function parseBulkExportThreshold(value) {
28
+ const parsed = Number(value);
29
+ return Number.isFinite(parsed) && parsed > 0 && parsed <= 1e6 ? parsed : 1e3;
30
+ }
31
+ const BULK_EXPORT_THRESHOLD = parseBulkExportThreshold(process.env.BULK_EXPORT_THRESHOLD);
26
32
  // Annotate the CommonJS export names for ESM import in node:
27
33
  0 && (module.exports = {
34
+ BULK_EXPORT_THRESHOLD,
28
35
  EXPORT_LENGTH_MAX,
29
36
  EXPORT_WORKER_PAGESIZE
30
37
  });
@@ -1,4 +1,7 @@
1
1
  import { InstallOptions, Plugin } from '@tego/server';
2
+ /**
3
+ * Provides the export plugin helper for this module.
4
+ */
2
5
  export declare class ExportPlugin extends Plugin {
3
6
  beforeLoad(): void;
4
7
  load(): Promise<void>;
@@ -39,6 +39,115 @@ var import_node_xlsx = __toESM(require("node-xlsx"));
39
39
  var import_actions = require("./actions");
40
40
  var import_constants = require("./constants");
41
41
  var import_renders = __toESM(require("./renders"));
42
+ var import_utils = require("./utils");
43
+ function isEmptyObject(value) {
44
+ return isPlainObject(value) && Object.keys(value).length === 0;
45
+ }
46
+ function isPlainObject(value) {
47
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
48
+ return false;
49
+ }
50
+ const prototype = Object.getPrototypeOf(value);
51
+ return prototype === Object.prototype || prototype === null;
52
+ }
53
+ function cleanFilter(filter, stripTenant) {
54
+ if (!filter || typeof filter !== "object") {
55
+ return filter;
56
+ }
57
+ if (Array.isArray(filter)) {
58
+ return filter.map((item) => cleanFilter(item, stripTenant)).filter((item) => !isEmptyObject(item));
59
+ }
60
+ if (!isPlainObject(filter)) {
61
+ return filter;
62
+ }
63
+ const next = Object.fromEntries(
64
+ Object.entries(filter).filter(([key]) => !stripTenant || key !== "tenantId" && !key.startsWith("tenantId.")).map(([key, value]) => [key, cleanFilter(value, stripTenant)])
65
+ );
66
+ for (const key of ["$and", "$or"]) {
67
+ if (Array.isArray(next[key])) {
68
+ next[key] = next[key].filter((item) => !isEmptyObject(item));
69
+ if (next[key].length === 0) {
70
+ delete next[key];
71
+ }
72
+ }
73
+ }
74
+ return next;
75
+ }
76
+ function stripTenantFilter(filter) {
77
+ return cleanFilter(filter, true);
78
+ }
79
+ function canReadLegacyData(tenantId, legacyDataTenantIds) {
80
+ return (legacyDataTenantIds || []).some((item) => `${item}` === `${tenantId}`);
81
+ }
82
+ function getLegacyDataTenantIds(tenantContext, collection) {
83
+ var _a;
84
+ if (tenantContext && Object.prototype.hasOwnProperty.call(tenantContext, "currentLegacyDataTenantIds") && Array.isArray(tenantContext.currentLegacyDataTenantIds)) {
85
+ return Array.from(new Set(tenantContext.currentLegacyDataTenantIds));
86
+ }
87
+ return ((_a = collection == null ? void 0 : collection.options) == null ? void 0 : _a.legacyDataTenantIds) || [];
88
+ }
89
+ function appendTenantFilter(original, tenantFilter) {
90
+ const sanitizedOriginal = cleanFilter(original, false);
91
+ if (!sanitizedOriginal || Object.keys(sanitizedOriginal).length === 0) {
92
+ return tenantFilter;
93
+ }
94
+ return {
95
+ $and: [sanitizedOriginal, tenantFilter]
96
+ };
97
+ }
98
+ function buildTenantFilter(tenantId, includeLegacyData = false) {
99
+ if (!includeLegacyData) {
100
+ return { tenantId };
101
+ }
102
+ return {
103
+ $or: [{ tenantId }, { tenantId: null }]
104
+ };
105
+ }
106
+ function buildInheritedTenantFilter(tenantIds, includeLegacyData = false) {
107
+ const tenantFilter = { tenantId: { $in: tenantIds } };
108
+ if (!includeLegacyData) {
109
+ return tenantFilter;
110
+ }
111
+ return {
112
+ $or: [tenantFilter, { tenantId: null }]
113
+ };
114
+ }
115
+ function buildWorkerTenantState(currentTenantId, tenantContext) {
116
+ const tenantId = (0, import_utils.getExportTenantId)({
117
+ state: tenantContext,
118
+ currentTenantId
119
+ });
120
+ if (tenantId === null || tenantId === void 0) {
121
+ return;
122
+ }
123
+ const hasLegacyDataTenantIds = tenantContext && Object.prototype.hasOwnProperty.call(tenantContext, "currentLegacyDataTenantIds");
124
+ return {
125
+ ...tenantContext,
126
+ currentTenant: (tenantContext == null ? void 0 : tenantContext.currentTenant) || { id: tenantId },
127
+ currentTenantId: tenantId,
128
+ currentTenantDescendantIds: Array.from(new Set((tenantContext == null ? void 0 : tenantContext.currentTenantDescendantIds) || [])),
129
+ ...hasLegacyDataTenantIds ? {
130
+ currentLegacyDataTenantIds: Array.from(new Set((tenantContext == null ? void 0 : tenantContext.currentLegacyDataTenantIds) || []))
131
+ } : {}
132
+ };
133
+ }
134
+ function applyTenantScopeToWorkerFindOptions(options, collection, tenantContext) {
135
+ var _a, _b;
136
+ const tenancyMode = (_a = collection == null ? void 0 : collection.options) == null ? void 0 : _a.tenancy;
137
+ if (tenancyMode !== "tenantScoped" && tenancyMode !== "tenantInherited") {
138
+ return options;
139
+ }
140
+ const tenantId = ((_b = tenantContext == null ? void 0 : tenantContext.currentTenant) == null ? void 0 : _b.id) ?? (tenantContext == null ? void 0 : tenantContext.currentTenantId);
141
+ if (tenantId === null || tenantId === void 0) {
142
+ throw new Error("Tenant context is required for tenant-scoped export worker");
143
+ }
144
+ const includeLegacyData = canReadLegacyData(tenantId, getLegacyDataTenantIds(tenantContext, collection));
145
+ const tenantFilter = tenancyMode === "tenantInherited" ? buildInheritedTenantFilter([tenantId, ...tenantContext.currentTenantDescendantIds || []], includeLegacyData) : buildTenantFilter(tenantId, includeLegacyData);
146
+ return {
147
+ ...options,
148
+ filter: appendTenantFilter(options.filter, tenantFilter)
149
+ };
150
+ }
42
151
  const _ExportPlugin = class _ExportPlugin extends import_server.Plugin {
43
152
  beforeLoad() {
44
153
  }
@@ -55,30 +164,49 @@ const _ExportPlugin = class _ExportPlugin extends import_server.Plugin {
55
164
  return import_node_path.default.resolve(process.env.TEGO_RUNTIME_HOME, _ExportPlugin.defaultSavePath);
56
165
  }
57
166
  async workerExportXlsx(params) {
58
- const { title, filter, sort, fields, except, appends, resourceName, resourceOf, timezone } = params;
59
- let columns = (params == null ? void 0 : params.columns) || (params == null ? void 0 : params.columns);
60
- if (typeof columns === "string") {
61
- columns = JSON.parse(columns);
62
- }
167
+ const {
168
+ title,
169
+ filter,
170
+ sort,
171
+ fields,
172
+ except,
173
+ appends,
174
+ resourceName,
175
+ resourceOf,
176
+ timezone,
177
+ currentTenantId,
178
+ tenantContext: rawTenantContext
179
+ } = params;
180
+ let columns = (0, import_utils.normalizeExportColumns)(params == null ? void 0 : params.columns);
63
181
  const repository = this.db.getRepository(resourceName, resourceOf);
64
182
  const collection = repository.collection;
65
- columns = columns == null ? void 0 : columns.filter((col) => {
66
- var _a;
67
- return collection.hasField(col.dataIndex[0]) && ((_a = col == null ? void 0 : col.dataIndex) == null ? void 0 : _a.length) > 0;
68
- });
183
+ const tenantState = buildWorkerTenantState(currentTenantId, rawTenantContext);
184
+ const tenantId = (0, import_utils.getExportTenantId)({ state: tenantState });
185
+ const tenantContext = tenantState ? {
186
+ state: tenantState
187
+ } : void 0;
188
+ const findOptions = applyTenantScopeToWorkerFindOptions(
189
+ {
190
+ filter,
191
+ fields,
192
+ appends,
193
+ except,
194
+ sort
195
+ },
196
+ collection,
197
+ tenantState
198
+ );
199
+ columns = (0, import_utils.filterExportColumnsByCollection)(columns, collection);
69
200
  let page = 1;
70
201
  const pageSize = import_constants.EXPORT_WORKER_PAGESIZE;
71
202
  let data = [];
72
203
  let hasMore = true;
73
204
  while (hasMore) {
74
205
  const pageData = await repository.find({
75
- filter,
76
- fields,
77
- appends,
78
- except,
79
- sort,
206
+ ...findOptions,
80
207
  offset: (page - 1) * pageSize,
81
- limit: pageSize
208
+ limit: pageSize,
209
+ context: tenantContext
82
210
  });
83
211
  data = data.concat(pageData);
84
212
  hasMore = pageData.length === pageSize;
@@ -104,14 +232,14 @@ const _ExportPlugin = class _ExportPlugin extends import_server.Plugin {
104
232
  }
105
233
  }
106
234
  ]);
107
- const savePath = this.xlsxStorageDir();
235
+ const savePath = (0, import_utils.buildWorkerExportSavePath)(this.xlsxStorageDir(), tenantId);
108
236
  if (!(0, import_node_fs.existsSync)(savePath)) {
109
237
  (0, import_node_fs.mkdirSync)(savePath, { recursive: true });
110
238
  }
111
- const fileName = `${resourceName}_${(0, import_dayjs.default)().format("YYYYMMDDHHmm")}.xlsx`;
239
+ const fileName = (0, import_utils.buildWorkerExportFileName)(resourceName, title, tenantId);
112
240
  const rawFile = `${savePath}/${fileName}`;
113
241
  (0, import_node_fs.writeFileSync)(rawFile, Buffer.from(stream));
114
- return `${_ExportPlugin.defaultSavePath}/${fileName}`;
242
+ return (0, import_utils.buildWorkerExportRelativePath)(fileName, tenantId, _ExportPlugin.defaultSavePath);
115
243
  }
116
244
  };
117
245
  _ExportPlugin.defaultSavePath = "storage/uploads";
@@ -109,6 +109,12 @@ async function renderRows({ columns, fields, data, utcOffset }, db) {
109
109
  }, Promise.resolve([]));
110
110
  }
111
111
  async function renders_default({ columns, fields, data, utcOffset }, db) {
112
+ if (fields.length === 0) {
113
+ return {
114
+ rows: [],
115
+ ranges: []
116
+ };
117
+ }
112
118
  const headers = [];
113
119
  renderHeader({ columns, fields, headers });
114
120
  const ranges = [];
@@ -1 +1,44 @@
1
1
  export * from './columns2Appends';
2
+ /**
3
+ * Error thrown when an export columns payload cannot be normalized safely.
4
+ */
5
+ export declare class ExportColumnsError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ /**
9
+ * Parses and validates the client-provided columns option for xlsx export.
10
+ */
11
+ export declare function normalizeExportColumns(columns: any): any[];
12
+ /**
13
+ * Keeps only export columns whose root field exists on the target collection.
14
+ */
15
+ export declare function filterExportColumnsByCollection(columns: any[], collection: any): any[];
16
+ type TenantId = string | number | undefined | null;
17
+ /**
18
+ * Sanitizes a value for use as one segment of an export filename or path.
19
+ */
20
+ export declare function sanitizeExportSegment(value: string | number): string;
21
+ /**
22
+ * Resolves the effective tenant ID from request state or worker context.
23
+ */
24
+ export declare function getExportTenantId(source: any): any;
25
+ /**
26
+ * Builds the user-facing export download name with an optional tenant suffix.
27
+ */
28
+ export declare function buildExportDownloadName(title: string, tenantId?: TenantId): string;
29
+ /**
30
+ * Emits a tenant security event without blocking the export request path.
31
+ */
32
+ export declare function emitSecurityViolation(ctx: any, event: Record<string, any>): void;
33
+ /**
34
+ * Builds a collision-resistant worker export filename for the target tenant.
35
+ */
36
+ export declare function buildWorkerExportFileName(resourceName: string, title: string, tenantId?: TenantId): string;
37
+ /**
38
+ * Builds the persisted relative path for an exported worker file.
39
+ */
40
+ export declare function buildWorkerExportRelativePath(fileName: string, tenantId?: TenantId, basePath?: string): string;
41
+ /**
42
+ * Builds the local directory used by worker exports for a tenant.
43
+ */
44
+ export declare function buildWorkerExportSavePath(rootPath: string, tenantId?: TenantId): string;
@@ -1,7 +1,13 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
4
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
5
11
  var __copyProps = (to, from, except, desc) => {
6
12
  if (from && typeof from === "object" || typeof from === "function") {
7
13
  for (let key of __getOwnPropNames(from))
@@ -11,11 +17,158 @@ var __copyProps = (to, from, except, desc) => {
11
17
  return to;
12
18
  };
13
19
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
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
+ ));
14
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
29
  var utils_exports = {};
30
+ __export(utils_exports, {
31
+ ExportColumnsError: () => ExportColumnsError,
32
+ buildExportDownloadName: () => buildExportDownloadName,
33
+ buildWorkerExportFileName: () => buildWorkerExportFileName,
34
+ buildWorkerExportRelativePath: () => buildWorkerExportRelativePath,
35
+ buildWorkerExportSavePath: () => buildWorkerExportSavePath,
36
+ emitSecurityViolation: () => emitSecurityViolation,
37
+ filterExportColumnsByCollection: () => filterExportColumnsByCollection,
38
+ getExportTenantId: () => getExportTenantId,
39
+ normalizeExportColumns: () => normalizeExportColumns,
40
+ sanitizeExportSegment: () => sanitizeExportSegment
41
+ });
16
42
  module.exports = __toCommonJS(utils_exports);
43
+ var import_node_crypto = require("node:crypto");
44
+ var import_node_path = __toESM(require("node:path"));
45
+ var import_dayjs = __toESM(require("dayjs"));
17
46
  __reExport(utils_exports, require("./columns2Appends"), module.exports);
47
+ class ExportColumnsError extends Error {
48
+ constructor(message) {
49
+ super(`Invalid export columns: ${message}`);
50
+ this.name = "ExportColumnsError";
51
+ }
52
+ }
53
+ function parseColumns(columns) {
54
+ if (columns == null || columns === "") {
55
+ return [];
56
+ }
57
+ if (typeof columns !== "string") {
58
+ return columns;
59
+ }
60
+ try {
61
+ return JSON.parse(columns);
62
+ } catch {
63
+ throw new ExportColumnsError("columns must be valid JSON");
64
+ }
65
+ }
66
+ function normalizeColumn(column, index) {
67
+ if (typeof column === "string") {
68
+ if (!column.trim()) {
69
+ throw new ExportColumnsError(`columns[${index}] must be a non-empty string`);
70
+ }
71
+ return { dataIndex: [column] };
72
+ }
73
+ if (!column || typeof column !== "object" || Array.isArray(column)) {
74
+ throw new ExportColumnsError(`columns[${index}] must be a string or object with dataIndex`);
75
+ }
76
+ if (!Array.isArray(column.dataIndex) || column.dataIndex.length === 0) {
77
+ throw new ExportColumnsError(`columns[${index}].dataIndex must be a non-empty array`);
78
+ }
79
+ for (const [dataIndexPosition, segment] of column.dataIndex.entries()) {
80
+ if (typeof segment !== "string" || !segment.trim()) {
81
+ throw new ExportColumnsError(`columns[${index}].dataIndex[${dataIndexPosition}] must be a non-empty string`);
82
+ }
83
+ }
84
+ return column;
85
+ }
86
+ function normalizeExportColumns(columns) {
87
+ const parsedColumns = parseColumns(columns);
88
+ if (!Array.isArray(parsedColumns)) {
89
+ throw new ExportColumnsError("columns must be an array");
90
+ }
91
+ return parsedColumns.map(normalizeColumn);
92
+ }
93
+ function filterExportColumnsByCollection(columns, collection) {
94
+ return columns.filter((col) => {
95
+ var _a;
96
+ return ((_a = col == null ? void 0 : col.dataIndex) == null ? void 0 : _a.length) > 0 && collection.hasField(col.dataIndex[0]);
97
+ });
98
+ }
99
+ function hasTenantId(tenantId) {
100
+ return tenantId !== null && tenantId !== void 0 && tenantId !== "";
101
+ }
102
+ function sanitizeExportSegment(value) {
103
+ const normalized = String(value ?? "").trim().replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
104
+ if (normalized === "." || normalized === "..") {
105
+ return "export";
106
+ }
107
+ return normalized || "export";
108
+ }
109
+ function getExportTenantId(source) {
110
+ var _a, _b, _c;
111
+ return ((_b = (_a = source == null ? void 0 : source.state) == null ? void 0 : _a.currentTenant) == null ? void 0 : _b.id) ?? ((_c = source == null ? void 0 : source.state) == null ? void 0 : _c.currentTenantId) ?? (source == null ? void 0 : source.currentTenantId);
112
+ }
113
+ function buildExportDownloadName(title, tenantId) {
114
+ const base = String(title || "export").trim().replace(/[\\/:*?"<>|]/g, "_") || "export";
115
+ return hasTenantId(tenantId) ? `${base}_${sanitizeExportSegment(tenantId)}` : base;
116
+ }
117
+ function resolveApplicationEmitter(ctx) {
118
+ var _a, _b, _c, _d, _e, _f;
119
+ const backRef = (_a = ctx.app) == null ? void 0 : _a.__application;
120
+ if (backRef && typeof backRef.emit === "function") {
121
+ return backRef;
122
+ }
123
+ const logger = ((_b = ctx.tego) == null ? void 0 : _b.logger) || ((_c = ctx.app) == null ? void 0 : _c.logger);
124
+ (_f = logger == null ? void 0 : logger.warn) == null ? void 0 : _f.call(
125
+ logger,
126
+ "Application emitter back-reference is missing; tenant security events may not reach audit listeners",
127
+ {
128
+ resourceName: (_d = ctx.action) == null ? void 0 : _d.resourceName,
129
+ actionName: (_e = ctx.action) == null ? void 0 : _e.actionName
130
+ }
131
+ );
132
+ return ctx.app;
133
+ }
134
+ function emitSecurityViolation(ctx, event) {
135
+ var _a, _b, _c, _d, _e;
136
+ try {
137
+ const emitter = resolveApplicationEmitter(ctx);
138
+ emitter.emit("tenant.securityViolation", event);
139
+ } catch (error) {
140
+ const logger = ((_a = ctx.tego) == null ? void 0 : _a.logger) || ((_b = ctx.app) == null ? void 0 : _b.logger);
141
+ (_e = logger == null ? void 0 : logger.warn) == null ? void 0 : _e.call(logger, "Failed to emit tenant security event; export flow will continue", {
142
+ resourceName: (_c = ctx.action) == null ? void 0 : _c.resourceName,
143
+ actionName: (_d = ctx.action) == null ? void 0 : _d.actionName,
144
+ eventType: event == null ? void 0 : event.type,
145
+ error: error instanceof Error ? error.message : String(error)
146
+ });
147
+ }
148
+ }
149
+ function buildWorkerExportFileName(resourceName, title, tenantId) {
150
+ const base = sanitizeExportSegment(title || resourceName || "export");
151
+ const tenantSuffix = hasTenantId(tenantId) ? `_${sanitizeExportSegment(tenantId)}` : "";
152
+ const uniqueSuffix = (0, import_node_crypto.randomUUID)().slice(0, 8);
153
+ return `${base}${tenantSuffix}_${(0, import_dayjs.default)().format("YYYYMMDDHHmm")}_${uniqueSuffix}.xlsx`;
154
+ }
155
+ function buildWorkerExportRelativePath(fileName, tenantId, basePath = "storage/uploads") {
156
+ return hasTenantId(tenantId) ? import_node_path.default.posix.join(basePath, "tenants", sanitizeExportSegment(tenantId), fileName) : import_node_path.default.posix.join(basePath, fileName);
157
+ }
158
+ function buildWorkerExportSavePath(rootPath, tenantId) {
159
+ return hasTenantId(tenantId) ? import_node_path.default.join(rootPath, "tenants", sanitizeExportSegment(tenantId)) : rootPath;
160
+ }
18
161
  // Annotate the CommonJS export names for ESM import in node:
19
162
  0 && (module.exports = {
163
+ ExportColumnsError,
164
+ buildExportDownloadName,
165
+ buildWorkerExportFileName,
166
+ buildWorkerExportRelativePath,
167
+ buildWorkerExportSavePath,
168
+ emitSecurityViolation,
169
+ filterExportColumnsByCollection,
170
+ getExportTenantId,
171
+ normalizeExportColumns,
172
+ sanitizeExportSegment,
20
173
  ...require("./columns2Appends")
21
174
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tachybase/plugin-action-export",
3
3
  "displayName": "Action: Export records",
4
- "version": "1.6.24",
4
+ "version": "1.6.26",
5
5
  "description": "Export filtered records to excel, you can configure which fields to export.",
6
6
  "keywords": [
7
7
  "Actions"
@@ -30,7 +30,7 @@
30
30
  "node-xlsx": "^0.24.0",
31
31
  "react": "18.3.1",
32
32
  "react-i18next": "16.2.1",
33
- "@tachybase/client": "1.6.24"
33
+ "@tachybase/client": "1.6.26"
34
34
  },
35
35
  "description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。",
36
36
  "displayName.zh-CN": "操作:导出记录",