@dxos/functions 0.5.3-main.979c3c1 → 0.5.3-main.a09cd97

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