@dxos/functions 0.5.2 → 0.5.3-main.088a2c8
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/dist/lib/browser/index.mjs +492 -146
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +488 -143
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/handler.d.ts +33 -12
- package/dist/types/src/handler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +17 -6
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.test.d.ts +2 -0
- package/dist/types/src/runtime/dev-server.test.d.ts.map +1 -0
- package/dist/types/src/runtime/scheduler.d.ts +55 -7
- package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
- package/dist/types/src/testing/test/handler.d.ts +3 -0
- package/dist/types/src/testing/test/handler.d.ts.map +1 -0
- package/dist/types/src/testing/test/index.d.ts +3 -0
- package/dist/types/src/testing/test/index.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +182 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/tools/schema.d.ts +2 -0
- package/dist/types/tools/schema.d.ts.map +1 -0
- package/package.json +20 -11
- package/schema/functions.json +183 -0
- package/src/handler.ts +56 -26
- package/src/index.ts +1 -1
- package/src/runtime/dev-server.test.ts +80 -0
- package/src/runtime/dev-server.ts +74 -40
- package/src/runtime/scheduler.test.ts +163 -9
- package/src/runtime/scheduler.ts +228 -64
- package/src/testing/test/handler.ts +9 -0
- package/src/testing/test/index.ts +7 -0
- package/src/types.ts +87 -0
- package/dist/types/src/manifest.d.ts +0 -26
- package/dist/types/src/manifest.d.ts.map +0 -1
- package/src/manifest.ts +0 -42
|
@@ -20,16 +20,16 @@ import { log } from "@dxos/log";
|
|
|
20
20
|
import { nonNullable } from "@dxos/util";
|
|
21
21
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/handler.ts";
|
|
22
22
|
var subscriptionHandler = (handler) => {
|
|
23
|
-
return ({ event, context, ...rest }) => {
|
|
23
|
+
return ({ event: { data }, context, ...rest }) => {
|
|
24
24
|
const { client } = context;
|
|
25
|
-
const space =
|
|
26
|
-
const objects = space
|
|
27
|
-
if (!!
|
|
25
|
+
const space = data.spaceKey ? client.spaces.get(PublicKey.from(data.spaceKey)) : void 0;
|
|
26
|
+
const objects = space ? data.objects?.map((id) => space.db.getObjectById(id)).filter(nonNullable) : [];
|
|
27
|
+
if (!!data.spaceKey && !space) {
|
|
28
28
|
log.warn("invalid space", {
|
|
29
|
-
|
|
29
|
+
data
|
|
30
30
|
}, {
|
|
31
31
|
F: __dxlog_file,
|
|
32
|
-
L:
|
|
32
|
+
L: 91,
|
|
33
33
|
S: void 0,
|
|
34
34
|
C: (f, a) => f(...a)
|
|
35
35
|
});
|
|
@@ -39,15 +39,18 @@ var subscriptionHandler = (handler) => {
|
|
|
39
39
|
objects: objects?.length
|
|
40
40
|
}, {
|
|
41
41
|
F: __dxlog_file,
|
|
42
|
-
L:
|
|
42
|
+
L: 93,
|
|
43
43
|
S: void 0,
|
|
44
44
|
C: (f, a) => f(...a)
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
return handler({
|
|
48
48
|
event: {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
data: {
|
|
50
|
+
...data,
|
|
51
|
+
space,
|
|
52
|
+
objects
|
|
53
|
+
}
|
|
51
54
|
},
|
|
52
55
|
context,
|
|
53
56
|
...rest
|
|
@@ -59,7 +62,7 @@ var subscriptionHandler = (handler) => {
|
|
|
59
62
|
import express from "express";
|
|
60
63
|
import { getPort } from "get-port-please";
|
|
61
64
|
import { join } from "@dxos/node-std/path";
|
|
62
|
-
import { Trigger } from "@dxos/async";
|
|
65
|
+
import { Event, Trigger } from "@dxos/async";
|
|
63
66
|
import { invariant } from "@dxos/invariant";
|
|
64
67
|
import { log as log2 } from "@dxos/log";
|
|
65
68
|
var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
|
|
@@ -70,11 +73,17 @@ var DevServer = class {
|
|
|
70
73
|
this._options = _options;
|
|
71
74
|
this._handlers = {};
|
|
72
75
|
this._seq = 0;
|
|
76
|
+
this.update = new Event();
|
|
77
|
+
}
|
|
78
|
+
get stats() {
|
|
79
|
+
return {
|
|
80
|
+
seq: this._seq
|
|
81
|
+
};
|
|
73
82
|
}
|
|
74
83
|
get endpoint() {
|
|
75
84
|
invariant(this._port, void 0, {
|
|
76
85
|
F: __dxlog_file2,
|
|
77
|
-
L:
|
|
86
|
+
L: 54,
|
|
78
87
|
S: this,
|
|
79
88
|
A: [
|
|
80
89
|
"this._port",
|
|
@@ -96,7 +105,7 @@ var DevServer = class {
|
|
|
96
105
|
} catch (err) {
|
|
97
106
|
log2.error("parsing function (check manifest)", err, {
|
|
98
107
|
F: __dxlog_file2,
|
|
99
|
-
L:
|
|
108
|
+
L: 71,
|
|
100
109
|
S: this,
|
|
101
110
|
C: (f, a) => f(...a)
|
|
102
111
|
});
|
|
@@ -104,29 +113,44 @@ var DevServer = class {
|
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
async start() {
|
|
116
|
+
invariant(!this._server, void 0, {
|
|
117
|
+
F: __dxlog_file2,
|
|
118
|
+
L: 77,
|
|
119
|
+
S: this,
|
|
120
|
+
A: [
|
|
121
|
+
"!this._server",
|
|
122
|
+
""
|
|
123
|
+
]
|
|
124
|
+
});
|
|
125
|
+
log2.info("starting...", void 0, {
|
|
126
|
+
F: __dxlog_file2,
|
|
127
|
+
L: 78,
|
|
128
|
+
S: this,
|
|
129
|
+
C: (f, a) => f(...a)
|
|
130
|
+
});
|
|
107
131
|
const app = express();
|
|
108
132
|
app.use(express.json());
|
|
109
|
-
app.post("/:
|
|
110
|
-
const {
|
|
133
|
+
app.post("/:path", async (req, res) => {
|
|
134
|
+
const { path: path2 } = req.params;
|
|
111
135
|
try {
|
|
112
136
|
log2.info("calling", {
|
|
113
|
-
|
|
137
|
+
path: path2
|
|
114
138
|
}, {
|
|
115
139
|
F: __dxlog_file2,
|
|
116
|
-
L:
|
|
140
|
+
L: 87,
|
|
117
141
|
S: this,
|
|
118
142
|
C: (f, a) => f(...a)
|
|
119
143
|
});
|
|
120
144
|
if (this._options.reload) {
|
|
121
|
-
const { def } = this._handlers[
|
|
145
|
+
const { def } = this._handlers["/" + path2];
|
|
122
146
|
await this._load(def, true);
|
|
123
147
|
}
|
|
124
|
-
res.statusCode = await this.
|
|
148
|
+
res.statusCode = await this.invoke("/" + path2, req.body);
|
|
125
149
|
res.end();
|
|
126
150
|
} catch (err) {
|
|
127
151
|
log2.catch(err, void 0, {
|
|
128
152
|
F: __dxlog_file2,
|
|
129
|
-
L:
|
|
153
|
+
L: 97,
|
|
130
154
|
S: this,
|
|
131
155
|
C: (f, a) => f(...a)
|
|
132
156
|
});
|
|
@@ -146,92 +170,170 @@ var DevServer = class {
|
|
|
146
170
|
try {
|
|
147
171
|
const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService.register({
|
|
148
172
|
endpoint: this.endpoint,
|
|
149
|
-
functions: this.functions.map(({ def: {
|
|
150
|
-
|
|
173
|
+
functions: this.functions.map(({ def: { id, path: path2 } }) => ({
|
|
174
|
+
id,
|
|
175
|
+
path: path2
|
|
151
176
|
}))
|
|
152
177
|
});
|
|
153
178
|
log2.info("registered", {
|
|
154
|
-
registrationId,
|
|
155
179
|
endpoint
|
|
156
180
|
}, {
|
|
157
181
|
F: __dxlog_file2,
|
|
158
|
-
L:
|
|
182
|
+
L: 113,
|
|
159
183
|
S: this,
|
|
160
184
|
C: (f, a) => f(...a)
|
|
161
185
|
});
|
|
162
|
-
this._registrationId = registrationId;
|
|
163
186
|
this._proxy = endpoint;
|
|
187
|
+
this._functionServiceRegistration = registrationId;
|
|
164
188
|
} catch (err) {
|
|
165
189
|
await this.stop();
|
|
166
190
|
throw new Error("FunctionRegistryService not available (check plugin is configured).");
|
|
167
191
|
}
|
|
192
|
+
log2.info("started", {
|
|
193
|
+
port: this._port
|
|
194
|
+
}, {
|
|
195
|
+
F: __dxlog_file2,
|
|
196
|
+
L: 121,
|
|
197
|
+
S: this,
|
|
198
|
+
C: (f, a) => f(...a)
|
|
199
|
+
});
|
|
168
200
|
}
|
|
169
201
|
async stop() {
|
|
202
|
+
invariant(this._server, void 0, {
|
|
203
|
+
F: __dxlog_file2,
|
|
204
|
+
L: 125,
|
|
205
|
+
S: this,
|
|
206
|
+
A: [
|
|
207
|
+
"this._server",
|
|
208
|
+
""
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
log2.info("stopping...", void 0, {
|
|
212
|
+
F: __dxlog_file2,
|
|
213
|
+
L: 126,
|
|
214
|
+
S: this,
|
|
215
|
+
C: (f, a) => f(...a)
|
|
216
|
+
});
|
|
170
217
|
const trigger = new Trigger();
|
|
171
|
-
this._server
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
218
|
+
this._server.close(async () => {
|
|
219
|
+
log2.info("server stopped", void 0, {
|
|
220
|
+
F: __dxlog_file2,
|
|
221
|
+
L: 130,
|
|
222
|
+
S: this,
|
|
223
|
+
C: (f, a) => f(...a)
|
|
224
|
+
});
|
|
225
|
+
try {
|
|
226
|
+
if (this._functionServiceRegistration) {
|
|
227
|
+
invariant(this._client.services.services.FunctionRegistryService, void 0, {
|
|
228
|
+
F: __dxlog_file2,
|
|
229
|
+
L: 133,
|
|
230
|
+
S: this,
|
|
231
|
+
A: [
|
|
232
|
+
"this._client.services.services.FunctionRegistryService",
|
|
233
|
+
""
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
await this._client.services.services.FunctionRegistryService.unregister({
|
|
237
|
+
registrationId: this._functionServiceRegistration
|
|
238
|
+
});
|
|
239
|
+
log2.info("unregistered", {
|
|
240
|
+
registrationId: this._functionServiceRegistration
|
|
241
|
+
}, {
|
|
242
|
+
F: __dxlog_file2,
|
|
243
|
+
L: 138,
|
|
244
|
+
S: this,
|
|
245
|
+
C: (f, a) => f(...a)
|
|
246
|
+
});
|
|
247
|
+
this._functionServiceRegistration = void 0;
|
|
248
|
+
this._proxy = void 0;
|
|
249
|
+
}
|
|
250
|
+
trigger.wake();
|
|
251
|
+
} catch (err) {
|
|
252
|
+
trigger.throw(err);
|
|
186
253
|
}
|
|
187
|
-
trigger.wake();
|
|
188
254
|
});
|
|
189
255
|
await trigger.wait();
|
|
190
256
|
this._port = void 0;
|
|
191
257
|
this._server = void 0;
|
|
258
|
+
log2.info("stopped", void 0, {
|
|
259
|
+
F: __dxlog_file2,
|
|
260
|
+
L: 152,
|
|
261
|
+
S: this,
|
|
262
|
+
C: (f, a) => f(...a)
|
|
263
|
+
});
|
|
192
264
|
}
|
|
193
265
|
/**
|
|
194
266
|
* Load function.
|
|
195
267
|
*/
|
|
196
|
-
async _load(def,
|
|
197
|
-
const { id,
|
|
198
|
-
const
|
|
268
|
+
async _load(def, force = false) {
|
|
269
|
+
const { id, path: path2, handler } = def;
|
|
270
|
+
const filePath = join(this._options.baseDir, handler);
|
|
199
271
|
log2.info("loading", {
|
|
200
|
-
id
|
|
272
|
+
id,
|
|
273
|
+
force
|
|
201
274
|
}, {
|
|
202
275
|
F: __dxlog_file2,
|
|
203
|
-
L:
|
|
276
|
+
L: 161,
|
|
204
277
|
S: this,
|
|
205
278
|
C: (f, a) => f(...a)
|
|
206
279
|
});
|
|
207
|
-
if (
|
|
208
|
-
Object.keys(__require.cache).filter((key) => key.startsWith(
|
|
280
|
+
if (force) {
|
|
281
|
+
Object.keys(__require.cache).filter((key) => key.startsWith(filePath)).forEach((key) => {
|
|
282
|
+
delete __require.cache[key];
|
|
283
|
+
});
|
|
209
284
|
}
|
|
210
|
-
const module = __require(
|
|
285
|
+
const module = __require(filePath);
|
|
211
286
|
if (typeof module.default !== "function") {
|
|
212
287
|
throw new Error(`Handler must export default function: ${id}`);
|
|
213
288
|
}
|
|
214
|
-
this._handlers[
|
|
289
|
+
this._handlers[path2] = {
|
|
215
290
|
def,
|
|
216
291
|
handler: module.default
|
|
217
292
|
};
|
|
218
293
|
}
|
|
219
294
|
/**
|
|
220
|
-
* Invoke function
|
|
295
|
+
* Invoke function.
|
|
221
296
|
*/
|
|
222
|
-
async
|
|
297
|
+
async invoke(path2, data) {
|
|
223
298
|
const seq = ++this._seq;
|
|
224
299
|
const now = Date.now();
|
|
225
300
|
log2.info("req", {
|
|
226
301
|
seq,
|
|
227
|
-
|
|
302
|
+
path: path2
|
|
228
303
|
}, {
|
|
229
304
|
F: __dxlog_file2,
|
|
230
|
-
L:
|
|
305
|
+
L: 188,
|
|
306
|
+
S: this,
|
|
307
|
+
C: (f, a) => f(...a)
|
|
308
|
+
});
|
|
309
|
+
const statusCode = await this._invoke(path2, {
|
|
310
|
+
data
|
|
311
|
+
});
|
|
312
|
+
log2.info("res", {
|
|
313
|
+
seq,
|
|
314
|
+
path: path2,
|
|
315
|
+
statusCode,
|
|
316
|
+
duration: Date.now() - now
|
|
317
|
+
}, {
|
|
318
|
+
F: __dxlog_file2,
|
|
319
|
+
L: 191,
|
|
231
320
|
S: this,
|
|
232
321
|
C: (f, a) => f(...a)
|
|
233
322
|
});
|
|
234
|
-
|
|
323
|
+
this.update.emit(statusCode);
|
|
324
|
+
return statusCode;
|
|
325
|
+
}
|
|
326
|
+
async _invoke(path2, event) {
|
|
327
|
+
const { handler } = this._handlers[path2] ?? {};
|
|
328
|
+
invariant(handler, `invalid path: ${path2}`, {
|
|
329
|
+
F: __dxlog_file2,
|
|
330
|
+
L: 198,
|
|
331
|
+
S: this,
|
|
332
|
+
A: [
|
|
333
|
+
"handler",
|
|
334
|
+
"`invalid path: ${path}`"
|
|
335
|
+
]
|
|
336
|
+
});
|
|
235
337
|
const context = {
|
|
236
338
|
client: this._client,
|
|
237
339
|
dataDir: this._options.dataDir
|
|
@@ -248,26 +350,19 @@ var DevServer = class {
|
|
|
248
350
|
event,
|
|
249
351
|
response
|
|
250
352
|
});
|
|
251
|
-
log2.info("res", {
|
|
252
|
-
seq,
|
|
253
|
-
name,
|
|
254
|
-
statusCode,
|
|
255
|
-
duration: Date.now() - now
|
|
256
|
-
}, {
|
|
257
|
-
F: __dxlog_file2,
|
|
258
|
-
L: 178,
|
|
259
|
-
S: this,
|
|
260
|
-
C: (f, a) => f(...a)
|
|
261
|
-
});
|
|
262
353
|
return statusCode;
|
|
263
354
|
}
|
|
264
355
|
};
|
|
265
356
|
|
|
266
357
|
// packages/core/functions/src/runtime/scheduler.ts
|
|
267
358
|
import { CronJob } from "cron";
|
|
359
|
+
import { getPort as getPort2 } from "get-port-please";
|
|
360
|
+
import http from "@dxos/node-std/http";
|
|
361
|
+
import path from "@dxos/node-std/path";
|
|
362
|
+
import WebSocket from "ws";
|
|
268
363
|
import { TextV0Type } from "@braneframe/types";
|
|
269
|
-
import { debounce, DeferredTask } from "@dxos/async";
|
|
270
|
-
import {
|
|
364
|
+
import { debounce, DeferredTask, sleep, Trigger as Trigger2 } from "@dxos/async";
|
|
365
|
+
import { createSubscription, Filter, getAutomergeObjectCore } from "@dxos/client/echo";
|
|
271
366
|
import { Context } from "@dxos/context";
|
|
272
367
|
import { invariant as invariant2 } from "@dxos/invariant";
|
|
273
368
|
import { log as log3 } from "@dxos/log";
|
|
@@ -278,7 +373,13 @@ var Scheduler = class {
|
|
|
278
373
|
this._client = _client;
|
|
279
374
|
this._manifest = _manifest;
|
|
280
375
|
this._options = _options;
|
|
281
|
-
this._mounts = new ComplexMap(({
|
|
376
|
+
this._mounts = new ComplexMap(({ spaceKey, id }) => `${spaceKey.toHex()}:${id}`);
|
|
377
|
+
}
|
|
378
|
+
get mounts() {
|
|
379
|
+
return Array.from(this._mounts.values()).reduce((acc, { trigger }) => {
|
|
380
|
+
acc.push(trigger);
|
|
381
|
+
return acc;
|
|
382
|
+
}, []);
|
|
282
383
|
}
|
|
283
384
|
async start() {
|
|
284
385
|
this._client.spaces.subscribe(async (spaces) => {
|
|
@@ -295,15 +396,18 @@ var Scheduler = class {
|
|
|
295
396
|
await this.unmount(id, spaceKey);
|
|
296
397
|
}
|
|
297
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Mount trigger.
|
|
401
|
+
*/
|
|
298
402
|
async mount(ctx, space, trigger) {
|
|
299
403
|
const key = {
|
|
300
|
-
|
|
301
|
-
|
|
404
|
+
spaceKey: space.key,
|
|
405
|
+
id: trigger.function
|
|
302
406
|
};
|
|
303
407
|
const def = this._manifest.functions.find((config) => config.id === trigger.function);
|
|
304
408
|
invariant2(def, `Function not found: ${trigger.function}`, {
|
|
305
409
|
F: __dxlog_file3,
|
|
306
|
-
L:
|
|
410
|
+
L: 76,
|
|
307
411
|
S: this,
|
|
308
412
|
A: [
|
|
309
413
|
"def",
|
|
@@ -321,18 +425,24 @@ var Scheduler = class {
|
|
|
321
425
|
trigger
|
|
322
426
|
}, {
|
|
323
427
|
F: __dxlog_file3,
|
|
324
|
-
L:
|
|
428
|
+
L: 82,
|
|
325
429
|
S: this,
|
|
326
430
|
C: (f, a) => f(...a)
|
|
327
431
|
});
|
|
328
432
|
if (ctx.disposed) {
|
|
329
433
|
return;
|
|
330
434
|
}
|
|
331
|
-
if (trigger.
|
|
332
|
-
this._createTimer(ctx, space, def, trigger);
|
|
435
|
+
if (trigger.timer) {
|
|
436
|
+
await this._createTimer(ctx, space, def, trigger);
|
|
437
|
+
}
|
|
438
|
+
if (trigger.webhook) {
|
|
439
|
+
await this._createWebhook(ctx, space, def, trigger);
|
|
440
|
+
}
|
|
441
|
+
if (trigger.websocket) {
|
|
442
|
+
await this._createWebsocket(ctx, space, def, trigger);
|
|
333
443
|
}
|
|
334
|
-
|
|
335
|
-
this._createSubscription(ctx, space, def,
|
|
444
|
+
if (trigger.subscription) {
|
|
445
|
+
await this._createSubscription(ctx, space, def, trigger);
|
|
336
446
|
}
|
|
337
447
|
}
|
|
338
448
|
}
|
|
@@ -347,25 +457,95 @@ var Scheduler = class {
|
|
|
347
457
|
await ctx.dispose();
|
|
348
458
|
}
|
|
349
459
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
460
|
+
async _execFunction(def, trigger, data) {
|
|
461
|
+
let status = 0;
|
|
462
|
+
try {
|
|
463
|
+
const payload = Object.assign({}, {
|
|
464
|
+
meta: trigger.meta
|
|
465
|
+
}, data);
|
|
466
|
+
const { endpoint, callback } = this._options;
|
|
467
|
+
if (endpoint) {
|
|
468
|
+
const url = path.join(endpoint, def.path);
|
|
469
|
+
log3.info("exec", {
|
|
470
|
+
function: def.id,
|
|
471
|
+
url
|
|
472
|
+
}, {
|
|
473
|
+
F: __dxlog_file3,
|
|
474
|
+
L: 128,
|
|
475
|
+
S: this,
|
|
476
|
+
C: (f, a) => f(...a)
|
|
477
|
+
});
|
|
478
|
+
const response = await fetch(url, {
|
|
479
|
+
method: "POST",
|
|
480
|
+
headers: {
|
|
481
|
+
"Content-Type": "application/json"
|
|
482
|
+
},
|
|
483
|
+
body: JSON.stringify(payload)
|
|
484
|
+
});
|
|
485
|
+
status = response.status;
|
|
486
|
+
} else if (callback) {
|
|
487
|
+
log3.info("exec", {
|
|
488
|
+
function: def.id
|
|
489
|
+
}, {
|
|
490
|
+
F: __dxlog_file3,
|
|
491
|
+
L: 139,
|
|
492
|
+
S: this,
|
|
493
|
+
C: (f, a) => f(...a)
|
|
494
|
+
});
|
|
495
|
+
status = await callback(payload) ?? 200;
|
|
496
|
+
}
|
|
497
|
+
if (status && status >= 400) {
|
|
498
|
+
throw new Error(`Response: ${status}`);
|
|
499
|
+
}
|
|
500
|
+
log3.info("done", {
|
|
501
|
+
function: def.id,
|
|
502
|
+
status
|
|
503
|
+
}, {
|
|
504
|
+
F: __dxlog_file3,
|
|
505
|
+
L: 149,
|
|
506
|
+
S: this,
|
|
507
|
+
C: (f, a) => f(...a)
|
|
354
508
|
});
|
|
355
|
-
})
|
|
356
|
-
|
|
509
|
+
} catch (err) {
|
|
510
|
+
log3.error("error", {
|
|
511
|
+
function: def.id,
|
|
512
|
+
error: err.message
|
|
513
|
+
}, {
|
|
514
|
+
F: __dxlog_file3,
|
|
515
|
+
L: 151,
|
|
516
|
+
S: this,
|
|
517
|
+
C: (f, a) => f(...a)
|
|
518
|
+
});
|
|
519
|
+
status = 500;
|
|
520
|
+
}
|
|
521
|
+
return status;
|
|
522
|
+
}
|
|
523
|
+
//
|
|
524
|
+
// Triggers
|
|
525
|
+
//
|
|
526
|
+
/**
|
|
527
|
+
* Cron timer.
|
|
528
|
+
*/
|
|
529
|
+
async _createTimer(ctx, space, def, trigger) {
|
|
530
|
+
log3.info("timer", {
|
|
531
|
+
space: space.key,
|
|
532
|
+
trigger
|
|
533
|
+
}, {
|
|
357
534
|
F: __dxlog_file3,
|
|
358
|
-
L:
|
|
535
|
+
L: 166,
|
|
359
536
|
S: this,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
537
|
+
C: (f, a) => f(...a)
|
|
538
|
+
});
|
|
539
|
+
const spec = trigger.timer;
|
|
540
|
+
const task = new DeferredTask(ctx, async () => {
|
|
541
|
+
await this._execFunction(def, trigger, {
|
|
542
|
+
spaceKey: space.key
|
|
543
|
+
});
|
|
364
544
|
});
|
|
365
545
|
let last = 0;
|
|
366
546
|
let run = 0;
|
|
367
547
|
const job = CronJob.from({
|
|
368
|
-
cronTime:
|
|
548
|
+
cronTime: spec.cron,
|
|
369
549
|
runOnInit: false,
|
|
370
550
|
onTick: () => {
|
|
371
551
|
const now = Date.now();
|
|
@@ -378,7 +558,7 @@ var Scheduler = class {
|
|
|
378
558
|
delta
|
|
379
559
|
}, {
|
|
380
560
|
F: __dxlog_file3,
|
|
381
|
-
L:
|
|
561
|
+
L: 186,
|
|
382
562
|
S: this,
|
|
383
563
|
C: (f, a) => f(...a)
|
|
384
564
|
});
|
|
@@ -388,20 +568,180 @@ var Scheduler = class {
|
|
|
388
568
|
job.start();
|
|
389
569
|
ctx.onDispose(() => job.stop());
|
|
390
570
|
}
|
|
391
|
-
|
|
571
|
+
/**
|
|
572
|
+
* Webhook.
|
|
573
|
+
*/
|
|
574
|
+
async _createWebhook(ctx, space, def, trigger) {
|
|
575
|
+
log3.info("webhook", {
|
|
576
|
+
space: space.key,
|
|
577
|
+
trigger
|
|
578
|
+
}, {
|
|
579
|
+
F: __dxlog_file3,
|
|
580
|
+
L: 199,
|
|
581
|
+
S: this,
|
|
582
|
+
C: (f, a) => f(...a)
|
|
583
|
+
});
|
|
584
|
+
const spec = trigger.webhook;
|
|
585
|
+
const server = http.createServer(async (req, res) => {
|
|
586
|
+
if (req.method !== spec.method) {
|
|
587
|
+
res.statusCode = 405;
|
|
588
|
+
return res.end();
|
|
589
|
+
}
|
|
590
|
+
res.statusCode = await this._execFunction(def, trigger, {
|
|
591
|
+
spaceKey: space.key
|
|
592
|
+
});
|
|
593
|
+
res.end();
|
|
594
|
+
});
|
|
595
|
+
const port = await getPort2({
|
|
596
|
+
random: true
|
|
597
|
+
});
|
|
598
|
+
server.listen(port, () => {
|
|
599
|
+
log3.info("started webhook", {
|
|
600
|
+
port
|
|
601
|
+
}, {
|
|
602
|
+
F: __dxlog_file3,
|
|
603
|
+
L: 223,
|
|
604
|
+
S: this,
|
|
605
|
+
C: (f, a) => f(...a)
|
|
606
|
+
});
|
|
607
|
+
spec.port = port;
|
|
608
|
+
});
|
|
609
|
+
ctx.onDispose(() => {
|
|
610
|
+
server.close();
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Websocket.
|
|
615
|
+
* NOTE: The port must be unique, so the same hook cannot be used for multiple spaces.
|
|
616
|
+
*/
|
|
617
|
+
async _createWebsocket(ctx, space, def, trigger, options = {
|
|
618
|
+
retryDelay: 2,
|
|
619
|
+
maxAttempts: 5
|
|
620
|
+
}) {
|
|
621
|
+
log3.info("websocket", {
|
|
622
|
+
space: space.key,
|
|
623
|
+
trigger
|
|
624
|
+
}, {
|
|
625
|
+
F: __dxlog_file3,
|
|
626
|
+
L: 249,
|
|
627
|
+
S: this,
|
|
628
|
+
C: (f, a) => f(...a)
|
|
629
|
+
});
|
|
630
|
+
const spec = trigger.websocket;
|
|
631
|
+
const { url, init } = spec;
|
|
632
|
+
let ws;
|
|
633
|
+
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
|
|
634
|
+
const open = new Trigger2();
|
|
635
|
+
ws = new WebSocket(url);
|
|
636
|
+
Object.assign(ws, {
|
|
637
|
+
onopen: () => {
|
|
638
|
+
log3.info("opened", {
|
|
639
|
+
url
|
|
640
|
+
}, {
|
|
641
|
+
F: __dxlog_file3,
|
|
642
|
+
L: 260,
|
|
643
|
+
S: this,
|
|
644
|
+
C: (f, a) => f(...a)
|
|
645
|
+
});
|
|
646
|
+
if (spec.init) {
|
|
647
|
+
ws.send(new TextEncoder().encode(JSON.stringify(init)));
|
|
648
|
+
}
|
|
649
|
+
open.wake(true);
|
|
650
|
+
},
|
|
651
|
+
onclose: (event) => {
|
|
652
|
+
log3.info("closed", {
|
|
653
|
+
url,
|
|
654
|
+
code: event.code
|
|
655
|
+
}, {
|
|
656
|
+
F: __dxlog_file3,
|
|
657
|
+
L: 269,
|
|
658
|
+
S: this,
|
|
659
|
+
C: (f, a) => f(...a)
|
|
660
|
+
});
|
|
661
|
+
if (event.code === 1006) {
|
|
662
|
+
setTimeout(async () => {
|
|
663
|
+
log3.info(`reconnecting in ${options.retryDelay}s...`, {
|
|
664
|
+
url
|
|
665
|
+
}, {
|
|
666
|
+
F: __dxlog_file3,
|
|
667
|
+
L: 274,
|
|
668
|
+
S: this,
|
|
669
|
+
C: (f, a) => f(...a)
|
|
670
|
+
});
|
|
671
|
+
await this._createWebsocket(ctx, space, def, trigger, options);
|
|
672
|
+
}, options.retryDelay * 1e3);
|
|
673
|
+
}
|
|
674
|
+
open.wake(false);
|
|
675
|
+
},
|
|
676
|
+
onerror: (event) => {
|
|
677
|
+
log3.catch(event.error, {
|
|
678
|
+
url
|
|
679
|
+
}, {
|
|
680
|
+
F: __dxlog_file3,
|
|
681
|
+
L: 283,
|
|
682
|
+
S: this,
|
|
683
|
+
C: (f, a) => f(...a)
|
|
684
|
+
});
|
|
685
|
+
},
|
|
686
|
+
onmessage: async (event) => {
|
|
687
|
+
try {
|
|
688
|
+
const data = JSON.parse(new TextDecoder().decode(event.data));
|
|
689
|
+
await this._execFunction(def, trigger, {
|
|
690
|
+
spaceKey: space.key,
|
|
691
|
+
data
|
|
692
|
+
});
|
|
693
|
+
} catch (err) {
|
|
694
|
+
log3.catch(err, {
|
|
695
|
+
url
|
|
696
|
+
}, {
|
|
697
|
+
F: __dxlog_file3,
|
|
698
|
+
L: 291,
|
|
699
|
+
S: this,
|
|
700
|
+
C: (f, a) => f(...a)
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
const isOpen = await open.wait();
|
|
706
|
+
if (isOpen) {
|
|
707
|
+
break;
|
|
708
|
+
} else {
|
|
709
|
+
const wait = Math.pow(attempt, 2) * options.retryDelay;
|
|
710
|
+
if (attempt < options.maxAttempts) {
|
|
711
|
+
log3.warn(`failed to connect; trying again in ${wait}s`, {
|
|
712
|
+
attempt
|
|
713
|
+
}, {
|
|
714
|
+
F: __dxlog_file3,
|
|
715
|
+
L: 302,
|
|
716
|
+
S: this,
|
|
717
|
+
C: (f, a) => f(...a)
|
|
718
|
+
});
|
|
719
|
+
await sleep(wait * 1e3);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
ctx.onDispose(() => {
|
|
724
|
+
ws?.close();
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* ECHO subscription.
|
|
729
|
+
*/
|
|
730
|
+
async _createSubscription(ctx, space, def, trigger) {
|
|
392
731
|
log3.info("subscription", {
|
|
393
732
|
space: space.key,
|
|
394
|
-
|
|
733
|
+
trigger
|
|
395
734
|
}, {
|
|
396
735
|
F: __dxlog_file3,
|
|
397
|
-
L:
|
|
736
|
+
L: 317,
|
|
398
737
|
S: this,
|
|
399
738
|
C: (f, a) => f(...a)
|
|
400
739
|
});
|
|
740
|
+
const spec = trigger.subscription;
|
|
401
741
|
const objectIds = /* @__PURE__ */ new Set();
|
|
402
742
|
const task = new DeferredTask(ctx, async () => {
|
|
403
|
-
await this._execFunction(def, {
|
|
404
|
-
|
|
743
|
+
await this._execFunction(def, trigger, {
|
|
744
|
+
spaceKey: space.key,
|
|
405
745
|
objects: Array.from(objectIds)
|
|
406
746
|
});
|
|
407
747
|
});
|
|
@@ -412,7 +752,7 @@ var Scheduler = class {
|
|
|
412
752
|
updated: updated.length
|
|
413
753
|
}, {
|
|
414
754
|
F: __dxlog_file3,
|
|
415
|
-
L:
|
|
755
|
+
L: 329,
|
|
416
756
|
S: this,
|
|
417
757
|
C: (f, a) => f(...a)
|
|
418
758
|
});
|
|
@@ -425,17 +765,15 @@ var Scheduler = class {
|
|
|
425
765
|
task.schedule();
|
|
426
766
|
});
|
|
427
767
|
subscriptions.push(() => subscription.unsubscribe());
|
|
428
|
-
const {
|
|
768
|
+
const { filter, options: { deep, delay } = {} } = spec;
|
|
429
769
|
const update = ({ objects }) => {
|
|
430
770
|
subscription.update(objects);
|
|
431
771
|
if (deep) {
|
|
432
772
|
log3.info("update", {
|
|
433
|
-
type,
|
|
434
|
-
deep,
|
|
435
773
|
objects: objects.length
|
|
436
774
|
}, {
|
|
437
775
|
F: __dxlog_file3,
|
|
438
|
-
L:
|
|
776
|
+
L: 349,
|
|
439
777
|
S: this,
|
|
440
778
|
C: (f, a) => f(...a)
|
|
441
779
|
});
|
|
@@ -449,60 +787,68 @@ var Scheduler = class {
|
|
|
449
787
|
}
|
|
450
788
|
}
|
|
451
789
|
};
|
|
452
|
-
const query = space.db.query(Filter.typename(type, props));
|
|
453
|
-
subscriptions.push(query.subscribe(delay ? debounce(update, delay
|
|
790
|
+
const query = space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
|
|
791
|
+
subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
|
|
454
792
|
ctx.onDispose(() => {
|
|
455
793
|
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
456
794
|
});
|
|
457
795
|
}
|
|
458
|
-
async _execFunction(def, data) {
|
|
459
|
-
try {
|
|
460
|
-
log3("request", {
|
|
461
|
-
function: def.id
|
|
462
|
-
}, {
|
|
463
|
-
F: __dxlog_file3,
|
|
464
|
-
L: 183,
|
|
465
|
-
S: this,
|
|
466
|
-
C: (f, a) => f(...a)
|
|
467
|
-
});
|
|
468
|
-
const { endpoint, callback } = this._options;
|
|
469
|
-
let status = 0;
|
|
470
|
-
if (endpoint) {
|
|
471
|
-
const response = await fetch(`${this._options.endpoint}/${def.name}`, {
|
|
472
|
-
method: "POST",
|
|
473
|
-
headers: {
|
|
474
|
-
"Content-Type": "application/json"
|
|
475
|
-
},
|
|
476
|
-
body: JSON.stringify(data)
|
|
477
|
-
});
|
|
478
|
-
status = response.status;
|
|
479
|
-
} else if (callback) {
|
|
480
|
-
status = await callback(data);
|
|
481
|
-
}
|
|
482
|
-
log3("result", {
|
|
483
|
-
function: def.id,
|
|
484
|
-
result: status
|
|
485
|
-
}, {
|
|
486
|
-
F: __dxlog_file3,
|
|
487
|
-
L: 202,
|
|
488
|
-
S: this,
|
|
489
|
-
C: (f, a) => f(...a)
|
|
490
|
-
});
|
|
491
|
-
} catch (err) {
|
|
492
|
-
log3.error("error", {
|
|
493
|
-
function: def.id,
|
|
494
|
-
error: err.message
|
|
495
|
-
}, {
|
|
496
|
-
F: __dxlog_file3,
|
|
497
|
-
L: 204,
|
|
498
|
-
S: this,
|
|
499
|
-
C: (f, a) => f(...a)
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
796
|
};
|
|
797
|
+
|
|
798
|
+
// packages/core/functions/src/types.ts
|
|
799
|
+
import * as S from "@effect/schema/Schema";
|
|
800
|
+
var TimerTriggerSchema = S.struct({
|
|
801
|
+
cron: S.string
|
|
802
|
+
});
|
|
803
|
+
var WebhookTriggerSchema = S.mutable(S.struct({
|
|
804
|
+
method: S.string,
|
|
805
|
+
// Assigned port.
|
|
806
|
+
port: S.optional(S.number)
|
|
807
|
+
}));
|
|
808
|
+
var WebsocketTriggerSchema = S.struct({
|
|
809
|
+
url: S.string,
|
|
810
|
+
init: S.optional(S.record(S.string, S.any))
|
|
811
|
+
});
|
|
812
|
+
var SubscriptionTriggerSchema = S.struct({
|
|
813
|
+
spaceKey: S.optional(S.string),
|
|
814
|
+
// TODO(burdon): Define query DSL.
|
|
815
|
+
filter: S.array(S.struct({
|
|
816
|
+
type: S.string,
|
|
817
|
+
props: S.optional(S.record(S.string, S.any))
|
|
818
|
+
})),
|
|
819
|
+
options: S.optional(S.struct({
|
|
820
|
+
// Watch changes to object (not just creation).
|
|
821
|
+
deep: S.optional(S.boolean),
|
|
822
|
+
// Debounce changes (delay in ms).
|
|
823
|
+
delay: S.optional(S.number)
|
|
824
|
+
}))
|
|
825
|
+
});
|
|
826
|
+
var FunctionTriggerSchema = S.struct({
|
|
827
|
+
function: S.string.pipe(S.description("Function ID/URI.")),
|
|
828
|
+
// Context passed to function.
|
|
829
|
+
meta: S.optional(S.record(S.string, S.any)),
|
|
830
|
+
// Triggers.
|
|
831
|
+
timer: S.optional(TimerTriggerSchema),
|
|
832
|
+
webhook: S.optional(WebhookTriggerSchema),
|
|
833
|
+
websocket: S.optional(WebsocketTriggerSchema),
|
|
834
|
+
subscription: S.optional(SubscriptionTriggerSchema)
|
|
835
|
+
});
|
|
836
|
+
var FunctionDefSchema = S.struct({
|
|
837
|
+
id: S.string,
|
|
838
|
+
// name: S.string,
|
|
839
|
+
description: S.optional(S.string),
|
|
840
|
+
// TODO(burdon): Rename route?
|
|
841
|
+
path: S.string,
|
|
842
|
+
// TODO(burdon): NPM/GitHub/Docker/CF URL?
|
|
843
|
+
handler: S.string
|
|
844
|
+
});
|
|
845
|
+
var FunctionManifestSchema = S.struct({
|
|
846
|
+
functions: S.mutable(S.array(FunctionDefSchema)),
|
|
847
|
+
triggers: S.optional(S.mutable(S.array(FunctionTriggerSchema)))
|
|
848
|
+
});
|
|
504
849
|
export {
|
|
505
850
|
DevServer,
|
|
851
|
+
FunctionManifestSchema,
|
|
506
852
|
Scheduler,
|
|
507
853
|
subscriptionHandler
|
|
508
854
|
};
|