@dxos/functions 0.5.3-main.979c3c1 → 0.5.3-main.a09cd97

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