@dxos/functions 0.5.4-next.70d721e → 0.5.4

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