@xeplr/actions 2.2.1 → 2.2.2

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
@@ -26,6 +26,7 @@ Everything else is an **optional peer**, required only when an action actually u
26
26
  | `mailparser` | `email-read` (unless `body: 'none'`), `email-download-attachments` |
27
27
  | `nodemailer` (^10.0.13 — up to 10.0.5 has high-severity advisories) | `email-send` with its own connection |
28
28
  | `@xeplr/email` | `email-send` with `templateName` |
29
+ | `@xeplr/push` | `push-send` (the host sets it up at boot: `push.init({ knex })`) |
29
30
  | `knex` | the knex meta store |
30
31
  | `mongodb`, `ssh2-sftp-client`, `exceljs` | declared, but their drivers/sources are not implemented |
31
32
 
@@ -121,7 +122,7 @@ The input is checked exactly as a run checks it.
121
122
 
122
123
  | previewed (reads, up to 5) | example only (writes or sends; never run) |
123
124
  |---|---|
124
- | `db-fetch` (through the run's own cursor, then stops; nothing spooled), `db-list-tables` / `-views` / `-procedures` / `-columns`, `file-list`, `file-read`, `data-operation` (the run itself: it only computes), `email-read` (the mailbox opened **read-only**, IMAP `EXAMINE`, so nothing is marked read) | `file-move`, `file-delete`, `email-send`, `email-move`, `email-delete`, `email-download-email`, `email-download-attachments`, `export-table`, `export-pages`, `db-procedure` (a procedure may write) |
125
+ | `db-fetch` (through the run's own cursor, then stops; nothing spooled), `db-list-tables` / `-views` / `-procedures` / `-columns`, `file-list`, `file-read`, `data-operation` (the run itself: it only computes), `email-read` (the mailbox opened **read-only**, IMAP `EXAMINE`, so nothing is marked read) | `file-move`, `file-delete`, `email-send`, `push-send`, `email-move`, `email-delete`, `email-download-email`, `email-download-attachments`, `export-table`, `export-pages`, `db-procedure` (a procedure may write) |
125
126
 
126
127
  Neither: `db-push`, `db-move`, `http-request` (an outside system, which may change something), `spawn-program`, `file-upload`; a designer asks for an example by hand. `test/previews.test.js` checks it on a real folder, file and Postgres: five rows read and no file spooled, the real count kept, a deleting step's file still there.
127
128
 
@@ -144,6 +145,7 @@ Register the ones you want (`register(actions.builtins.dbFetch)`) or pass a modu
144
145
  | `dbListColumns` | `db-list-columns` | `{ columns }` — `name`, `dataType`, `udtName` and `maxLength` (characters; `-1` is SQL Server `(max)`, `null` when it has none) for one table. |
145
146
  | `fileUpload` | `file-upload` | Read a file (`sourceType`, `sourcePath`), parse it (`format`), load it into `targetTable` (`dbType`, `dbConnection`) as a New table, Append or Replace (`mode`, see [Import modes](#import-modes)). Passes `system.onProgress` (`{ rowsRead, batches, rowsWritten }`, per batch) and `system.signal` (stop) to the uploader, as `db-move` does. |
146
147
  | `sendEmail` | `email-send` | Send one message. See [Email](#email). |
148
+ | `pushSend` | `push-send` | A push notification on people's phones and computers, even with the app closed, by `emails` and/or `userIds`: `title` (required), `body`, `url` (a page of the app a tap opens), `tag`. Returns `{ devices, sent, removed, failed }`. Through [`@xeplr/push`](https://www.npmjs.com/package/@xeplr/push), which the host sets up. |
147
149
  | `emailRead` | `email-read` | List messages, newest first, with `unread_only`, `limit` (1–1000, default 50), `since`, `from_equals`, `from_contains`, `subject_contains`, `body: 'full' \| 'text' \| 'none'`, optional NDJSON spooling. |
148
150
  | `emailMove` | `email-move` | Move a message (`messageId`, `folder`) to `toFolder`. |
149
151
  | `emailDelete` | `email-delete` | Flag `\Deleted` **and expunge** — permanent, not a move to Trash. |
@@ -22,6 +22,8 @@ module.exports = {
22
22
  dataOperation: require('./data-operation'),
23
23
  spawnProgram: require('./spawn-program'),
24
24
  sendEmail: require('./send-email'),
25
+ // A notification on people's devices (Web Push) — peer @xeplr/push.
26
+ pushSend: require('./push-send'),
25
27
 
26
28
  // DB actions — factory-shaped (call fileUpload({ metaKnex }) to get a
27
29
  // registerable module) so the Knex handle for the meta store binds at
@@ -0,0 +1,58 @@
1
+ // push-send action — a notification on people's phones and computers, even
2
+ // with the app closed, through @xeplr/push (Web Push).
3
+ //
4
+ // Peer dep: `@xeplr/push`, lazy-required here, so an install that never
5
+ // pushes does not need it. The host app sets it up once at boot —
6
+ // push.init({ knex }) with its VAPID keys — and this step only sends.
7
+ //
8
+ // WHO: account ids (`userIds`) and/or emails (`emails`) — every device each of
9
+ // them switched on, each device once. Nobody to send to is refused, rather
10
+ // than "succeeding" with nothing sent.
11
+ //
12
+ // WHERE A TAP GOES: `url` is a page of the app ("/orders/{params.id}"). A full
13
+ // address to another site is replaced by the app's home by @xeplr/push, so a
14
+ // step can never send people off somewhere else.
15
+
16
+ function asList(v) {
17
+ if (v === undefined || v === null || v === '') return [];
18
+ return (Array.isArray(v) ? v : [v]).map(function(x) { return String(x).trim(); }).filter(Boolean);
19
+ }
20
+
21
+ module.exports = {
22
+ name: 'push-send',
23
+ // It sends, so it is never previewed.
24
+ example: { devices: 2, sent: 2, removed: 0, failed: 0 },
25
+ description: 'Send a push notification to people\'s phones and computers (Web Push, through @xeplr/push) — by account id or email. A tap opens the page given.',
26
+ requires: [], // @xeplr/push checked at runtime
27
+
28
+ inputSchema: [
29
+ { name: 'emails', type: 'array', order: 1,
30
+ description: 'Who gets it, by email. A bare string is accepted and wrapped.' },
31
+ { name: 'userIds', type: 'array', order: 2,
32
+ description: 'Who gets it, by account id — instead of, or as well as, emails.' },
33
+ { name: 'title', type: 'string', required: true, order: 3,
34
+ description: 'The first line, e.g. "RTO: #{params.orderNumber}". At most 120 characters.' },
35
+ { name: 'body', type: 'string', order: 4,
36
+ description: 'Under it. At most 300 characters.' },
37
+ { name: 'url', type: 'string', order: 5,
38
+ description: 'The page of the app a tap opens, e.g. /orders/{params.id}. Leave empty for the home page.' },
39
+ { name: 'tag', type: 'string', order: 6, group: 'Message options',
40
+ description: 'A new notification with the same tag replaces the one shown, so repeats stack as one.' }
41
+ ],
42
+
43
+ execute: async function(ctx) {
44
+ var input = ctx.input || {};
45
+ var push;
46
+ try {
47
+ push = require('@xeplr/push');
48
+ } catch (_) {
49
+ throw new Error("push-send: '@xeplr/push' is not installed. Run: npm install @xeplr/push — and set it up at boot with push.init({ knex }).");
50
+ }
51
+ if (!push.enabled || !push.enabled()) {
52
+ throw new Error('push-send: push is not set up on this server — the host must call push.init() with PUSH_VAPID_PUBLIC_KEY / PUSH_VAPID_PRIVATE_KEY / PUSH_VAPID_SUBJECT (npx xeplr-push keys).');
53
+ }
54
+ var target = { emails: asList(input.emails), userIds: asList(input.userIds) };
55
+ if (!target.emails.length && !target.userIds.length) throw new Error('push-send: say who gets it — emails or userIds.');
56
+ return push.send(target, { title: input.title, body: input.body, url: input.url, tag: input.tag });
57
+ }
58
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeplr/actions",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
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": {
@@ -42,6 +42,7 @@
42
42
  "@duckdb/node-api": "1.5.5-r.3",
43
43
  "@xeplr/db": "^1.1.0",
44
44
  "@xeplr/email": "^1.0.0",
45
+ "@xeplr/push": "^1.0.0",
45
46
  "@xeplr/utils": "^1.0.0",
46
47
  "csv-parse": "^7.0.2",
47
48
  "dejavu-fonts-ttf": "^2.37.3",
@@ -61,61 +62,64 @@
61
62
  "unzipper": "^0.12.5"
62
63
  },
63
64
  "peerDependenciesMeta": {
64
- "@xeplr/utils": {
65
- "optional": true
66
- },
67
65
  "@duckdb/node-api": {
68
66
  "optional": true
69
67
  },
70
- "knex": {
68
+ "@xeplr/email": {
71
69
  "optional": true
72
70
  },
73
- "pg": {
71
+ "@xeplr/push": {
74
72
  "optional": true
75
73
  },
76
- "mysql2": {
74
+ "@xeplr/utils": {
77
75
  "optional": true
78
76
  },
79
- "mssql": {
77
+ "csv-parse": {
80
78
  "optional": true
81
79
  },
82
- "mongodb": {
80
+ "dejavu-fonts-ttf": {
83
81
  "optional": true
84
82
  },
85
83
  "exceljs": {
86
84
  "optional": true
87
85
  },
88
- "unzipper": {
86
+ "fflate": {
89
87
  "optional": true
90
88
  },
91
- "saxes": {
89
+ "imapflow": {
92
90
  "optional": true
93
91
  },
94
- "csv-parse": {
92
+ "knex": {
95
93
  "optional": true
96
94
  },
97
- "imapflow": {
95
+ "mailparser": {
98
96
  "optional": true
99
97
  },
100
- "mailparser": {
98
+ "mongodb": {
99
+ "optional": true
100
+ },
101
+ "mssql": {
102
+ "optional": true
103
+ },
104
+ "mysql2": {
101
105
  "optional": true
102
106
  },
103
107
  "nodemailer": {
104
108
  "optional": true
105
109
  },
106
- "ssh2-sftp-client": {
110
+ "pdfkit": {
107
111
  "optional": true
108
112
  },
109
- "@xeplr/email": {
113
+ "pg": {
110
114
  "optional": true
111
115
  },
112
- "fflate": {
116
+ "saxes": {
113
117
  "optional": true
114
118
  },
115
- "pdfkit": {
119
+ "ssh2-sftp-client": {
116
120
  "optional": true
117
121
  },
118
- "dejavu-fonts-ttf": {
122
+ "unzipper": {
119
123
  "optional": true
120
124
  }
121
125
  },