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