@dxos/functions 0.5.3-main.bc67fdb → 0.5.3-main.c8ad1bb

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