@dxos/functions 0.5.4 → 0.5.5-main.1aa8b60

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.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/lib/browser/{chunk-4D4I3YMJ.mjs → chunk-ERWZ4JUZ.mjs} +31 -31
  3. package/dist/lib/browser/{chunk-4D4I3YMJ.mjs.map → chunk-ERWZ4JUZ.mjs.map} +2 -2
  4. package/dist/lib/browser/chunk-SXZ5DYJG.mjs +1089 -0
  5. package/dist/lib/browser/chunk-SXZ5DYJG.mjs.map +7 -0
  6. package/dist/lib/browser/index.mjs +18 -1076
  7. package/dist/lib/browser/index.mjs.map +4 -4
  8. package/dist/lib/browser/meta.json +1 -1
  9. package/dist/lib/browser/testing/index.mjs +151 -0
  10. package/dist/lib/browser/testing/index.mjs.map +7 -0
  11. package/dist/lib/browser/types.mjs +1 -1
  12. package/dist/lib/node/{chunk-3UYUR5N5.cjs → chunk-BLLSDTKZ.cjs} +34 -34
  13. package/dist/lib/node/{chunk-3UYUR5N5.cjs.map → chunk-BLLSDTKZ.cjs.map} +2 -2
  14. package/dist/lib/node/chunk-RPHL3ORN.cjs +1102 -0
  15. package/dist/lib/node/chunk-RPHL3ORN.cjs.map +7 -0
  16. package/dist/lib/node/index.cjs +20 -1074
  17. package/dist/lib/node/index.cjs.map +4 -4
  18. package/dist/lib/node/meta.json +1 -1
  19. package/dist/lib/node/testing/index.cjs +172 -0
  20. package/dist/lib/node/testing/index.cjs.map +7 -0
  21. package/dist/lib/node/types.cjs +5 -5
  22. package/dist/lib/node/types.cjs.map +1 -1
  23. package/dist/types/src/function/function-registry.d.ts +1 -0
  24. package/dist/types/src/function/function-registry.d.ts.map +1 -1
  25. package/dist/types/src/runtime/dev-server.d.ts +1 -0
  26. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  27. package/dist/types/src/testing/index.d.ts +1 -0
  28. package/dist/types/src/testing/index.d.ts.map +1 -1
  29. package/dist/types/src/testing/manifest.d.ts +3 -0
  30. package/dist/types/src/testing/manifest.d.ts.map +1 -0
  31. package/dist/types/src/testing/plugin-init.d.ts +6 -0
  32. package/dist/types/src/testing/plugin-init.d.ts.map +1 -0
  33. package/dist/types/src/testing/setup.d.ts +11 -1
  34. package/dist/types/src/testing/setup.d.ts.map +1 -1
  35. package/dist/types/src/testing/util.d.ts +2 -0
  36. package/dist/types/src/testing/util.d.ts.map +1 -1
  37. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -1
  38. package/dist/types/src/types.d.ts +27 -38
  39. package/dist/types/src/types.d.ts.map +1 -1
  40. package/package.json +24 -15
  41. package/src/function/function-registry.test.ts +14 -1
  42. package/src/function/function-registry.ts +10 -0
  43. package/src/runtime/dev-server.test.ts +42 -24
  44. package/src/runtime/dev-server.ts +17 -13
  45. package/src/runtime/scheduler.test.ts +4 -2
  46. package/src/testing/functions-integration.test.ts +8 -45
  47. package/src/testing/index.ts +1 -0
  48. package/src/testing/manifest.ts +15 -0
  49. package/src/testing/plugin-init.ts +20 -0
  50. package/src/testing/setup.ts +65 -6
  51. package/src/testing/types.ts +1 -1
  52. package/src/testing/util.ts +10 -0
  53. package/src/trigger/type/websocket-trigger.ts +12 -8
  54. package/src/types.ts +31 -31
@@ -0,0 +1,1089 @@
1
+ import "@dxos/node-std/globals";
2
+ import {
3
+ FunctionDef,
4
+ FunctionTrigger,
5
+ __require
6
+ } from "./chunk-ERWZ4JUZ.mjs";
7
+
8
+ // packages/core/functions/src/function/function-registry.ts
9
+ import { Event } from "@dxos/async";
10
+ import { create, Filter } from "@dxos/client/echo";
11
+ import { Resource } from "@dxos/context";
12
+ import { PublicKey } from "@dxos/keys";
13
+ import { log } from "@dxos/log";
14
+ import { ComplexMap, diff } from "@dxos/util";
15
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/function/function-registry.ts";
16
+ var FunctionRegistry = class extends Resource {
17
+ constructor(_client) {
18
+ super();
19
+ this._client = _client;
20
+ this._functionBySpaceKey = new ComplexMap(PublicKey.hash);
21
+ this.registered = new Event();
22
+ }
23
+ getFunctions(space) {
24
+ return this._functionBySpaceKey.get(space.key) ?? [];
25
+ }
26
+ getUniqueByUri() {
27
+ const uniqueByUri = [
28
+ ...this._functionBySpaceKey.values()
29
+ ].flatMap((defs) => defs).reduce((acc, v) => {
30
+ acc.set(v.uri, v);
31
+ return acc;
32
+ }, /* @__PURE__ */ new Map());
33
+ return [
34
+ ...uniqueByUri.values()
35
+ ];
36
+ }
37
+ /**
38
+ * Loads function definitions from the manifest into the space.
39
+ * We first load all the definitions from the space to deduplicate by functionId.
40
+ */
41
+ async register(space, functions) {
42
+ log("register", {
43
+ space: space.key,
44
+ functions: functions?.length ?? 0
45
+ }, {
46
+ F: __dxlog_file,
47
+ L: 48,
48
+ S: this,
49
+ C: (f, a) => f(...a)
50
+ });
51
+ if (!functions?.length) {
52
+ return;
53
+ }
54
+ if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionDef)) {
55
+ space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionDef);
56
+ }
57
+ const { objects: existing } = await space.db.query(Filter.schema(FunctionDef)).run();
58
+ const { added } = diff(existing, functions, (a, b) => a.uri === b.uri);
59
+ added.forEach((def) => space.db.add(create(FunctionDef, def)));
60
+ }
61
+ async _open() {
62
+ log.info("opening...", void 0, {
63
+ F: __dxlog_file,
64
+ L: 64,
65
+ S: this,
66
+ C: (f, a) => f(...a)
67
+ });
68
+ const spacesSubscription = this._client.spaces.subscribe(async (spaces) => {
69
+ for (const space of spaces) {
70
+ if (this._functionBySpaceKey.has(space.key)) {
71
+ continue;
72
+ }
73
+ const registered = [];
74
+ this._functionBySpaceKey.set(space.key, registered);
75
+ await space.waitUntilReady();
76
+ if (this._ctx.disposed) {
77
+ break;
78
+ }
79
+ this._ctx.onDispose(space.db.query(Filter.schema(FunctionDef)).subscribe(({ objects }) => {
80
+ const { added } = diff(registered, objects, (a, b) => a.uri === b.uri);
81
+ if (added.length > 0) {
82
+ registered.push(...added);
83
+ this.registered.emit({
84
+ space,
85
+ added
86
+ });
87
+ }
88
+ }));
89
+ }
90
+ });
91
+ this._ctx.onDispose(() => spacesSubscription.unsubscribe());
92
+ }
93
+ async _close(_) {
94
+ log.info("closing...", void 0, {
95
+ F: __dxlog_file,
96
+ L: 97,
97
+ S: this,
98
+ C: (f, a) => f(...a)
99
+ });
100
+ this._functionBySpaceKey.clear();
101
+ }
102
+ };
103
+
104
+ // packages/core/functions/src/runtime/dev-server.ts
105
+ import express from "express";
106
+ import { getPort } from "get-port-please";
107
+ import { join } from "@dxos/node-std/path";
108
+ import { asyncTimeout, Event as Event2, Trigger } from "@dxos/async";
109
+ import { Context } from "@dxos/context";
110
+ import { invariant } from "@dxos/invariant";
111
+ import { log as log2 } from "@dxos/log";
112
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
113
+ var DevServer = class {
114
+ constructor(_client, _functionsRegistry, _options) {
115
+ this._client = _client;
116
+ this._functionsRegistry = _functionsRegistry;
117
+ this._options = _options;
118
+ this._ctx = createContext();
119
+ this._handlers = {};
120
+ this._seq = 0;
121
+ this.update = new Event2();
122
+ }
123
+ get stats() {
124
+ return {
125
+ seq: this._seq
126
+ };
127
+ }
128
+ get endpoint() {
129
+ invariant(this._port, void 0, {
130
+ F: __dxlog_file2,
131
+ L: 57,
132
+ S: this,
133
+ A: [
134
+ "this._port",
135
+ ""
136
+ ]
137
+ });
138
+ return `http://localhost:${this._port}`;
139
+ }
140
+ get proxy() {
141
+ return this._proxy;
142
+ }
143
+ get functions() {
144
+ return Object.values(this._handlers);
145
+ }
146
+ async start() {
147
+ invariant(!this._server, void 0, {
148
+ F: __dxlog_file2,
149
+ L: 70,
150
+ S: this,
151
+ A: [
152
+ "!this._server",
153
+ ""
154
+ ]
155
+ });
156
+ log2.info("starting...", void 0, {
157
+ F: __dxlog_file2,
158
+ L: 71,
159
+ S: this,
160
+ C: (f, a) => f(...a)
161
+ });
162
+ this._ctx = createContext();
163
+ const app = express();
164
+ app.use(express.json());
165
+ app.post("/:path", async (req, res) => {
166
+ const { path: path2 } = req.params;
167
+ try {
168
+ log2.info("calling", {
169
+ path: path2
170
+ }, {
171
+ F: __dxlog_file2,
172
+ L: 81,
173
+ S: this,
174
+ C: (f, a) => f(...a)
175
+ });
176
+ if (this._options.reload) {
177
+ const { def } = this._handlers["/" + path2];
178
+ await this._load(def, true);
179
+ }
180
+ res.statusCode = await asyncTimeout(this.invoke("/" + path2, req.body), 2e4);
181
+ res.end();
182
+ } catch (err) {
183
+ log2.catch(err, void 0, {
184
+ F: __dxlog_file2,
185
+ L: 91,
186
+ S: this,
187
+ C: (f, a) => f(...a)
188
+ });
189
+ res.statusCode = 500;
190
+ res.end();
191
+ }
192
+ });
193
+ this._port = this._options.port ?? await getPort({
194
+ host: "localhost",
195
+ port: 7200,
196
+ portRange: [
197
+ 7200,
198
+ 7299
199
+ ]
200
+ });
201
+ this._server = app.listen(this._port);
202
+ try {
203
+ const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService.register({
204
+ endpoint: this.endpoint
205
+ });
206
+ log2.info("registered", {
207
+ endpoint
208
+ }, {
209
+ F: __dxlog_file2,
210
+ L: 106,
211
+ S: this,
212
+ C: (f, a) => f(...a)
213
+ });
214
+ this._proxy = endpoint;
215
+ this._functionServiceRegistration = registrationId;
216
+ await this._handleNewFunctions(this._functionsRegistry.getUniqueByUri());
217
+ this._ctx.onDispose(this._functionsRegistry.registered.on(({ added }) => this._handleNewFunctions(added)));
218
+ } catch (err) {
219
+ await this.stop();
220
+ throw new Error("FunctionRegistryService not available (check plugin is configured).");
221
+ }
222
+ log2.info("started", {
223
+ port: this._port
224
+ }, {
225
+ F: __dxlog_file2,
226
+ L: 118,
227
+ S: this,
228
+ C: (f, a) => f(...a)
229
+ });
230
+ }
231
+ async stop() {
232
+ if (!this._server) {
233
+ return;
234
+ }
235
+ log2.info("stopping...", void 0, {
236
+ F: __dxlog_file2,
237
+ L: 126,
238
+ S: this,
239
+ C: (f, a) => f(...a)
240
+ });
241
+ await this._ctx.dispose();
242
+ const trigger = new Trigger();
243
+ this._server.close(async () => {
244
+ log2.info("server stopped", void 0, {
245
+ F: __dxlog_file2,
246
+ L: 131,
247
+ S: this,
248
+ C: (f, a) => f(...a)
249
+ });
250
+ try {
251
+ if (this._functionServiceRegistration) {
252
+ invariant(this._client.services.services.FunctionRegistryService, void 0, {
253
+ F: __dxlog_file2,
254
+ L: 134,
255
+ S: this,
256
+ A: [
257
+ "this._client.services.services.FunctionRegistryService",
258
+ ""
259
+ ]
260
+ });
261
+ await this._client.services.services.FunctionRegistryService.unregister({
262
+ registrationId: this._functionServiceRegistration
263
+ });
264
+ log2.info("unregistered", {
265
+ registrationId: this._functionServiceRegistration
266
+ }, {
267
+ F: __dxlog_file2,
268
+ L: 139,
269
+ S: this,
270
+ C: (f, a) => f(...a)
271
+ });
272
+ this._functionServiceRegistration = void 0;
273
+ this._proxy = void 0;
274
+ }
275
+ trigger.wake();
276
+ } catch (err) {
277
+ trigger.throw(err);
278
+ }
279
+ });
280
+ await trigger.wait();
281
+ this._port = void 0;
282
+ this._server = void 0;
283
+ log2.info("stopped", void 0, {
284
+ F: __dxlog_file2,
285
+ L: 153,
286
+ S: this,
287
+ C: (f, a) => f(...a)
288
+ });
289
+ }
290
+ async _handleNewFunctions(newFunctions) {
291
+ newFunctions.forEach((def) => this._load(def));
292
+ await this._safeUpdateRegistration();
293
+ log2("new functions loaded", {
294
+ newFunctions
295
+ }, {
296
+ F: __dxlog_file2,
297
+ L: 159,
298
+ S: this,
299
+ C: (f, a) => f(...a)
300
+ });
301
+ }
302
+ /**
303
+ * Load function.
304
+ */
305
+ async _load(def, force) {
306
+ const { uri, route, handler } = def;
307
+ const filePath = join(this._options.baseDir, handler);
308
+ log2.info("loading", {
309
+ uri,
310
+ force
311
+ }, {
312
+ F: __dxlog_file2,
313
+ L: 168,
314
+ S: this,
315
+ C: (f, a) => f(...a)
316
+ });
317
+ if (force) {
318
+ Object.keys(__require.cache).filter((key) => key.startsWith(filePath)).forEach((key) => {
319
+ delete __require.cache[key];
320
+ });
321
+ }
322
+ const module = __require(filePath);
323
+ if (typeof module.default !== "function") {
324
+ throw new Error(`Handler must export default function: ${uri}`);
325
+ }
326
+ this._handlers[route] = {
327
+ def,
328
+ handler: module.default
329
+ };
330
+ }
331
+ async _safeUpdateRegistration() {
332
+ invariant(this._functionServiceRegistration, void 0, {
333
+ F: __dxlog_file2,
334
+ L: 190,
335
+ S: this,
336
+ A: [
337
+ "this._functionServiceRegistration",
338
+ ""
339
+ ]
340
+ });
341
+ try {
342
+ await this._client.services.services.FunctionRegistryService.updateRegistration({
343
+ registrationId: this._functionServiceRegistration,
344
+ functions: this.functions.map(({ def: { id, route } }) => ({
345
+ id,
346
+ route
347
+ }))
348
+ });
349
+ } catch (err) {
350
+ log2.catch(err, void 0, {
351
+ F: __dxlog_file2,
352
+ L: 197,
353
+ S: this,
354
+ C: (f, a) => f(...a)
355
+ });
356
+ }
357
+ }
358
+ /**
359
+ * Invoke function.
360
+ */
361
+ async invoke(path2, data) {
362
+ const seq = ++this._seq;
363
+ const now = Date.now();
364
+ log2.info("req", {
365
+ seq,
366
+ path: path2
367
+ }, {
368
+ F: __dxlog_file2,
369
+ L: 208,
370
+ S: this,
371
+ C: (f, a) => f(...a)
372
+ });
373
+ const statusCode = await this._invoke(path2, {
374
+ data
375
+ });
376
+ log2.info("res", {
377
+ seq,
378
+ path: path2,
379
+ statusCode,
380
+ duration: Date.now() - now
381
+ }, {
382
+ F: __dxlog_file2,
383
+ L: 211,
384
+ S: this,
385
+ C: (f, a) => f(...a)
386
+ });
387
+ this.update.emit(statusCode);
388
+ return statusCode;
389
+ }
390
+ async _invoke(path2, event) {
391
+ const { handler } = this._handlers[path2] ?? {};
392
+ invariant(handler, `invalid path: ${path2}`, {
393
+ F: __dxlog_file2,
394
+ L: 218,
395
+ S: this,
396
+ A: [
397
+ "handler",
398
+ "`invalid path: ${path}`"
399
+ ]
400
+ });
401
+ const context = {
402
+ client: this._client,
403
+ dataDir: this._options.dataDir
404
+ };
405
+ let statusCode = 200;
406
+ const response = {
407
+ status: (code) => {
408
+ statusCode = code;
409
+ return response;
410
+ }
411
+ };
412
+ await handler({
413
+ context,
414
+ event,
415
+ response
416
+ });
417
+ return statusCode;
418
+ }
419
+ };
420
+ var createContext = () => new Context({
421
+ name: "DevServer"
422
+ });
423
+
424
+ // packages/core/functions/src/runtime/scheduler.ts
425
+ import path from "@dxos/node-std/path";
426
+ import { Mutex } from "@dxos/async";
427
+ import { Context as Context2 } from "@dxos/context";
428
+ import { log as log3 } from "@dxos/log";
429
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/scheduler.ts";
430
+ var Scheduler = class {
431
+ constructor(functions, triggers, _options = {}) {
432
+ this.functions = functions;
433
+ this.triggers = triggers;
434
+ this._options = _options;
435
+ this._ctx = createContext2();
436
+ this._functionUriToCallMutex = /* @__PURE__ */ new Map();
437
+ this.functions.registered.on(async ({ space, added }) => {
438
+ await this._safeActivateTriggers(space, this.triggers.getInactiveTriggers(space), added);
439
+ });
440
+ this.triggers.registered.on(async ({ space, triggers: triggers2 }) => {
441
+ await this._safeActivateTriggers(space, triggers2, this.functions.getFunctions(space));
442
+ });
443
+ }
444
+ async start() {
445
+ await this._ctx.dispose();
446
+ this._ctx = createContext2();
447
+ await this.functions.open(this._ctx);
448
+ await this.triggers.open(this._ctx);
449
+ }
450
+ async stop() {
451
+ await this._ctx.dispose();
452
+ await this.functions.close();
453
+ await this.triggers.close();
454
+ }
455
+ // TODO(burdon): Remove and update registries directly.
456
+ async register(space, manifest) {
457
+ await this.functions.register(space, manifest.functions);
458
+ await this.triggers.register(space, manifest);
459
+ }
460
+ async _safeActivateTriggers(space, triggers, functions) {
461
+ const mountTasks = triggers.map((trigger) => {
462
+ return this.activate(space, functions, trigger);
463
+ });
464
+ await Promise.all(mountTasks).catch(log3.catch);
465
+ }
466
+ async activate(space, functions, trigger) {
467
+ const definition = functions.find((def) => def.uri === trigger.function);
468
+ if (!definition) {
469
+ log3.info("function is not found for trigger", {
470
+ trigger
471
+ }, {
472
+ F: __dxlog_file3,
473
+ L: 78,
474
+ S: this,
475
+ C: (f, a) => f(...a)
476
+ });
477
+ return;
478
+ }
479
+ await this.triggers.activate(space, trigger, async (args) => {
480
+ const mutex = this._functionUriToCallMutex.get(definition.uri) ?? new Mutex();
481
+ this._functionUriToCallMutex.set(definition.uri, mutex);
482
+ log3.info("function triggered, waiting for mutex", {
483
+ uri: definition.uri
484
+ }, {
485
+ F: __dxlog_file3,
486
+ L: 86,
487
+ S: this,
488
+ C: (f, a) => f(...a)
489
+ });
490
+ return mutex.executeSynchronized(() => {
491
+ log3.info("mutex acquired", {
492
+ uri: definition.uri
493
+ }, {
494
+ F: __dxlog_file3,
495
+ L: 88,
496
+ S: this,
497
+ C: (f, a) => f(...a)
498
+ });
499
+ return this._execFunction(definition, trigger, {
500
+ meta: trigger.meta ?? {},
501
+ data: {
502
+ ...args,
503
+ spaceKey: space.key
504
+ }
505
+ });
506
+ });
507
+ });
508
+ log3("activated trigger", {
509
+ space: space.key,
510
+ trigger
511
+ }, {
512
+ F: __dxlog_file3,
513
+ L: 96,
514
+ S: this,
515
+ C: (f, a) => f(...a)
516
+ });
517
+ }
518
+ async _execFunction(def, trigger, { data, meta }) {
519
+ let status = 0;
520
+ try {
521
+ const payload = Object.assign({}, meta && {
522
+ meta
523
+ }, data);
524
+ const { endpoint, callback } = this._options;
525
+ if (endpoint) {
526
+ const url = path.join(endpoint, def.route);
527
+ log3.info("exec", {
528
+ function: def.uri,
529
+ url,
530
+ triggerType: trigger.spec.type
531
+ }, {
532
+ F: __dxlog_file3,
533
+ L: 113,
534
+ S: this,
535
+ C: (f, a) => f(...a)
536
+ });
537
+ const response = await fetch(url, {
538
+ method: "POST",
539
+ headers: {
540
+ "Content-Type": "application/json"
541
+ },
542
+ body: JSON.stringify(payload)
543
+ });
544
+ status = response.status;
545
+ } else if (callback) {
546
+ log3.info("exec", {
547
+ function: def.uri
548
+ }, {
549
+ F: __dxlog_file3,
550
+ L: 124,
551
+ S: this,
552
+ C: (f, a) => f(...a)
553
+ });
554
+ status = await callback(payload) ?? 200;
555
+ }
556
+ if (status && status >= 400) {
557
+ throw new Error(`Response: ${status}`);
558
+ }
559
+ log3.info("done", {
560
+ function: def.uri,
561
+ status
562
+ }, {
563
+ F: __dxlog_file3,
564
+ L: 134,
565
+ S: this,
566
+ C: (f, a) => f(...a)
567
+ });
568
+ } catch (err) {
569
+ log3.error("error", {
570
+ function: def.uri,
571
+ error: err.message
572
+ }, {
573
+ F: __dxlog_file3,
574
+ L: 136,
575
+ S: this,
576
+ C: (f, a) => f(...a)
577
+ });
578
+ status = 500;
579
+ }
580
+ return status;
581
+ }
582
+ };
583
+ var createContext2 = () => new Context2({
584
+ name: "FunctionScheduler"
585
+ });
586
+
587
+ // packages/core/functions/src/trigger/trigger-registry.ts
588
+ import { Event as Event3 } from "@dxos/async";
589
+ import { create as create2, Filter as Filter3, getMeta } from "@dxos/client/echo";
590
+ import { Context as Context3, Resource as Resource2 } from "@dxos/context";
591
+ import { compareForeignKeys, ECHO_ATTR_META, foreignKey } from "@dxos/echo-schema";
592
+ import { invariant as invariant2 } from "@dxos/invariant";
593
+ import { PublicKey as PublicKey2 } from "@dxos/keys";
594
+ import { log as log8 } from "@dxos/log";
595
+ import { ComplexMap as ComplexMap2, diff as diff2 } from "@dxos/util";
596
+
597
+ // packages/core/functions/src/trigger/type/subscription-trigger.ts
598
+ import { TextV0Type } from "@braneframe/types";
599
+ import { debounce, UpdateScheduler } from "@dxos/async";
600
+ import { Filter as Filter2 } from "@dxos/client/echo";
601
+ import { createSubscription, getAutomergeObjectCore } from "@dxos/echo-db";
602
+ import { log as log4 } from "@dxos/log";
603
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/subscription-trigger.ts";
604
+ var createSubscriptionTrigger = async (ctx, space, spec, callback) => {
605
+ const objectIds = /* @__PURE__ */ new Set();
606
+ const task = new UpdateScheduler(ctx, async () => {
607
+ if (objectIds.size > 0) {
608
+ const objects = Array.from(objectIds);
609
+ objectIds.clear();
610
+ await callback({
611
+ objects
612
+ });
613
+ }
614
+ }, {
615
+ maxFrequency: 4
616
+ });
617
+ const subscriptions = [];
618
+ const subscription = createSubscription(({ added, updated }) => {
619
+ const sizeBefore = objectIds.size;
620
+ for (const object of added) {
621
+ objectIds.add(object.id);
622
+ }
623
+ for (const object of updated) {
624
+ objectIds.add(object.id);
625
+ }
626
+ if (objectIds.size > sizeBefore) {
627
+ log4.info("updated", {
628
+ added: added.length,
629
+ updated: updated.length
630
+ }, {
631
+ F: __dxlog_file4,
632
+ L: 47,
633
+ S: void 0,
634
+ C: (f, a) => f(...a)
635
+ });
636
+ task.trigger();
637
+ }
638
+ });
639
+ subscriptions.push(() => subscription.unsubscribe());
640
+ const { filter, options: { deep, delay } = {} } = spec;
641
+ const update = ({ objects }) => {
642
+ log4.info("update", {
643
+ objects: objects.length
644
+ }, {
645
+ F: __dxlog_file4,
646
+ L: 57,
647
+ S: void 0,
648
+ C: (f, a) => f(...a)
649
+ });
650
+ subscription.update(objects);
651
+ if (deep) {
652
+ for (const object of objects) {
653
+ const content = object.content;
654
+ if (content instanceof TextV0Type) {
655
+ subscriptions.push(getAutomergeObjectCore(content).updates.on(debounce(() => subscription.update([
656
+ object
657
+ ]), 1e3)));
658
+ }
659
+ }
660
+ }
661
+ };
662
+ log4.info("subscription", {
663
+ filter
664
+ }, {
665
+ F: __dxlog_file4,
666
+ L: 76,
667
+ S: void 0,
668
+ C: (f, a) => f(...a)
669
+ });
670
+ if (filter) {
671
+ const query = space.db.query(Filter2.typename(filter[0].type, filter[0].props));
672
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
673
+ }
674
+ ctx.onDispose(() => {
675
+ subscriptions.forEach((unsubscribe) => unsubscribe());
676
+ });
677
+ };
678
+
679
+ // packages/core/functions/src/trigger/type/timer-trigger.ts
680
+ import { CronJob } from "cron";
681
+ import { DeferredTask } from "@dxos/async";
682
+ import { log as log5 } from "@dxos/log";
683
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/timer-trigger.ts";
684
+ var createTimerTrigger = async (ctx, space, spec, callback) => {
685
+ const task = new DeferredTask(ctx, async () => {
686
+ await callback({});
687
+ });
688
+ let last = 0;
689
+ let run = 0;
690
+ const job = CronJob.from({
691
+ cronTime: spec.cron,
692
+ runOnInit: false,
693
+ onTick: () => {
694
+ const now = Date.now();
695
+ const delta = last ? now - last : 0;
696
+ last = now;
697
+ run++;
698
+ log5.info("tick", {
699
+ space: space.key.truncate(),
700
+ count: run,
701
+ delta
702
+ }, {
703
+ F: __dxlog_file5,
704
+ L: 38,
705
+ S: void 0,
706
+ C: (f, a) => f(...a)
707
+ });
708
+ task.schedule();
709
+ }
710
+ });
711
+ job.start();
712
+ ctx.onDispose(() => job.stop());
713
+ };
714
+
715
+ // packages/core/functions/src/trigger/type/webhook-trigger.ts
716
+ import { getPort as getPort2 } from "get-port-please";
717
+ import http from "@dxos/node-std/http";
718
+ import { log as log6 } from "@dxos/log";
719
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/webhook-trigger.ts";
720
+ var createWebhookTrigger = async (ctx, space, spec, callback) => {
721
+ const server = http.createServer(async (req, res) => {
722
+ if (req.method !== spec.method) {
723
+ res.statusCode = 405;
724
+ return res.end();
725
+ }
726
+ res.statusCode = await callback({});
727
+ res.end();
728
+ });
729
+ const port = await getPort2({
730
+ random: true
731
+ });
732
+ server.listen(port, () => {
733
+ log6.info("started webhook", {
734
+ port
735
+ }, {
736
+ F: __dxlog_file6,
737
+ L: 41,
738
+ S: void 0,
739
+ C: (f, a) => f(...a)
740
+ });
741
+ spec.port = port;
742
+ });
743
+ ctx.onDispose(() => {
744
+ server.close();
745
+ });
746
+ };
747
+
748
+ // packages/core/functions/src/trigger/type/websocket-trigger.ts
749
+ import WebSocket from "ws";
750
+ import { sleep, Trigger as Trigger2 } from "@dxos/async";
751
+ import { log as log7 } from "@dxos/log";
752
+ var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/websocket-trigger.ts";
753
+ var createWebsocketTrigger = async (ctx, space, spec, callback, options = {
754
+ retryDelay: 2,
755
+ maxAttempts: 5
756
+ }) => {
757
+ const { url, init } = spec;
758
+ let wasOpen = false;
759
+ let ws;
760
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
761
+ const open = new Trigger2();
762
+ ws = new WebSocket(url);
763
+ Object.assign(ws, {
764
+ onopen: () => {
765
+ log7.info("opened", {
766
+ url
767
+ }, {
768
+ F: __dxlog_file7,
769
+ L: 41,
770
+ S: void 0,
771
+ C: (f, a) => f(...a)
772
+ });
773
+ if (spec.init) {
774
+ ws.send(new TextEncoder().encode(JSON.stringify(init)));
775
+ }
776
+ open.wake(true);
777
+ },
778
+ onclose: (event) => {
779
+ log7.info("closed", {
780
+ url,
781
+ code: event.code
782
+ }, {
783
+ F: __dxlog_file7,
784
+ L: 50,
785
+ S: void 0,
786
+ C: (f, a) => f(...a)
787
+ });
788
+ if (event.code === 1006 && wasOpen && !ctx.disposed) {
789
+ setTimeout(async () => {
790
+ log7.info(`reconnecting in ${options.retryDelay}s...`, {
791
+ url
792
+ }, {
793
+ F: __dxlog_file7,
794
+ L: 55,
795
+ S: void 0,
796
+ C: (f, a) => f(...a)
797
+ });
798
+ await createWebsocketTrigger(ctx, space, spec, callback, options);
799
+ }, options.retryDelay * 1e3);
800
+ }
801
+ open.wake(false);
802
+ },
803
+ onerror: (event) => {
804
+ log7.catch(event.error, {
805
+ url
806
+ }, {
807
+ F: __dxlog_file7,
808
+ L: 63,
809
+ S: void 0,
810
+ C: (f, a) => f(...a)
811
+ });
812
+ open.wake(false);
813
+ },
814
+ onmessage: async (event) => {
815
+ try {
816
+ log7.info("message", void 0, {
817
+ F: __dxlog_file7,
818
+ L: 69,
819
+ S: void 0,
820
+ C: (f, a) => f(...a)
821
+ });
822
+ const data = JSON.parse(new TextDecoder().decode(event.data));
823
+ await callback({
824
+ data
825
+ });
826
+ } catch (err) {
827
+ log7.catch(err, {
828
+ url
829
+ }, {
830
+ F: __dxlog_file7,
831
+ L: 73,
832
+ S: void 0,
833
+ C: (f, a) => f(...a)
834
+ });
835
+ }
836
+ }
837
+ });
838
+ const isOpen = await open.wait();
839
+ if (ctx.disposed) {
840
+ break;
841
+ }
842
+ if (isOpen) {
843
+ wasOpen = true;
844
+ break;
845
+ }
846
+ const wait = Math.pow(attempt, 2) * options.retryDelay;
847
+ if (attempt < options.maxAttempts) {
848
+ log7.warn(`failed to connect; trying again in ${wait}s`, {
849
+ attempt
850
+ }, {
851
+ F: __dxlog_file7,
852
+ L: 88,
853
+ S: void 0,
854
+ C: (f, a) => f(...a)
855
+ });
856
+ await sleep(wait * 1e3);
857
+ }
858
+ }
859
+ ctx.onDispose(() => {
860
+ ws?.close();
861
+ });
862
+ };
863
+
864
+ // packages/core/functions/src/trigger/trigger-registry.ts
865
+ var __dxlog_file8 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/trigger-registry.ts";
866
+ var triggerHandlers = {
867
+ subscription: createSubscriptionTrigger,
868
+ timer: createTimerTrigger,
869
+ webhook: createWebhookTrigger,
870
+ websocket: createWebsocketTrigger
871
+ };
872
+ var TriggerRegistry = class extends Resource2 {
873
+ constructor(_client, _options) {
874
+ super();
875
+ this._client = _client;
876
+ this._options = _options;
877
+ this._triggersBySpaceKey = new ComplexMap2(PublicKey2.hash);
878
+ this.registered = new Event3();
879
+ this.removed = new Event3();
880
+ }
881
+ getActiveTriggers(space) {
882
+ return this._getTriggers(space, (t) => t.activationCtx != null);
883
+ }
884
+ getInactiveTriggers(space) {
885
+ return this._getTriggers(space, (t) => t.activationCtx == null);
886
+ }
887
+ async activate(space, trigger, callback) {
888
+ log8("activate", {
889
+ space: space.key,
890
+ trigger
891
+ }, {
892
+ F: __dxlog_file8,
893
+ L: 72,
894
+ S: this,
895
+ C: (f, a) => f(...a)
896
+ });
897
+ const activationCtx = new Context3({
898
+ name: `FunctionTrigger-${trigger.function}`
899
+ });
900
+ this._ctx.onDispose(() => activationCtx.dispose());
901
+ const registeredTrigger = this._triggersBySpaceKey.get(space.key)?.find((reg) => reg.trigger.id === trigger.id);
902
+ invariant2(registeredTrigger, `Trigger is not registered: ${trigger.function}`, {
903
+ F: __dxlog_file8,
904
+ L: 77,
905
+ S: this,
906
+ A: [
907
+ "registeredTrigger",
908
+ "`Trigger is not registered: ${trigger.function}`"
909
+ ]
910
+ });
911
+ registeredTrigger.activationCtx = activationCtx;
912
+ try {
913
+ const options = this._options?.[trigger.spec.type];
914
+ await triggerHandlers[trigger.spec.type](activationCtx, space, trigger.spec, callback, options);
915
+ } catch (err) {
916
+ delete registeredTrigger.activationCtx;
917
+ throw err;
918
+ }
919
+ }
920
+ /**
921
+ * Loads triggers from the manifest into the space.
922
+ */
923
+ async register(space, manifest) {
924
+ log8("register", {
925
+ space: space.key
926
+ }, {
927
+ F: __dxlog_file8,
928
+ L: 93,
929
+ S: this,
930
+ C: (f, a) => f(...a)
931
+ });
932
+ if (!manifest.triggers?.length) {
933
+ return;
934
+ }
935
+ if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionTrigger)) {
936
+ space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionTrigger);
937
+ }
938
+ const manifestTriggers = manifest.triggers.map((trigger) => {
939
+ let keys = trigger[ECHO_ATTR_META]?.keys;
940
+ delete trigger[ECHO_ATTR_META];
941
+ if (!keys?.length) {
942
+ keys = [
943
+ foreignKey("manifest", [
944
+ trigger.function,
945
+ trigger.spec.type
946
+ ].join(":"))
947
+ ];
948
+ }
949
+ return create2(FunctionTrigger, trigger, {
950
+ keys
951
+ });
952
+ });
953
+ const { objects: existing } = await space.db.query(Filter3.schema(FunctionTrigger)).run();
954
+ const { added } = diff2(existing, manifestTriggers, compareForeignKeys);
955
+ added.forEach((trigger) => {
956
+ space.db.add(trigger);
957
+ log8.info("added", {
958
+ meta: getMeta(trigger)
959
+ }, {
960
+ F: __dxlog_file8,
961
+ L: 120,
962
+ S: this,
963
+ C: (f, a) => f(...a)
964
+ });
965
+ });
966
+ if (added.length > 0) {
967
+ await space.db.flush();
968
+ }
969
+ }
970
+ async _open() {
971
+ log8.info("open...", void 0, {
972
+ F: __dxlog_file8,
973
+ L: 129,
974
+ S: this,
975
+ C: (f, a) => f(...a)
976
+ });
977
+ const spaceListSubscription = this._client.spaces.subscribe(async (spaces) => {
978
+ for (const space of spaces) {
979
+ if (this._triggersBySpaceKey.has(space.key)) {
980
+ continue;
981
+ }
982
+ const registered = [];
983
+ this._triggersBySpaceKey.set(space.key, registered);
984
+ await space.waitUntilReady();
985
+ if (this._ctx.disposed) {
986
+ break;
987
+ }
988
+ this._ctx.onDispose(space.db.query(Filter3.schema(FunctionTrigger)).subscribe(async ({ objects: current }) => {
989
+ log8.info("update", {
990
+ space: space.key,
991
+ registered: registered.length,
992
+ current: current.length
993
+ }, {
994
+ F: __dxlog_file8,
995
+ L: 146,
996
+ S: this,
997
+ C: (f, a) => f(...a)
998
+ });
999
+ await this._handleRemovedTriggers(space, current, registered);
1000
+ this._handleNewTriggers(space, current, registered);
1001
+ }));
1002
+ }
1003
+ });
1004
+ this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
1005
+ log8.info("opened", void 0, {
1006
+ F: __dxlog_file8,
1007
+ L: 155,
1008
+ S: this,
1009
+ C: (f, a) => f(...a)
1010
+ });
1011
+ }
1012
+ async _close(_) {
1013
+ log8.info("close...", void 0, {
1014
+ F: __dxlog_file8,
1015
+ L: 159,
1016
+ S: this,
1017
+ C: (f, a) => f(...a)
1018
+ });
1019
+ this._triggersBySpaceKey.clear();
1020
+ log8.info("closed", void 0, {
1021
+ F: __dxlog_file8,
1022
+ L: 161,
1023
+ S: this,
1024
+ C: (f, a) => f(...a)
1025
+ });
1026
+ }
1027
+ _handleNewTriggers(space, current, registered) {
1028
+ const added = current.filter((candidate) => {
1029
+ return candidate.enabled && registered.find((reg) => reg.trigger.id === candidate.id) == null;
1030
+ });
1031
+ if (added.length > 0) {
1032
+ const newRegisteredTriggers = added.map((trigger) => ({
1033
+ trigger
1034
+ }));
1035
+ registered.push(...newRegisteredTriggers);
1036
+ log8.info("added", () => ({
1037
+ spaceKey: space.key,
1038
+ triggers: added.map((trigger) => trigger.function)
1039
+ }), {
1040
+ F: __dxlog_file8,
1041
+ L: 172,
1042
+ S: this,
1043
+ C: (f, a) => f(...a)
1044
+ });
1045
+ this.registered.emit({
1046
+ space,
1047
+ triggers: added
1048
+ });
1049
+ }
1050
+ }
1051
+ async _handleRemovedTriggers(space, current, registered) {
1052
+ const removed = [];
1053
+ for (let i = registered.length - 1; i >= 0; i--) {
1054
+ const wasRemoved = current.filter((trigger) => trigger.enabled).find((trigger) => trigger.id === registered[i].trigger.id) == null;
1055
+ if (wasRemoved) {
1056
+ const unregistered = registered.splice(i, 1)[0];
1057
+ await unregistered.activationCtx?.dispose();
1058
+ removed.push(unregistered.trigger);
1059
+ }
1060
+ }
1061
+ if (removed.length > 0) {
1062
+ log8.info("removed", () => ({
1063
+ spaceKey: space.key,
1064
+ triggers: removed.map((trigger) => trigger.function)
1065
+ }), {
1066
+ F: __dxlog_file8,
1067
+ L: 198,
1068
+ S: this,
1069
+ C: (f, a) => f(...a)
1070
+ });
1071
+ this.removed.emit({
1072
+ space,
1073
+ triggers: removed
1074
+ });
1075
+ }
1076
+ }
1077
+ _getTriggers(space, predicate) {
1078
+ const allSpaceTriggers = this._triggersBySpaceKey.get(space.key) ?? [];
1079
+ return allSpaceTriggers.filter(predicate).map((trigger) => trigger.trigger);
1080
+ }
1081
+ };
1082
+
1083
+ export {
1084
+ FunctionRegistry,
1085
+ DevServer,
1086
+ Scheduler,
1087
+ TriggerRegistry
1088
+ };
1089
+ //# sourceMappingURL=chunk-SXZ5DYJG.mjs.map