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

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