@dxos/functions 0.5.3-main.2c59258 → 0.5.3-main.3456876

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 +422 -663
  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 +419 -648
  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 +12 -32
  8. package/dist/types/src/handler.d.ts.map +1 -1
  9. package/dist/types/src/index.d.ts +0 -2
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/runtime/dev-server.d.ts +10 -7
  12. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  13. package/dist/types/src/runtime/scheduler.d.ts +59 -10
  14. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  15. package/dist/types/src/testing/test/handler.d.ts +0 -1
  16. package/dist/types/src/testing/test/handler.d.ts.map +1 -1
  17. package/dist/types/src/types.d.ts +112 -118
  18. package/dist/types/src/types.d.ts.map +1 -1
  19. package/package.json +13 -16
  20. package/schema/functions.json +103 -122
  21. package/src/handler.ts +27 -50
  22. package/src/index.ts +0 -2
  23. package/src/runtime/dev-server.test.ts +35 -15
  24. package/src/runtime/dev-server.ts +22 -40
  25. package/src/runtime/scheduler.test.ts +75 -54
  26. package/src/runtime/scheduler.ts +300 -67
  27. package/src/testing/test/handler.ts +2 -8
  28. package/src/types.ts +40 -56
  29. package/dist/types/src/registry/function-registry.d.ts +0 -24
  30. package/dist/types/src/registry/function-registry.d.ts.map +0 -1
  31. package/dist/types/src/registry/function-registry.test.d.ts +0 -2
  32. package/dist/types/src/registry/function-registry.test.d.ts.map +0 -1
  33. package/dist/types/src/registry/index.d.ts +0 -2
  34. package/dist/types/src/registry/index.d.ts.map +0 -1
  35. package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
  36. package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
  37. package/dist/types/src/testing/index.d.ts +0 -4
  38. package/dist/types/src/testing/index.d.ts.map +0 -1
  39. package/dist/types/src/testing/setup.d.ts +0 -5
  40. package/dist/types/src/testing/setup.d.ts.map +0 -1
  41. package/dist/types/src/testing/types.d.ts +0 -9
  42. package/dist/types/src/testing/types.d.ts.map +0 -1
  43. package/dist/types/src/testing/util.d.ts +0 -3
  44. package/dist/types/src/testing/util.d.ts.map +0 -1
  45. package/dist/types/src/trigger/index.d.ts +0 -2
  46. package/dist/types/src/trigger/index.d.ts.map +0 -1
  47. package/dist/types/src/trigger/trigger-registry.d.ts +0 -40
  48. package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
  49. package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
  50. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
  51. package/dist/types/src/trigger/type/index.d.ts +0 -5
  52. package/dist/types/src/trigger/type/index.d.ts.map +0 -1
  53. package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
  54. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
  55. package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
  56. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
  57. package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
  58. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
  59. package/dist/types/src/trigger/type/websocket-trigger.d.ts +0 -13
  60. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +0 -1
  61. package/src/registry/function-registry.test.ts +0 -105
  62. package/src/registry/function-registry.ts +0 -84
  63. package/src/registry/index.ts +0 -5
  64. package/src/testing/functions-integration.test.ts +0 -99
  65. package/src/testing/index.ts +0 -7
  66. package/src/testing/setup.ts +0 -45
  67. package/src/testing/types.ts +0 -9
  68. package/src/testing/util.ts +0 -16
  69. package/src/trigger/index.ts +0 -5
  70. package/src/trigger/trigger-registry.test.ts +0 -229
  71. package/src/trigger/trigger-registry.ts +0 -176
  72. package/src/trigger/type/index.ts +0 -8
  73. package/src/trigger/type/subscription-trigger.ts +0 -73
  74. package/src/trigger/type/timer-trigger.ts +0 -44
  75. package/src/trigger/type/webhook-trigger.ts +0 -47
  76. package/src/trigger/type/websocket-trigger.ts +0 -91
@@ -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: { data }, context, ...rest }) => {
23
+ return ({ event, context, ...rest }) => {
24
24
  const { client } = context;
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) {
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) {
28
28
  log.warn("invalid space", {
29
- data
29
+ event
30
30
  }, {
31
31
  F: __dxlog_file,
32
- L: 91,
32
+ L: 68,
33
33
  S: void 0,
34
34
  C: (f, a) => f(...a)
35
35
  });
@@ -39,18 +39,15 @@ var subscriptionHandler = (handler) => {
39
39
  objects: objects?.length
40
40
  }, {
41
41
  F: __dxlog_file,
42
- L: 93,
42
+ L: 70,
43
43
  S: void 0,
44
44
  C: (f, a) => f(...a)
45
45
  });
46
46
  }
47
47
  return handler({
48
48
  event: {
49
- data: {
50
- ...data,
51
- space,
52
- objects
53
- }
49
+ space,
50
+ objects
54
51
  },
55
52
  context,
56
53
  ...rest
@@ -58,170 +55,22 @@ var subscriptionHandler = (handler) => {
58
55
  };
59
56
  };
60
57
 
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
-
194
58
  // packages/core/functions/src/runtime/dev-server.ts
195
59
  import express from "express";
196
60
  import { getPort } from "get-port-please";
197
61
  import { join } from "@dxos/node-std/path";
198
- import { Event as Event2, Trigger } from "@dxos/async";
199
- import { Context } from "@dxos/context";
62
+ import { Event, Trigger } from "@dxos/async";
200
63
  import { invariant } from "@dxos/invariant";
201
64
  import { log as log2 } from "@dxos/log";
202
65
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
203
66
  var DevServer = class {
204
67
  // prettier-ignore
205
- constructor(_client, _functionsRegistry, _options) {
68
+ constructor(_client, _options) {
206
69
  this._client = _client;
207
- this._functionsRegistry = _functionsRegistry;
208
70
  this._options = _options;
209
- this._ctx = createContext();
210
71
  this._handlers = {};
211
72
  this._seq = 0;
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
- });
73
+ this.update = new Event();
225
74
  }
226
75
  get stats() {
227
76
  return {
@@ -231,7 +80,7 @@ var DevServer = class {
231
80
  get endpoint() {
232
81
  invariant(this._port, void 0, {
233
82
  F: __dxlog_file2,
234
- L: 64,
83
+ L: 54,
235
84
  S: this,
236
85
  A: [
237
86
  "this._port",
@@ -246,6 +95,20 @@ var DevServer = class {
246
95
  get functions() {
247
96
  return Object.values(this._handlers);
248
97
  }
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
+ }
249
112
  async start() {
250
113
  invariant(!this._server, void 0, {
251
114
  F: __dxlog_file2,
@@ -262,7 +125,6 @@ var DevServer = class {
262
125
  S: this,
263
126
  C: (f, a) => f(...a)
264
127
  });
265
- this._ctx = createContext();
266
128
  const app = express();
267
129
  app.use(express.json());
268
130
  app.post("/:path", async (req, res) => {
@@ -272,7 +134,7 @@ var DevServer = class {
272
134
  path: path2
273
135
  }, {
274
136
  F: __dxlog_file2,
275
- L: 88,
137
+ L: 87,
276
138
  S: this,
277
139
  C: (f, a) => f(...a)
278
140
  });
@@ -285,7 +147,7 @@ var DevServer = class {
285
147
  } catch (err) {
286
148
  log2.catch(err, void 0, {
287
149
  F: __dxlog_file2,
288
- L: 98,
150
+ L: 97,
289
151
  S: this,
290
152
  C: (f, a) => f(...a)
291
153
  });
@@ -304,7 +166,11 @@ var DevServer = class {
304
166
  this._server = app.listen(this._port);
305
167
  try {
306
168
  const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService.register({
307
- endpoint: this.endpoint
169
+ endpoint: this.endpoint,
170
+ functions: this.functions.map(({ def: { id, path: path2 } }) => ({
171
+ id,
172
+ path: path2
173
+ }))
308
174
  });
309
175
  log2.info("registered", {
310
176
  endpoint
@@ -316,7 +182,6 @@ var DevServer = class {
316
182
  });
317
183
  this._proxy = endpoint;
318
184
  this._functionServiceRegistration = registrationId;
319
- await this._functionsRegistry.open(this._ctx);
320
185
  } catch (err) {
321
186
  await this.stop();
322
187
  throw new Error("FunctionRegistryService not available (check plugin is configured).");
@@ -325,7 +190,7 @@ var DevServer = class {
325
190
  port: this._port
326
191
  }, {
327
192
  F: __dxlog_file2,
328
- L: 124,
193
+ L: 121,
329
194
  S: this,
330
195
  C: (f, a) => f(...a)
331
196
  });
@@ -333,7 +198,7 @@ var DevServer = class {
333
198
  async stop() {
334
199
  invariant(this._server, void 0, {
335
200
  F: __dxlog_file2,
336
- L: 128,
201
+ L: 125,
337
202
  S: this,
338
203
  A: [
339
204
  "this._server",
@@ -342,7 +207,7 @@ var DevServer = class {
342
207
  });
343
208
  log2.info("stopping...", void 0, {
344
209
  F: __dxlog_file2,
345
- L: 129,
210
+ L: 126,
346
211
  S: this,
347
212
  C: (f, a) => f(...a)
348
213
  });
@@ -350,7 +215,7 @@ var DevServer = class {
350
215
  this._server.close(async () => {
351
216
  log2.info("server stopped", void 0, {
352
217
  F: __dxlog_file2,
353
- L: 133,
218
+ L: 130,
354
219
  S: this,
355
220
  C: (f, a) => f(...a)
356
221
  });
@@ -358,7 +223,7 @@ var DevServer = class {
358
223
  if (this._functionServiceRegistration) {
359
224
  invariant(this._client.services.services.FunctionRegistryService, void 0, {
360
225
  F: __dxlog_file2,
361
- L: 136,
226
+ L: 133,
362
227
  S: this,
363
228
  A: [
364
229
  "this._client.services.services.FunctionRegistryService",
@@ -372,7 +237,7 @@ var DevServer = class {
372
237
  registrationId: this._functionServiceRegistration
373
238
  }, {
374
239
  F: __dxlog_file2,
375
- L: 141,
240
+ L: 138,
376
241
  S: this,
377
242
  C: (f, a) => f(...a)
378
243
  });
@@ -389,7 +254,7 @@ var DevServer = class {
389
254
  this._server = void 0;
390
255
  log2.info("stopped", void 0, {
391
256
  F: __dxlog_file2,
392
- L: 155,
257
+ L: 152,
393
258
  S: this,
394
259
  C: (f, a) => f(...a)
395
260
  });
@@ -398,14 +263,14 @@ var DevServer = class {
398
263
  * Load function.
399
264
  */
400
265
  async _load(def, force = false) {
401
- const { uri, route, handler } = def;
266
+ const { id, path: path2, handler } = def;
402
267
  const filePath = join(this._options.baseDir, handler);
403
268
  log2.info("loading", {
404
- uri,
269
+ id,
405
270
  force
406
271
  }, {
407
272
  F: __dxlog_file2,
408
- L: 164,
273
+ L: 161,
409
274
  S: this,
410
275
  C: (f, a) => f(...a)
411
276
  });
@@ -416,40 +281,13 @@ var DevServer = class {
416
281
  }
417
282
  const module = __require(filePath);
418
283
  if (typeof module.default !== "function") {
419
- throw new Error(`Handler must export default function: ${uri}`);
284
+ throw new Error(`Handler must export default function: ${id}`);
420
285
  }
421
- this._handlers[route] = {
286
+ this._handlers[path2] = {
422
287
  def,
423
288
  handler: module.default
424
289
  };
425
290
  }
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
- }
453
291
  /**
454
292
  * Invoke function.
455
293
  */
@@ -461,13 +299,11 @@ var DevServer = class {
461
299
  path: path2
462
300
  }, {
463
301
  F: __dxlog_file2,
464
- L: 204,
302
+ L: 188,
465
303
  S: this,
466
304
  C: (f, a) => f(...a)
467
305
  });
468
- const statusCode = await this._invoke(path2, {
469
- data
470
- });
306
+ const statusCode = await this._invoke(path2, data);
471
307
  log2.info("res", {
472
308
  seq,
473
309
  path: path2,
@@ -475,7 +311,7 @@ var DevServer = class {
475
311
  duration: Date.now() - now
476
312
  }, {
477
313
  F: __dxlog_file2,
478
- L: 207,
314
+ L: 191,
479
315
  S: this,
480
316
  C: (f, a) => f(...a)
481
317
  });
@@ -486,7 +322,7 @@ var DevServer = class {
486
322
  const { handler } = this._handlers[path2] ?? {};
487
323
  invariant(handler, `invalid path: ${path2}`, {
488
324
  F: __dxlog_file2,
489
- L: 214,
325
+ L: 198,
490
326
  S: this,
491
327
  A: [
492
328
  "handler",
@@ -512,98 +348,123 @@ var DevServer = class {
512
348
  return statusCode;
513
349
  }
514
350
  };
515
- var createContext = () => new Context({
516
- name: "DevServer"
517
- });
518
351
 
519
352
  // 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";
520
356
  import path from "@dxos/node-std/path";
521
- import { Context as Context2 } from "@dxos/context";
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";
522
363
  import { log as log3 } from "@dxos/log";
364
+ import { ComplexMap } from "@dxos/util";
523
365
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/scheduler.ts";
524
366
  var Scheduler = class {
525
- constructor(functions, triggers, _options = {}) {
526
- this.functions = functions;
527
- this.triggers = triggers;
367
+ constructor(_client, _manifest, _options = {}) {
368
+ this._client = _client;
369
+ this._manifest = _manifest;
528
370
  this._options = _options;
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
- });
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
+ }, []);
536
378
  }
537
379
  async start() {
538
- await this._ctx.dispose();
539
- this._ctx = createContext2();
540
- await this.functions.open(this._ctx);
541
- await this.triggers.open(this._ctx);
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
+ });
542
388
  }
543
389
  async stop() {
544
- await this._ctx.dispose();
545
- await this.functions.close();
546
- await this.triggers.close();
547
- }
548
- async register(space, manifest) {
549
- await this.functions.register(space, manifest);
550
- await this.triggers.register(space, manifest);
390
+ for (const { id, spaceKey } of this._mounts.keys()) {
391
+ await this.unmount(id, spaceKey);
392
+ }
551
393
  }
552
- async _safeActivateTriggers(space, triggers, functions) {
553
- const mountTasks = triggers.map((trigger) => {
554
- return this.activate(space, functions, trigger);
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
+ ]
555
411
  });
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
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
563
421
  }, {
564
422
  F: __dxlog_file3,
565
- L: 74,
423
+ L: 89,
566
424
  S: this,
567
425
  C: (f, a) => f(...a)
568
426
  });
569
- return;
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();
570
453
  }
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
- });
591
454
  }
592
- async _execFunction(def, { data, meta }) {
593
- let status = 0;
455
+ // TODO(burdon): Pass in Space key (common context).
456
+ async _execFunction(def, data) {
594
457
  try {
595
- const payload = Object.assign({}, meta && {
596
- meta
597
- }, data);
458
+ let status = 0;
598
459
  const { endpoint, callback } = this._options;
599
460
  if (endpoint) {
600
- const url = path.join(endpoint, def.route);
461
+ const url = path.join(endpoint, def.path);
601
462
  log3.info("exec", {
602
- function: def.uri,
463
+ function: def.id,
603
464
  url
604
465
  }, {
605
466
  F: __dxlog_file3,
606
- L: 100,
467
+ L: 133,
607
468
  S: this,
608
469
  C: (f, a) => f(...a)
609
470
  });
@@ -612,462 +473,360 @@ var Scheduler = class {
612
473
  headers: {
613
474
  "Content-Type": "application/json"
614
475
  },
615
- body: JSON.stringify(payload)
476
+ body: JSON.stringify(data)
616
477
  });
617
478
  status = response.status;
618
479
  } else if (callback) {
619
480
  log3.info("exec", {
620
- function: def.uri
481
+ function: def.id
621
482
  }, {
622
483
  F: __dxlog_file3,
623
- L: 111,
484
+ L: 144,
624
485
  S: this,
625
486
  C: (f, a) => f(...a)
626
487
  });
627
- status = await callback(payload) ?? 200;
488
+ status = await callback(data) ?? 200;
628
489
  }
629
490
  if (status && status >= 400) {
630
491
  throw new Error(`Response: ${status}`);
631
492
  }
632
493
  log3.info("done", {
633
- function: def.uri,
494
+ function: def.id,
634
495
  status
635
496
  }, {
636
497
  F: __dxlog_file3,
637
- L: 121,
498
+ L: 154,
638
499
  S: this,
639
500
  C: (f, a) => f(...a)
640
501
  });
502
+ return status;
641
503
  } catch (err) {
642
504
  log3.error("error", {
643
- function: def.uri,
505
+ function: def.id,
644
506
  error: err.message
645
507
  }, {
646
508
  F: __dxlog_file3,
647
- L: 123,
509
+ L: 157,
648
510
  S: this,
649
511
  C: (f, a) => f(...a)
650
512
  });
651
- status = 500;
513
+ return 500;
652
514
  }
653
- return status;
654
515
  }
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
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
690
526
  }, {
691
- F: __dxlog_file4,
692
- L: 32,
693
- S: void 0,
527
+ F: __dxlog_file3,
528
+ L: 170,
529
+ S: this,
694
530
  C: (f, a) => f(...a)
695
531
  });
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)
532
+ const { cron } = trigger;
533
+ const task = new DeferredTask(ctx, async () => {
534
+ await this._execFunction(def, {
535
+ spaceKey: space.key
762
536
  });
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)
795
537
  });
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
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
821
552
  }, {
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,
840
- C: (f, a) => f(...a)
841
- });
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,
553
+ F: __dxlog_file3,
554
+ L: 190,
555
+ S: this,
864
556
  C: (f, a) => f(...a)
865
557
  });
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
- }
558
+ task.schedule();
889
559
  }
890
560
  });
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
- }
908
- }
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);
561
+ job.start();
562
+ ctx.onDispose(() => job.stop());
933
563
  }
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,
564
+ /**
565
+ * Webhook.
566
+ */
567
+ async _createWebhook(ctx, space, def, trigger) {
568
+ log3.info("webhook", {
569
+ space: space.key,
940
570
  trigger
941
571
  }, {
942
- F: __dxlog_file8,
943
- L: 73,
572
+ F: __dxlog_file3,
573
+ L: 203,
944
574
  S: this,
945
575
  C: (f, a) => f(...a)
946
576
  });
947
- const activationCtx = new Context3({
948
- name: `trigger_${trigger.function}`
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();
949
586
  });
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
- ]
587
+ const port = await getPort2({
588
+ random: true
589
+ });
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();
960
603
  });
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
- }
969
604
  }
970
605
  /**
971
- * Loads triggers from the manifest into the space.
606
+ * Websocket.
607
+ * NOTE: The port must be unique, so the same hook cannot be used for multiple spaces.
972
608
  */
973
- async register(space, manifest) {
974
- log8("register", {
975
- space: space.key
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
976
616
  }, {
977
- F: __dxlog_file8,
978
- L: 95,
617
+ F: __dxlog_file3,
618
+ L: 252,
979
619
  S: this,
980
620
  C: (f, a) => f(...a)
981
621
  });
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;
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
+ }
998
682
  }
999
- const registered = [];
1000
- this._triggersBySpaceKey.set(space.key, registered);
1001
- await space.waitUntilReady();
1002
- if (this._ctx.disposed) {
1003
- break;
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);
1004
699
  }
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);
1010
700
  }
701
+ }
702
+ ctx.onDispose(() => {
703
+ ws?.close();
1011
704
  });
1012
- this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
1013
- }
1014
- async _close(_) {
1015
- this._triggersBySpaceKey.clear();
1016
705
  }
1017
- _handleNewTriggers(space, allTriggers, registered) {
1018
- const newTriggers = allTriggers.filter((candidate) => {
1019
- return registered.find((reg) => reg.trigger.id === candidate.id) == null;
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)
1020
718
  });
1021
- if (newTriggers.length > 0) {
1022
- const newRegisteredTriggers = newTriggers.map((trigger) => ({
1023
- trigger
1024
- }));
1025
- registered.push(...newRegisteredTriggers);
1026
- log8("registered new triggers", () => ({
719
+ const objectIds = /* @__PURE__ */ new Set();
720
+ const task = new DeferredTask(ctx, async () => {
721
+ await this._execFunction(def, {
1027
722
  spaceKey: space.key,
1028
- functions: newTriggers.map((t) => t.function)
1029
- }), {
1030
- F: __dxlog_file8,
1031
- L: 146,
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,
1032
734
  S: this,
1033
735
  C: (f, a) => f(...a)
1034
736
  });
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);
737
+ for (const object of added) {
738
+ objectIds.add(object.id);
1049
739
  }
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);
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
+ }
766
+ }
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
+ });
1061
773
  }
1062
774
  };
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
+ });
1063
826
  export {
1064
827
  DevServer,
1065
- FunctionDef,
1066
828
  FunctionManifestSchema,
1067
- FunctionRegistry,
1068
- FunctionTrigger,
1069
829
  Scheduler,
1070
- TriggerRegistry,
1071
830
  subscriptionHandler
1072
831
  };
1073
832
  //# sourceMappingURL=index.mjs.map