@dxos/functions 0.5.3-main.c3feabc → 0.5.3-main.cb47aab

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