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