@dxos/functions 0.5.3-main.6f960f5 → 0.5.3-main.79e0565

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