@xeplr/actions 2.0.0 → 2.1.0

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/README.md CHANGED
@@ -302,7 +302,7 @@ Which inputs are checked: `file-list` `folder`; `file-read` / `file-delete` `pat
302
302
 
303
303
  **`email-send` attachments** are `{ filename, path }` (inside the folder) or `{ filename, content }`. `href` and `raw` are refused, and so is a body that is not text: the mail library reads `path`, `href` and `html: { path }` from disk or from the network, so an unchecked one would mail the server's own settings or fetch an internal address.
304
304
 
305
- `dataDir` is exported (`resolve(name)`, `tenantFolder()`, `scratch()`, `nameOf(path)`) for a host's own code that keeps files per tenant.
305
+ `dataDir` is exported (`resolve(name)`, `tenantFolder()`, `scratch()`, `nameOf(path)`) for a host's own code that keeps files per tenant. It is `@xeplr/db`'s (peer ^1.1.0), the one copy every package uses.
306
306
 
307
307
  **From 1.x:** `XEPLR_FILE_ROOTS` and `XEPLR_ACTIONS_TMP_DIR` are gone. Set `XEPLR_DATA_DIR`, call `registerApplication()`, and run actions inside a tenant context (jobs and the workflow engine already do). A saved path outside the tenant's folder is refused until its files are moved in.
308
308
 
package/lib/dataDir.js CHANGED
@@ -1,135 +1,6 @@
1
- // WHERE A STEP'S FILES ARE — one data root, one folder per tenant.
2
- //
3
- // XEPLR_DATA_DIR the root of all data, absolute, required (requiredEnv)
4
- //
5
- // Every file a step reads or writes is inside the folder of the tenant the
6
- // work runs as, and nowhere else:
7
- //
8
- // <XEPLR_DATA_DIR>/<applicationId>/<mtId1>/<mtId2>/…
9
- //
10
- // The application comes from @xeplr/db's registerApplication (a company id is
11
- // minted per app, so company 42 of BI and company 42 of ERP are two tenants);
12
- // the tenant from the context every query already runs in — a request after
13
- // the membership gate, a job or a workflow run (runWithMt). Never from a
14
- // step's input: a step names `bank/inbox/sept.csv`, and cannot name anything
15
- // outside its own folder.
16
- //
17
- // a name bank/inbox/sept.csv → inside the folder
18
- // a full path accepted only when it is inside the folder already (what
19
- // an earlier step returned: file-list's `path`)
20
- // '*' at a level `_all` — a job for every company has a folder of its own,
21
- // and no company's step can reach it
22
- // no tenant refused, as a query with no tenant finds nothing; never
23
- // the root
24
- //
25
- // `..` and symlinks are resolved before the check, so neither steps out.
26
- // NOT DEFAULTED: "Where data lives is never defaulted." A missing or relative
27
- // XEPLR_DATA_DIR is refused by name, and so is one that does not exist — a
28
- // wrong root that boots reads as empty data, not as an error.
29
-
30
- var fs = require('fs');
31
- var path = require('path');
32
-
33
- var VAR = 'XEPLR_DATA_DIR';
34
- var requiredEnv = [VAR];
35
- var ALL = '_all';
36
- var SCRATCH = 'tmp';
37
-
38
- function refused(message, code) {
39
- var err = new Error(message);
40
- err.code = code || 'PATH_NOT_ALLOWED';
41
- err.status = 400;
42
- return err;
43
- }
44
-
45
- function db() { return require('@xeplr/db'); }
46
-
47
- /** The data root, absolute and real. */
48
- function root() {
49
- var dir = process.env[VAR];
50
- if (!dir || !String(dir).trim()) {
51
- throw refused(VAR + ' is not set: it is the folder all data lives under (absolute), e.g. /srv/xeplr-data.', 'DATA_DIR_UNSET');
52
- }
53
- if (!path.isAbsolute(dir)) throw refused(VAR + ' must be an absolute folder, and "' + dir + '" is not one.', 'DATA_DIR_UNSET');
54
- if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
55
- throw refused(VAR + ' is "' + dir + '", which is not a folder that exists. Create it, or point ' + VAR + ' at the one that holds the data.', 'DATA_DIR_UNSET');
56
- }
57
- return fs.realpathSync(dir);
58
- }
59
-
60
- // An id becomes a folder name as it is. One that could not be one — or could
61
- // climb out (`..`) — is refused rather than cleaned up into someone else's.
62
- function segment(value, what) {
63
- if (value === '*') return ALL;
64
- var s = value === null || value === undefined ? '' : String(value);
65
- if (!/^[A-Za-z0-9_.-]{1,100}$/.test(s) || s === '.' || s === '..') {
66
- throw refused('The ' + what + ' "' + s + '" cannot name a folder.', 'NO_TENANT');
67
- }
68
- return s;
69
- }
70
-
71
- /**
72
- * The folder the current work may use: <root>/<app>/<mtId1>/…, made if it is
73
- * not there yet. An app with no tenancy registered is one tenant: <root>/<app>.
74
- */
75
- function tenantFolder() {
76
- var x = db();
77
- var app = x.getApplicationId();
78
- if (!app) throw refused('No application is registered (registerApplication), so there is no folder for its files.', 'NO_TENANT');
79
- var parts = [root(), segment(app, 'application')];
80
- var levels = x.getMtConfig().levels || 0;
81
- var ctx = x.getMtContext() || {};
82
- for (var i = 1; i <= levels; i++) {
83
- var v = ctx['mtId' + i];
84
- if (v === undefined || v === null || v === '') {
85
- throw refused('This work runs as no tenant, so it has no folder: files are only ever inside the folder of the company it runs for.', 'NO_TENANT');
86
- }
87
- parts.push(segment(v, 'tenant id'));
88
- }
89
- var dir = path.join.apply(path, parts);
90
- fs.mkdirSync(dir, { recursive: true });
91
- return fs.realpathSync(dir);
92
- }
93
-
94
- /** The tenant's own temporary folder: spools, downloads, exports nobody named. */
95
- function scratch() {
96
- var dir = path.join(tenantFolder(), SCRATCH);
97
- fs.mkdirSync(dir, { recursive: true });
98
- return dir;
99
- }
100
-
101
- function inside(real, folder) {
102
- return real === folder || real.indexOf(folder + path.sep) === 0;
103
- }
104
-
105
- /**
106
- * A name (or a full path already inside the folder) → the real absolute path,
107
- * inside the tenant's folder; refused otherwise. A path that does not exist
108
- * yet (a move's target, an export) is checked by its nearest existing parent,
109
- * so `../` and symlinks cannot step out.
110
- */
111
- function resolve(p, what) {
112
- if (!p || typeof p !== 'string' || !p.trim()) throw refused((what || 'A path') + ' is required.');
113
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(p)) throw refused((what || 'The path') + ' "' + p + '" is an address, not a file in this company\'s folder.');
114
- var folder = tenantFolder();
115
- var resolved = path.isAbsolute(p) ? path.resolve(p) : path.resolve(folder, p);
116
- var probe = resolved;
117
- var rest = '';
118
- while (!fs.existsSync(probe) && path.dirname(probe) !== probe) {
119
- rest = path.join(path.basename(probe), rest);
120
- probe = path.dirname(probe);
121
- }
122
- var real = path.join(fs.realpathSync(probe), rest);
123
- if (!inside(real, folder)) {
124
- throw refused((what || 'The path') + ' "' + p + '" is outside this company\'s folder. Name it inside, e.g. bank/inbox/statement.csv.');
125
- }
126
- return real;
127
- }
128
-
129
- /** The name a path has inside the tenant's folder — what a screen shows. */
130
- function nameOf(real) {
131
- var folder = tenantFolder();
132
- return inside(real, folder) ? path.relative(folder, real) : null;
133
- }
134
-
135
- module.exports = { VAR: VAR, requiredEnv: requiredEnv, ALL: ALL, root: root, tenantFolder: tenantFolder, scratch: scratch, resolve: resolve, nameOf: nameOf };
1
+ // WHERE A STEP'S FILES ARE — @xeplr/db's dataDir: one root (XEPLR_DATA_DIR),
2
+ // a folder per tenant (<root>/<app>/<mtId1>/…), the tenant from the context
3
+ // the work runs in, never from input. One copy, in @xeplr/db beside the
4
+ // tenant context, so uploads, imports and factory use the same one. See its
5
+ // README ("Files: a folder per tenant").
6
+ module.exports = require('@xeplr/db').dataDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeplr/actions",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Named-action registry with typed inputs and safe invocation — plus built-in actions for HTTP, DB, files, email, spawn",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@duckdb/node-api": "1.5.5-r.3",
43
- "@xeplr/db": "^1.0.8",
43
+ "@xeplr/db": "^1.1.0",
44
44
  "@xeplr/email": "^1.0.0",
45
45
  "@xeplr/utils": "^1.0.0",
46
46
  "csv-parse": "^7.0.2",
@@ -121,7 +121,7 @@
121
121
  },
122
122
  "devDependencies": {
123
123
  "@duckdb/node-api": "^1.5.5-r.3",
124
- "@xeplr/db": "^1.0.8",
124
+ "@xeplr/db": "^1.1.0",
125
125
  "@xeplr/utils": "^1.0.0",
126
126
  "csv-parse": "^7.0.2",
127
127
  "dejavu-fonts-ttf": "^2.37.3",