@dxos/functions 0.5.3-main.25e4f82 → 0.5.3-main.2ad0afc

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