@stackbone/cli 0.6.0 → 0.6.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/CHANGELOG.md +67 -0
- package/main.js +16680 -14142
- package/migrations/0090_trigger_subscriptions_sample_event.sql +14 -0
- package/migrations/0091_echo_guard_checks.sql +32 -0
- package/migrations/0092_trigger_subscriptions_outlook_new_email.sql +522 -0
- package/migrations/0093_trigger_subscriptions_webhook_token.sql +13 -0
- package/package.json +1 -1
- package/stackbone-cli-0.6.2.tgz +0 -0
- package/stackbone-cli-0.6.0.tgz +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- 0090_trigger_subscriptions_sample_event.sql — the first real event a link received (PLA-1900)
|
|
2
|
+
-- A trigger's declared `sampleData` is the only thing the mapper can derive event fields
|
|
3
|
+
-- from when the trigger publishes no `outputSchema`, and 49 Activepieces polling triggers
|
|
4
|
+
-- declare it as `{}` (Gmail's `gmail_new_email_received` among them). Their links offered
|
|
5
|
+
-- no field to wire. The link now keeps the first real event it delivers, and the mapper
|
|
6
|
+
-- reads that instead.
|
|
7
|
+
--
|
|
8
|
+
-- Written by the intake tick, never by an operator, and only while it is still NULL: the
|
|
9
|
+
-- first event wins and a later one never replaces it. The tick bounds it before writing
|
|
10
|
+
-- (long strings and arrays clipped), because `list()` returns this column for every link.
|
|
11
|
+
-- Nullable: NULL is "no sample yet", which every link that existed before this reads as.
|
|
12
|
+
|
|
13
|
+
ALTER TABLE stackbone_platform.trigger_subscriptions
|
|
14
|
+
ADD COLUMN IF NOT EXISTS sample_event jsonb;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- 0091_echo_guard_checks.sql — the runs that asked the self-write guard (PLA-1580)
|
|
2
|
+
-- A workflow that answers a connector trigger and writes back through the same
|
|
3
|
+
-- connection raises the event that trigger reads, so it starts itself again.
|
|
4
|
+
-- `isOwnEcho` stops that, and until now nothing recorded that a run called it:
|
|
5
|
+
-- the guard is a read, and it left no trace. The box now writes one row here per
|
|
6
|
+
-- run and connector the first time a run asks, whatever the guard answers, so the
|
|
7
|
+
-- link-level warning can leave a guarded run out.
|
|
8
|
+
--
|
|
9
|
+
-- One row per (run, connector), never one per call: the warning asks "did this run
|
|
10
|
+
-- guard this connector", and a run that asks a hundred times is the same answer.
|
|
11
|
+
-- The connector is compared exactly, like every connector id: a guard on `slack`
|
|
12
|
+
-- does not cover `linear`.
|
|
13
|
+
--
|
|
14
|
+
-- ON DELETE CASCADE from the run, the same as `run_steps`: a marker means nothing
|
|
15
|
+
-- once the run it describes is gone.
|
|
16
|
+
CREATE TABLE IF NOT EXISTS stackbone_platform.echo_guard_checks (
|
|
17
|
+
run_id uuid NOT NULL
|
|
18
|
+
REFERENCES stackbone_platform.runs(id) ON DELETE CASCADE,
|
|
19
|
+
connector text NOT NULL,
|
|
20
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
21
|
+
PRIMARY KEY (run_id, connector)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- The warning reads the runs ONE link started or resumed inside a short window,
|
|
25
|
+
-- once per link on every `GET /api/trigger-subscriptions`. 0075 left `trigger_ref`
|
|
26
|
+
-- unindexed because nothing filtered on it; this is that reader. On `started_at`
|
|
27
|
+
-- rather than `created_at` because a resume re-arms the row and moves `started_at`
|
|
28
|
+
-- to the resume, so a run that woke up and wrote is read by when it woke. Partial,
|
|
29
|
+
-- so only `event` runs (the only ones that carry a ref) pay for it on write.
|
|
30
|
+
CREATE INDEX IF NOT EXISTS runs_trigger_ref_started_at_idx
|
|
31
|
+
ON stackbone_platform.runs (trigger_ref, started_at DESC)
|
|
32
|
+
WHERE trigger_ref IS NOT NULL;
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
-- 0092_trigger_subscriptions_outlook_new_email.sql — the `outlook-app-only` links
|
|
2
|
+
-- move to the `microsoft-outlook` piece's `newEmail` (PLA-1932).
|
|
3
|
+
--
|
|
4
|
+
-- Both Outlook built-ins left the catalog: the one `microsoft-outlook` piece takes
|
|
5
|
+
-- either grant (PLA-1838) and its `newEmail` replaces their `message-received`.
|
|
6
|
+
-- Same folder (the Inbox), same `receivedDateTime gt` filter, every sender. What
|
|
7
|
+
-- differs is the event and where the mailbox lives, so each link is rewritten in
|
|
8
|
+
-- place, on the box that holds it, when that box boots: a box on a laptop is
|
|
9
|
+
-- converted by starting it, with nothing to run by hand.
|
|
10
|
+
--
|
|
11
|
+
-- Per link, `connector = 'outlook-app-only' AND trigger_id = 'message-received'`,
|
|
12
|
+
-- matched exactly (never `LIKE`: `outlook-app-only-2`, `outlook-catalog` and the
|
|
13
|
+
-- delegated `outlook` are other rows):
|
|
14
|
+
--
|
|
15
|
+
-- * the cursor. The built-in kept the ISO stamp of the newest mail it read; the
|
|
16
|
+
-- piece's TIMEBASED poll reads `{ "lastPoll": <epoch ms> }` and throws on
|
|
17
|
+
-- anything else, and filters `receivedDateTime gt` that instant, so the link
|
|
18
|
+
-- resumes where it stopped. Only a stamp with an explicit offset is read: one
|
|
19
|
+
-- without would be read in the session's time zone, and a guess is worse than
|
|
20
|
+
-- a link that refuses to poll. Anything else stays as it was (and is reported
|
|
21
|
+
-- below) rather than becoming epoch 0, which would replay the Inbox. A NULL
|
|
22
|
+
-- cursor stays NULL: arming the link runs the piece's own `onEnable`.
|
|
23
|
+
-- * the mapping. The built-in's event was its own flat map of Graph's message;
|
|
24
|
+
-- the piece's is the message as Graph answers it. A field the mapping reads
|
|
25
|
+
-- OFF THE EVENT is renamed: `messageId` to `id`, `snippet` to `bodyPreview`,
|
|
26
|
+
-- `date` to `receivedDateTime`, and `mailbox`, which the link stamped on, to
|
|
27
|
+
-- the mailbox itself as a string. Strings, later steps of a path, variables
|
|
28
|
+
-- and functions are never touched. The rewrite reads the JSONata token by
|
|
29
|
+
-- token; where it cannot be sure what a name reads (a predicate, a lambda,
|
|
30
|
+
-- the context `$` read whole or by name, an operator it does not model) it
|
|
31
|
+
-- leaves the mapping exactly as it was and reports it. That mapping fails at
|
|
32
|
+
-- the next event, where the operator sees it; a mangled one would keep
|
|
33
|
+
-- running with wrong data. A link with no mapping handed the workflow the
|
|
34
|
+
-- event itself, so it is given one that spells that event out.
|
|
35
|
+
-- * the mailbox goes from the link (`scope` `user-id`) to the connection, where
|
|
36
|
+
-- the piece reads it. The connector row is converted beside this, by the box
|
|
37
|
+
-- (`connect-tables.ts`) and by db-core 0038, before this runs.
|
|
38
|
+
-- * what described the old event goes: `mapping_graph` when the mapping
|
|
39
|
+
-- changed (it would compile back to the old names), and `sample_event`.
|
|
40
|
+
-- * `poll_revision` moves, so a poll result read before this is not saved.
|
|
41
|
+
--
|
|
42
|
+
-- The delivery journal needs nothing: both triggers key a delivery on Graph's
|
|
43
|
+
-- message id, so mail delivered before the move is not delivered again.
|
|
44
|
+
|
|
45
|
+
CREATE FUNCTION stackbone_platform.pla1932_last_poll(stored jsonb) RETURNS jsonb
|
|
46
|
+
LANGUAGE plpgsql AS $fn$
|
|
47
|
+
DECLARE
|
|
48
|
+
stamp text;
|
|
49
|
+
instant timestamptz;
|
|
50
|
+
BEGIN
|
|
51
|
+
IF stored IS NULL OR jsonb_typeof(stored) <> 'string' THEN
|
|
52
|
+
RETURN NULL;
|
|
53
|
+
END IF;
|
|
54
|
+
stamp := stored #>> '{}';
|
|
55
|
+
IF stamp !~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$' THEN
|
|
56
|
+
RETURN NULL;
|
|
57
|
+
END IF;
|
|
58
|
+
BEGIN
|
|
59
|
+
instant := stamp::timestamptz;
|
|
60
|
+
EXCEPTION WHEN others THEN
|
|
61
|
+
RETURN NULL;
|
|
62
|
+
END;
|
|
63
|
+
RETURN jsonb_build_object('lastPoll', floor(extract(epoch FROM instant) * 1000)::bigint);
|
|
64
|
+
END;
|
|
65
|
+
$fn$;
|
|
66
|
+
|
|
67
|
+
-- The JSONata over `message-received`'s event, rewritten over `newEmail`'s, or
|
|
68
|
+
-- NULL when it cannot be done with certainty. `context_is_event` tracks whether a
|
|
69
|
+
-- bare name at this point reads the event: true at the top, in a call's arguments,
|
|
70
|
+
-- in a group, an array or an object; false inside a predicate, a lambda body, a
|
|
71
|
+
-- block or object applied to a path, and a sort.
|
|
72
|
+
CREATE FUNCTION stackbone_platform.pla1932_new_email_jsonata(expr text, mailbox text)
|
|
73
|
+
RETURNS text
|
|
74
|
+
LANGUAGE plpgsql AS $fn$
|
|
75
|
+
DECLARE
|
|
76
|
+
-- Anything that ends a name: JSONata's operators, quotes and whitespace.
|
|
77
|
+
ends_name constant text := '[][{}().,;:?+*/%|=<>^&!~@#"''`$[:space:]-]';
|
|
78
|
+
n int := length(expr);
|
|
79
|
+
i int := 1;
|
|
80
|
+
j int;
|
|
81
|
+
c text;
|
|
82
|
+
nx text;
|
|
83
|
+
out text := '';
|
|
84
|
+
tok text;
|
|
85
|
+
word text;
|
|
86
|
+
quoted boolean;
|
|
87
|
+
is_field boolean;
|
|
88
|
+
-- The open brackets, innermost last, and what each one means.
|
|
89
|
+
opener text[] := '{}';
|
|
90
|
+
reads_event boolean[] := '{}';
|
|
91
|
+
is_object boolean[] := '{}';
|
|
92
|
+
reading_key boolean[] := '{}';
|
|
93
|
+
questions int[] := '{}';
|
|
94
|
+
is_params boolean[] := '{}';
|
|
95
|
+
depth int := 0;
|
|
96
|
+
top_questions int := 0;
|
|
97
|
+
context_is_event boolean := true;
|
|
98
|
+
closed_params boolean;
|
|
99
|
+
-- What came before: start, value, var, ctx (a bare `$`/`$$`), dot, op, open,
|
|
100
|
+
-- comma, colon, fn (`function`), params (a lambda's closed parameter list).
|
|
101
|
+
prev text := 'start';
|
|
102
|
+
before_dot text := 'other';
|
|
103
|
+
BEGIN
|
|
104
|
+
IF expr IS NULL THEN
|
|
105
|
+
RETURN NULL;
|
|
106
|
+
END IF;
|
|
107
|
+
WHILE i <= n LOOP
|
|
108
|
+
c := substr(expr, i, 1);
|
|
109
|
+
nx := substr(expr, i + 1, 1);
|
|
110
|
+
|
|
111
|
+
IF c ~ '[[:space:]]' THEN
|
|
112
|
+
out := out || c;
|
|
113
|
+
i := i + 1;
|
|
114
|
+
CONTINUE;
|
|
115
|
+
END IF;
|
|
116
|
+
|
|
117
|
+
IF c = '/' AND nx = '*' THEN
|
|
118
|
+
j := strpos(substr(expr, i + 2), '*/');
|
|
119
|
+
IF j = 0 THEN
|
|
120
|
+
RETURN NULL;
|
|
121
|
+
END IF;
|
|
122
|
+
out := out || substr(expr, i, j + 3);
|
|
123
|
+
i := i + j + 3;
|
|
124
|
+
CONTINUE;
|
|
125
|
+
END IF;
|
|
126
|
+
|
|
127
|
+
-- The context itself is understood only as `$.<field>`.
|
|
128
|
+
IF prev = 'ctx' AND c <> '.' THEN
|
|
129
|
+
RETURN NULL;
|
|
130
|
+
END IF;
|
|
131
|
+
|
|
132
|
+
IF c = '"' OR c = '''' THEN
|
|
133
|
+
j := i + 1;
|
|
134
|
+
LOOP
|
|
135
|
+
IF j > n THEN
|
|
136
|
+
RETURN NULL;
|
|
137
|
+
END IF;
|
|
138
|
+
IF substr(expr, j, 1) = '\' THEN
|
|
139
|
+
j := j + 2;
|
|
140
|
+
CONTINUE;
|
|
141
|
+
END IF;
|
|
142
|
+
EXIT WHEN substr(expr, j, 1) = c;
|
|
143
|
+
j := j + 1;
|
|
144
|
+
END LOOP;
|
|
145
|
+
out := out || substr(expr, i, j - i + 1);
|
|
146
|
+
i := j + 1;
|
|
147
|
+
prev := 'value';
|
|
148
|
+
CONTINUE;
|
|
149
|
+
END IF;
|
|
150
|
+
|
|
151
|
+
-- A regex literal stands where an operand does; elsewhere `/` divides.
|
|
152
|
+
IF c = '/' AND prev NOT IN ('value', 'var') THEN
|
|
153
|
+
j := i + 1;
|
|
154
|
+
LOOP
|
|
155
|
+
IF j > n THEN
|
|
156
|
+
RETURN NULL;
|
|
157
|
+
END IF;
|
|
158
|
+
IF substr(expr, j, 1) = '\' THEN
|
|
159
|
+
j := j + 2;
|
|
160
|
+
CONTINUE;
|
|
161
|
+
END IF;
|
|
162
|
+
EXIT WHEN substr(expr, j, 1) = '/';
|
|
163
|
+
j := j + 1;
|
|
164
|
+
END LOOP;
|
|
165
|
+
j := j + 1;
|
|
166
|
+
WHILE j <= n AND substr(expr, j, 1) ~ '[a-z]' LOOP
|
|
167
|
+
j := j + 1;
|
|
168
|
+
END LOOP;
|
|
169
|
+
out := out || substr(expr, i, j - i);
|
|
170
|
+
i := j;
|
|
171
|
+
prev := 'value';
|
|
172
|
+
CONTINUE;
|
|
173
|
+
END IF;
|
|
174
|
+
|
|
175
|
+
IF c = '$' THEN
|
|
176
|
+
j := i + 1;
|
|
177
|
+
IF nx = '$' THEN
|
|
178
|
+
j := j + 1;
|
|
179
|
+
ELSE
|
|
180
|
+
WHILE j <= n AND substr(expr, j, 1) !~ ends_name LOOP
|
|
181
|
+
j := j + 1;
|
|
182
|
+
END LOOP;
|
|
183
|
+
END IF;
|
|
184
|
+
tok := substr(expr, i, j - i);
|
|
185
|
+
out := out || tok;
|
|
186
|
+
i := j;
|
|
187
|
+
prev := CASE WHEN tok IN ('$', '$$') AND context_is_event THEN 'ctx' ELSE 'var' END;
|
|
188
|
+
CONTINUE;
|
|
189
|
+
END IF;
|
|
190
|
+
|
|
191
|
+
IF c ~ '[0-9]' THEN
|
|
192
|
+
j := i;
|
|
193
|
+
WHILE j <= n AND substr(expr, j, 1) ~ '[0-9]' LOOP
|
|
194
|
+
j := j + 1;
|
|
195
|
+
END LOOP;
|
|
196
|
+
IF substr(expr, j, 1) = '.' AND substr(expr, j + 1, 1) ~ '[0-9]' THEN
|
|
197
|
+
j := j + 1;
|
|
198
|
+
WHILE j <= n AND substr(expr, j, 1) ~ '[0-9]' LOOP
|
|
199
|
+
j := j + 1;
|
|
200
|
+
END LOOP;
|
|
201
|
+
END IF;
|
|
202
|
+
IF substr(expr, j, 2) ~ '^[eE][0-9]' OR substr(expr, j, 3) ~ '^[eE][+-][0-9]' THEN
|
|
203
|
+
j := j + 2;
|
|
204
|
+
WHILE j <= n AND substr(expr, j, 1) ~ '[0-9]' LOOP
|
|
205
|
+
j := j + 1;
|
|
206
|
+
END LOOP;
|
|
207
|
+
END IF;
|
|
208
|
+
out := out || substr(expr, i, j - i);
|
|
209
|
+
i := j;
|
|
210
|
+
prev := 'value';
|
|
211
|
+
CONTINUE;
|
|
212
|
+
END IF;
|
|
213
|
+
|
|
214
|
+
IF c = '`' OR c !~ ends_name THEN
|
|
215
|
+
IF c = '`' THEN
|
|
216
|
+
j := strpos(substr(expr, i + 1), '`');
|
|
217
|
+
IF j = 0 THEN
|
|
218
|
+
RETURN NULL;
|
|
219
|
+
END IF;
|
|
220
|
+
word := substr(expr, i + 1, j - 1);
|
|
221
|
+
tok := substr(expr, i, j + 1);
|
|
222
|
+
quoted := true;
|
|
223
|
+
i := i + j + 1;
|
|
224
|
+
ELSE
|
|
225
|
+
j := i;
|
|
226
|
+
WHILE j <= n AND substr(expr, j, 1) !~ ends_name LOOP
|
|
227
|
+
j := j + 1;
|
|
228
|
+
END LOOP;
|
|
229
|
+
word := substr(expr, i, j - i);
|
|
230
|
+
tok := word;
|
|
231
|
+
quoted := false;
|
|
232
|
+
i := j;
|
|
233
|
+
END IF;
|
|
234
|
+
|
|
235
|
+
IF NOT quoted AND word IN ('and', 'or', 'in') THEN
|
|
236
|
+
out := out || tok;
|
|
237
|
+
prev := 'op';
|
|
238
|
+
CONTINUE;
|
|
239
|
+
END IF;
|
|
240
|
+
IF NOT quoted AND word IN ('true', 'false', 'null') THEN
|
|
241
|
+
out := out || tok;
|
|
242
|
+
prev := 'value';
|
|
243
|
+
CONTINUE;
|
|
244
|
+
END IF;
|
|
245
|
+
IF NOT quoted AND word IN ('function', 'λ') THEN
|
|
246
|
+
out := out || tok;
|
|
247
|
+
prev := 'fn';
|
|
248
|
+
CONTINUE;
|
|
249
|
+
END IF;
|
|
250
|
+
|
|
251
|
+
is_field := word IN ('messageId', 'snippet', 'date', 'mailbox');
|
|
252
|
+
IF prev = 'dot' THEN
|
|
253
|
+
-- A later step of a path: another object's field, unless the step before
|
|
254
|
+
-- it is the context or a variable, whose value is unknown here.
|
|
255
|
+
IF is_field AND before_dot IN ('ctx', 'var') THEN
|
|
256
|
+
RETURN NULL;
|
|
257
|
+
END IF;
|
|
258
|
+
out := out || tok;
|
|
259
|
+
prev := 'value';
|
|
260
|
+
CONTINUE;
|
|
261
|
+
END IF;
|
|
262
|
+
|
|
263
|
+
IF NOT is_field THEN
|
|
264
|
+
out := out || tok;
|
|
265
|
+
ELSIF NOT context_is_event THEN
|
|
266
|
+
RETURN NULL;
|
|
267
|
+
ELSIF depth > 0 AND is_object[depth] AND reading_key[depth] THEN
|
|
268
|
+
-- An unquoted key is an expression: renaming it would rename the key.
|
|
269
|
+
RETURN NULL;
|
|
270
|
+
ELSIF word = 'mailbox' THEN
|
|
271
|
+
IF mailbox IS NULL THEN
|
|
272
|
+
RETURN NULL;
|
|
273
|
+
END IF;
|
|
274
|
+
-- The mailbox becomes a string, which has no field, no index and no call.
|
|
275
|
+
j := i;
|
|
276
|
+
WHILE j <= n AND substr(expr, j, 1) ~ '[[:space:]]' LOOP
|
|
277
|
+
j := j + 1;
|
|
278
|
+
END LOOP;
|
|
279
|
+
IF substr(expr, j, 1) IN ('.', '[', '(', '{', '/', '`') THEN
|
|
280
|
+
RETURN NULL;
|
|
281
|
+
END IF;
|
|
282
|
+
out := out || to_json(mailbox)::text;
|
|
283
|
+
ELSE
|
|
284
|
+
tok := CASE word
|
|
285
|
+
WHEN 'messageId' THEN 'id'
|
|
286
|
+
WHEN 'snippet' THEN 'bodyPreview'
|
|
287
|
+
ELSE 'receivedDateTime'
|
|
288
|
+
END;
|
|
289
|
+
out := out || CASE WHEN quoted THEN '`' || tok || '`' ELSE tok END;
|
|
290
|
+
END IF;
|
|
291
|
+
prev := 'value';
|
|
292
|
+
CONTINUE;
|
|
293
|
+
END IF;
|
|
294
|
+
|
|
295
|
+
IF c = '.' THEN
|
|
296
|
+
IF nx = '.' THEN
|
|
297
|
+
out := out || '..';
|
|
298
|
+
i := i + 2;
|
|
299
|
+
prev := 'op';
|
|
300
|
+
CONTINUE;
|
|
301
|
+
END IF;
|
|
302
|
+
before_dot := CASE WHEN prev IN ('ctx', 'var') THEN prev ELSE 'other' END;
|
|
303
|
+
out := out || c;
|
|
304
|
+
i := i + 1;
|
|
305
|
+
prev := 'dot';
|
|
306
|
+
CONTINUE;
|
|
307
|
+
END IF;
|
|
308
|
+
|
|
309
|
+
IF c IN ('(', '[', '{') OR (c = '^' AND nx = '(') THEN
|
|
310
|
+
depth := depth + 1;
|
|
311
|
+
opener[depth] := CASE WHEN c = '^' THEN '(' ELSE c END;
|
|
312
|
+
is_object[depth] := false;
|
|
313
|
+
reading_key[depth] := false;
|
|
314
|
+
questions[depth] := 0;
|
|
315
|
+
is_params[depth] := false;
|
|
316
|
+
IF c = '^' THEN
|
|
317
|
+
-- A sort's terms read each item.
|
|
318
|
+
reads_event[depth] := false;
|
|
319
|
+
out := out || '^(';
|
|
320
|
+
i := i + 2;
|
|
321
|
+
ELSE
|
|
322
|
+
IF c = '(' THEN
|
|
323
|
+
is_params[depth] := prev = 'fn';
|
|
324
|
+
-- A call's arguments and a group read the same context; a lambda's
|
|
325
|
+
-- parameters and a block applied to a path do not.
|
|
326
|
+
reads_event[depth] := context_is_event AND prev NOT IN ('fn', 'dot');
|
|
327
|
+
ELSIF c = '[' THEN
|
|
328
|
+
-- After a value it filters that value; at an operand it builds an array.
|
|
329
|
+
reads_event[depth] := context_is_event AND prev NOT IN ('value', 'var', 'dot');
|
|
330
|
+
ELSE
|
|
331
|
+
is_object[depth] := prev <> 'params';
|
|
332
|
+
reading_key[depth] := prev <> 'params';
|
|
333
|
+
-- An object built at an operand reads the context; after a path it
|
|
334
|
+
-- groups that path's items, and after a lambda's parameters it is the
|
|
335
|
+
-- lambda's body.
|
|
336
|
+
reads_event[depth] := context_is_event AND prev NOT IN ('value', 'var', 'dot', 'params');
|
|
337
|
+
END IF;
|
|
338
|
+
out := out || c;
|
|
339
|
+
i := i + 1;
|
|
340
|
+
END IF;
|
|
341
|
+
context_is_event := reads_event[depth];
|
|
342
|
+
prev := 'open';
|
|
343
|
+
CONTINUE;
|
|
344
|
+
END IF;
|
|
345
|
+
|
|
346
|
+
IF c IN (')', ']', '}') THEN
|
|
347
|
+
IF depth = 0 OR opener[depth] <> (CASE c WHEN ')' THEN '(' WHEN ']' THEN '[' ELSE '{' END) THEN
|
|
348
|
+
RETURN NULL;
|
|
349
|
+
END IF;
|
|
350
|
+
closed_params := is_params[depth];
|
|
351
|
+
depth := depth - 1;
|
|
352
|
+
context_is_event := CASE WHEN depth = 0 THEN true ELSE reads_event[depth] END;
|
|
353
|
+
out := out || c;
|
|
354
|
+
i := i + 1;
|
|
355
|
+
prev := CASE WHEN closed_params THEN 'params' ELSE 'value' END;
|
|
356
|
+
CONTINUE;
|
|
357
|
+
END IF;
|
|
358
|
+
|
|
359
|
+
IF c = ',' THEN
|
|
360
|
+
IF depth > 0 AND is_object[depth] THEN
|
|
361
|
+
reading_key[depth] := true;
|
|
362
|
+
questions[depth] := 0;
|
|
363
|
+
END IF;
|
|
364
|
+
out := out || c;
|
|
365
|
+
i := i + 1;
|
|
366
|
+
prev := 'comma';
|
|
367
|
+
CONTINUE;
|
|
368
|
+
END IF;
|
|
369
|
+
|
|
370
|
+
IF c = ':' THEN
|
|
371
|
+
IF nx = '=' THEN
|
|
372
|
+
out := out || ':=';
|
|
373
|
+
i := i + 2;
|
|
374
|
+
prev := 'op';
|
|
375
|
+
CONTINUE;
|
|
376
|
+
END IF;
|
|
377
|
+
IF depth > 0 AND questions[depth] > 0 THEN
|
|
378
|
+
questions[depth] := questions[depth] - 1;
|
|
379
|
+
prev := 'op';
|
|
380
|
+
ELSIF depth = 0 AND top_questions > 0 THEN
|
|
381
|
+
top_questions := top_questions - 1;
|
|
382
|
+
prev := 'op';
|
|
383
|
+
ELSIF depth > 0 AND is_object[depth] AND reading_key[depth] THEN
|
|
384
|
+
reading_key[depth] := false;
|
|
385
|
+
prev := 'colon';
|
|
386
|
+
ELSE
|
|
387
|
+
RETURN NULL;
|
|
388
|
+
END IF;
|
|
389
|
+
out := out || c;
|
|
390
|
+
i := i + 1;
|
|
391
|
+
CONTINUE;
|
|
392
|
+
END IF;
|
|
393
|
+
|
|
394
|
+
IF c = '?' THEN
|
|
395
|
+
IF nx IN (':', '?') THEN
|
|
396
|
+
out := out || c || nx;
|
|
397
|
+
i := i + 2;
|
|
398
|
+
ELSE
|
|
399
|
+
IF depth > 0 THEN
|
|
400
|
+
questions[depth] := questions[depth] + 1;
|
|
401
|
+
ELSE
|
|
402
|
+
top_questions := top_questions + 1;
|
|
403
|
+
END IF;
|
|
404
|
+
out := out || c;
|
|
405
|
+
i := i + 1;
|
|
406
|
+
END IF;
|
|
407
|
+
prev := 'op';
|
|
408
|
+
CONTINUE;
|
|
409
|
+
END IF;
|
|
410
|
+
|
|
411
|
+
IF c = '*' THEN
|
|
412
|
+
IF nx = '*' THEN
|
|
413
|
+
RETURN NULL;
|
|
414
|
+
END IF;
|
|
415
|
+
IF prev IN ('value', 'var') THEN
|
|
416
|
+
prev := 'op';
|
|
417
|
+
ELSIF prev = 'dot' OR NOT context_is_event THEN
|
|
418
|
+
prev := 'value';
|
|
419
|
+
ELSE
|
|
420
|
+
-- Every field of the event at once.
|
|
421
|
+
RETURN NULL;
|
|
422
|
+
END IF;
|
|
423
|
+
out := out || c;
|
|
424
|
+
i := i + 1;
|
|
425
|
+
CONTINUE;
|
|
426
|
+
END IF;
|
|
427
|
+
|
|
428
|
+
IF (c = '~' AND nx = '>') OR (c = '!' AND nx = '=') OR (c IN ('<', '>') AND nx = '=') THEN
|
|
429
|
+
out := out || c || nx;
|
|
430
|
+
i := i + 2;
|
|
431
|
+
prev := 'op';
|
|
432
|
+
CONTINUE;
|
|
433
|
+
END IF;
|
|
434
|
+
|
|
435
|
+
IF c IN (';', '=', '<', '>', '+', '-', '&', '/') THEN
|
|
436
|
+
out := out || c;
|
|
437
|
+
i := i + 1;
|
|
438
|
+
prev := 'op';
|
|
439
|
+
CONTINUE;
|
|
440
|
+
END IF;
|
|
441
|
+
|
|
442
|
+
-- `|` (transform), `@` and `#` (bindings), `%` (parent or modulo), a lone `~`,
|
|
443
|
+
-- `!` or `^`, and anything else: not modelled.
|
|
444
|
+
RETURN NULL;
|
|
445
|
+
END LOOP;
|
|
446
|
+
|
|
447
|
+
IF depth <> 0 OR prev = 'ctx' THEN
|
|
448
|
+
RETURN NULL;
|
|
449
|
+
END IF;
|
|
450
|
+
RETURN out;
|
|
451
|
+
END;
|
|
452
|
+
$fn$;
|
|
453
|
+
|
|
454
|
+
DO $$
|
|
455
|
+
DECLARE
|
|
456
|
+
link record;
|
|
457
|
+
v_mailbox text;
|
|
458
|
+
v_cursor jsonb;
|
|
459
|
+
v_mapping text;
|
|
460
|
+
v_correlation text;
|
|
461
|
+
BEGIN
|
|
462
|
+
FOR link IN
|
|
463
|
+
SELECT id, scope, poll_cursor, mapping, correlation_key
|
|
464
|
+
FROM stackbone_platform.trigger_subscriptions
|
|
465
|
+
WHERE connector = 'outlook-app-only'
|
|
466
|
+
AND trigger_id = 'message-received'
|
|
467
|
+
ORDER BY created_at, id
|
|
468
|
+
FOR UPDATE
|
|
469
|
+
LOOP
|
|
470
|
+
v_mailbox := nullif(btrim(link.scope ->> 'user-id'), '');
|
|
471
|
+
|
|
472
|
+
v_cursor := link.poll_cursor;
|
|
473
|
+
IF link.poll_cursor IS NOT NULL THEN
|
|
474
|
+
v_cursor := stackbone_platform.pla1932_last_poll(link.poll_cursor);
|
|
475
|
+
IF v_cursor IS NULL THEN
|
|
476
|
+
v_cursor := link.poll_cursor;
|
|
477
|
+
RAISE WARNING 'PLA-1932: trigger link % keeps its cursor %: it is no timestamp newEmail can resume from. Arm the link again, starting from now.',
|
|
478
|
+
link.id, link.poll_cursor;
|
|
479
|
+
END IF;
|
|
480
|
+
END IF;
|
|
481
|
+
|
|
482
|
+
IF link.mapping IS NULL THEN
|
|
483
|
+
v_mapping := '{ "messageId": id, "conversationId": conversationId, "from": from, "subject": subject, "snippet": bodyPreview, "date": receivedDateTime, "isRead": isRead'
|
|
484
|
+
|| CASE WHEN v_mailbox IS NULL THEN '' ELSE ', "mailbox": ' || to_json(v_mailbox)::text END
|
|
485
|
+
|| ' }';
|
|
486
|
+
ELSE
|
|
487
|
+
v_mapping := stackbone_platform.pla1932_new_email_jsonata(link.mapping, v_mailbox);
|
|
488
|
+
IF v_mapping IS NULL THEN
|
|
489
|
+
v_mapping := link.mapping;
|
|
490
|
+
RAISE WARNING 'PLA-1932: trigger link % keeps its mapping as it was: rewrite it over the newEmail event (id, bodyPreview, receivedDateTime, and the mailbox as a string).',
|
|
491
|
+
link.id;
|
|
492
|
+
END IF;
|
|
493
|
+
END IF;
|
|
494
|
+
|
|
495
|
+
v_correlation := link.correlation_key;
|
|
496
|
+
IF link.correlation_key IS NOT NULL THEN
|
|
497
|
+
v_correlation := coalesce(
|
|
498
|
+
stackbone_platform.pla1932_new_email_jsonata(link.correlation_key, v_mailbox),
|
|
499
|
+
link.correlation_key
|
|
500
|
+
);
|
|
501
|
+
END IF;
|
|
502
|
+
|
|
503
|
+
UPDATE stackbone_platform.trigger_subscriptions
|
|
504
|
+
SET connector = 'microsoft-outlook',
|
|
505
|
+
trigger_id = 'newEmail',
|
|
506
|
+
poll_cursor = v_cursor,
|
|
507
|
+
mapping = v_mapping,
|
|
508
|
+
mapping_graph = CASE
|
|
509
|
+
WHEN v_mapping IS DISTINCT FROM link.mapping THEN NULL
|
|
510
|
+
ELSE mapping_graph
|
|
511
|
+
END,
|
|
512
|
+
correlation_key = v_correlation,
|
|
513
|
+
scope = NULL,
|
|
514
|
+
sample_event = NULL,
|
|
515
|
+
poll_revision = poll_revision + 1,
|
|
516
|
+
updated_at = now()
|
|
517
|
+
WHERE id = link.id;
|
|
518
|
+
END LOOP;
|
|
519
|
+
END $$;
|
|
520
|
+
|
|
521
|
+
DROP FUNCTION stackbone_platform.pla1932_last_poll(jsonb);
|
|
522
|
+
DROP FUNCTION stackbone_platform.pla1932_new_email_jsonata(text, text);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- 0093_trigger_subscriptions_webhook_token.sql — a webhook link's ingress token (PLA-1856)
|
|
2
|
+
-- A piece's WEBHOOK trigger is delivered by the provider POSTing to the box at
|
|
3
|
+
-- `/api/hooks/<link id>/<token>`. The route is public, so the token is the credential:
|
|
4
|
+
-- 128 random bits, minted when the link arms, cleared when it disarms and minted
|
|
5
|
+
-- again on the next arm, so a URL the provider held before stops answering.
|
|
6
|
+
--
|
|
7
|
+
-- Plaintext, compared in constant time: `onDisable` is handed the same URL `onEnable`
|
|
8
|
+
-- registered, which a digest could not rebuild. Never returned by a list or a read.
|
|
9
|
+
-- Nullable: NULL is "no ingress", which every polling link and every link that is off
|
|
10
|
+
-- reads as.
|
|
11
|
+
|
|
12
|
+
ALTER TABLE stackbone_platform.trigger_subscriptions
|
|
13
|
+
ADD COLUMN IF NOT EXISTS webhook_token text;
|
package/package.json
CHANGED
|
Binary file
|
package/stackbone-cli-0.6.0.tgz
DELETED
|
Binary file
|