@dxos/functions 0.5.3-main.83788ff → 0.5.3-main.8ffbbae

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 (79) hide show
  1. package/dist/lib/browser/index.mjs +479 -744
  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 +474 -725
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/index.d.ts +0 -2
  8. package/dist/types/src/index.d.ts.map +1 -1
  9. package/dist/types/src/runtime/dev-server.d.ts +10 -7
  10. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  11. package/dist/types/src/runtime/scheduler.d.ts +59 -11
  12. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  13. package/dist/types/src/testing/test/handler.d.ts +0 -1
  14. package/dist/types/src/testing/test/handler.d.ts.map +1 -1
  15. package/dist/types/src/types.d.ts +111 -131
  16. package/dist/types/src/types.d.ts.map +1 -1
  17. package/package.json +12 -14
  18. package/schema/functions.json +116 -139
  19. package/src/index.ts +0 -2
  20. package/src/runtime/dev-server.test.ts +35 -15
  21. package/src/runtime/dev-server.ts +19 -36
  22. package/src/runtime/scheduler.test.ts +75 -54
  23. package/src/runtime/scheduler.ts +298 -66
  24. package/src/testing/test/handler.ts +2 -8
  25. package/src/types.ts +42 -58
  26. package/dist/types/src/function/function-registry.d.ts +0 -24
  27. package/dist/types/src/function/function-registry.d.ts.map +0 -1
  28. package/dist/types/src/function/function-registry.test.d.ts +0 -2
  29. package/dist/types/src/function/function-registry.test.d.ts.map +0 -1
  30. package/dist/types/src/function/index.d.ts +0 -2
  31. package/dist/types/src/function/index.d.ts.map +0 -1
  32. package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
  33. package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
  34. package/dist/types/src/testing/index.d.ts +0 -4
  35. package/dist/types/src/testing/index.d.ts.map +0 -1
  36. package/dist/types/src/testing/setup.d.ts +0 -5
  37. package/dist/types/src/testing/setup.d.ts.map +0 -1
  38. package/dist/types/src/testing/types.d.ts +0 -9
  39. package/dist/types/src/testing/types.d.ts.map +0 -1
  40. package/dist/types/src/testing/util.d.ts +0 -3
  41. package/dist/types/src/testing/util.d.ts.map +0 -1
  42. package/dist/types/src/trigger/index.d.ts +0 -2
  43. package/dist/types/src/trigger/index.d.ts.map +0 -1
  44. package/dist/types/src/trigger/trigger-registry.d.ts +0 -40
  45. package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
  46. package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
  47. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
  48. package/dist/types/src/trigger/type/index.d.ts +0 -5
  49. package/dist/types/src/trigger/type/index.d.ts.map +0 -1
  50. package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
  51. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
  52. package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
  53. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
  54. package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
  55. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
  56. package/dist/types/src/trigger/type/websocket-trigger.d.ts +0 -13
  57. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +0 -1
  58. package/dist/types/src/util.d.ts +0 -15
  59. package/dist/types/src/util.d.ts.map +0 -1
  60. package/dist/types/src/util.test.d.ts +0 -2
  61. package/dist/types/src/util.test.d.ts.map +0 -1
  62. package/src/function/function-registry.test.ts +0 -105
  63. package/src/function/function-registry.ts +0 -90
  64. package/src/function/index.ts +0 -5
  65. package/src/testing/functions-integration.test.ts +0 -99
  66. package/src/testing/index.ts +0 -7
  67. package/src/testing/setup.ts +0 -45
  68. package/src/testing/types.ts +0 -9
  69. package/src/testing/util.ts +0 -16
  70. package/src/trigger/index.ts +0 -5
  71. package/src/trigger/trigger-registry.test.ts +0 -255
  72. package/src/trigger/trigger-registry.ts +0 -189
  73. package/src/trigger/type/index.ts +0 -8
  74. package/src/trigger/type/subscription-trigger.ts +0 -80
  75. package/src/trigger/type/timer-trigger.ts +0 -44
  76. package/src/trigger/type/webhook-trigger.ts +0 -47
  77. package/src/trigger/type/websocket-trigger.ts +0 -91
  78. package/src/util.test.ts +0 -43
  79. package/src/util.ts +0 -48
@@ -29,57 +29,33 @@ 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,
33
32
  FunctionManifestSchema: () => FunctionManifestSchema,
34
- FunctionRegistry: () => FunctionRegistry,
35
- FunctionTrigger: () => FunctionTrigger,
36
33
  Scheduler: () => Scheduler,
37
- TriggerRegistry: () => TriggerRegistry,
38
34
  subscriptionHandler: () => subscriptionHandler
39
35
  });
40
36
  module.exports = __toCommonJS(node_exports);
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");
37
+ var import_client = require("@dxos/client");
45
38
  var import_log = require("@dxos/log");
46
39
  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");
51
40
  var import_express = __toESM(require("express"));
52
41
  var import_get_port_please = require("get-port-please");
53
42
  var import_node_path = require("node:path");
54
- var import_async2 = require("@dxos/async");
55
- var import_context2 = require("@dxos/context");
43
+ var import_async = require("@dxos/async");
56
44
  var import_invariant = require("@dxos/invariant");
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");
45
+ var import_log2 = require("@dxos/log");
74
46
  var import_cron = require("cron");
75
- var import_async6 = require("@dxos/async");
76
- var import_log7 = require("@dxos/log");
77
47
  var import_get_port_please2 = require("get-port-please");
78
48
  var import_node_http = __toESM(require("node:http"));
79
- var import_log8 = require("@dxos/log");
49
+ var import_node_path2 = __toESM(require("node:path"));
80
50
  var import_ws = __toESM(require("ws"));
81
- var import_async7 = require("@dxos/async");
82
- var import_log9 = require("@dxos/log");
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"));
83
59
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
84
60
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
85
61
  }) : x)(function(x) {
@@ -87,169 +63,27 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
87
63
  return require.apply(this, arguments);
88
64
  throw Error('Dynamic require of "' + x + '" is not supported');
89
65
  });
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";
66
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/handler.ts";
233
67
  var subscriptionHandler = (handler) => {
234
68
  return ({ event: { data }, context, ...rest }) => {
235
69
  const { client } = context;
236
70
  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) : [];
71
+ const objects = space ? data.objects?.map((id) => space.db.getObjectById(id)).filter(import_util.nonNullable) : [];
238
72
  if (!!data.spaceKey && !space) {
239
- import_log2.log.warn("invalid space", {
73
+ import_log.log.warn("invalid space", {
240
74
  data
241
75
  }, {
242
- F: __dxlog_file2,
76
+ F: __dxlog_file,
243
77
  L: 91,
244
78
  S: void 0,
245
79
  C: (f, a) => f(...a)
246
80
  });
247
81
  } else {
248
- import_log2.log.info("handler", {
82
+ import_log.log.info("handler", {
249
83
  space: space?.key.truncate(),
250
84
  objects: objects?.length
251
85
  }, {
252
- F: __dxlog_file2,
86
+ F: __dxlog_file,
253
87
  L: 93,
254
88
  S: void 0,
255
89
  C: (f, a) => f(...a)
@@ -268,28 +102,15 @@ var subscriptionHandler = (handler) => {
268
102
  });
269
103
  };
270
104
  };
271
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
105
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
272
106
  var DevServer = class {
273
- constructor(_client, _functionsRegistry, _options) {
107
+ // prettier-ignore
108
+ constructor(_client, _options) {
274
109
  this._client = _client;
275
- this._functionsRegistry = _functionsRegistry;
276
110
  this._options = _options;
277
- this._ctx = createContext();
278
111
  this._handlers = {};
279
112
  this._seq = 0;
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
- });
113
+ this.update = new import_async.Event();
293
114
  }
294
115
  get stats() {
295
116
  return {
@@ -298,8 +119,8 @@ var DevServer = class {
298
119
  }
299
120
  get endpoint() {
300
121
  (0, import_invariant.invariant)(this._port, void 0, {
301
- F: __dxlog_file3,
302
- L: 63,
122
+ F: __dxlog_file2,
123
+ L: 54,
303
124
  S: this,
304
125
  A: [
305
126
  "this._port",
@@ -314,32 +135,45 @@ var DevServer = class {
314
135
  get functions() {
315
136
  return Object.values(this._handlers);
316
137
  }
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
+ }
317
152
  async start() {
318
153
  (0, import_invariant.invariant)(!this._server, void 0, {
319
- F: __dxlog_file3,
320
- L: 76,
154
+ F: __dxlog_file2,
155
+ L: 77,
321
156
  S: this,
322
157
  A: [
323
158
  "!this._server",
324
159
  ""
325
160
  ]
326
161
  });
327
- import_log3.log.info("starting...", void 0, {
328
- F: __dxlog_file3,
329
- L: 77,
162
+ import_log2.log.info("starting...", void 0, {
163
+ F: __dxlog_file2,
164
+ L: 78,
330
165
  S: this,
331
166
  C: (f, a) => f(...a)
332
167
  });
333
- this._ctx = createContext();
334
168
  const app = (0, import_express.default)();
335
169
  app.use(import_express.default.json());
336
170
  app.post("/:path", async (req, res) => {
337
171
  const { path: path2 } = req.params;
338
172
  try {
339
- import_log3.log.info("calling", {
173
+ import_log2.log.info("calling", {
340
174
  path: path2
341
175
  }, {
342
- F: __dxlog_file3,
176
+ F: __dxlog_file2,
343
177
  L: 87,
344
178
  S: this,
345
179
  C: (f, a) => f(...a)
@@ -351,8 +185,8 @@ var DevServer = class {
351
185
  res.statusCode = await this.invoke("/" + path2, req.body);
352
186
  res.end();
353
187
  } catch (err) {
354
- import_log3.log.catch(err, void 0, {
355
- F: __dxlog_file3,
188
+ import_log2.log.catch(err, void 0, {
189
+ F: __dxlog_file2,
356
190
  L: 97,
357
191
  S: this,
358
192
  C: (f, a) => f(...a)
@@ -372,61 +206,64 @@ var DevServer = class {
372
206
  this._server = app.listen(this._port);
373
207
  try {
374
208
  const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService.register({
375
- endpoint: this.endpoint
209
+ endpoint: this.endpoint,
210
+ functions: this.functions.map(({ def: { id, path: path2 } }) => ({
211
+ id,
212
+ path: path2
213
+ }))
376
214
  });
377
- import_log3.log.info("registered", {
215
+ import_log2.log.info("registered", {
378
216
  endpoint
379
217
  }, {
380
- F: __dxlog_file3,
381
- L: 112,
218
+ F: __dxlog_file2,
219
+ L: 113,
382
220
  S: this,
383
221
  C: (f, a) => f(...a)
384
222
  });
385
223
  this._proxy = endpoint;
386
224
  this._functionServiceRegistration = registrationId;
387
- await this._functionsRegistry.open(this._ctx);
388
225
  } catch (err) {
389
226
  await this.stop();
390
227
  throw new Error("FunctionRegistryService not available (check plugin is configured).");
391
228
  }
392
- import_log3.log.info("started", {
229
+ import_log2.log.info("started", {
393
230
  port: this._port
394
231
  }, {
395
- F: __dxlog_file3,
396
- L: 123,
232
+ F: __dxlog_file2,
233
+ L: 121,
397
234
  S: this,
398
235
  C: (f, a) => f(...a)
399
236
  });
400
237
  }
401
238
  async stop() {
402
239
  (0, import_invariant.invariant)(this._server, void 0, {
403
- F: __dxlog_file3,
404
- L: 127,
240
+ F: __dxlog_file2,
241
+ L: 125,
405
242
  S: this,
406
243
  A: [
407
244
  "this._server",
408
245
  ""
409
246
  ]
410
247
  });
411
- import_log3.log.info("stopping...", void 0, {
412
- F: __dxlog_file3,
413
- L: 128,
248
+ import_log2.log.info("stopping...", void 0, {
249
+ F: __dxlog_file2,
250
+ L: 126,
414
251
  S: this,
415
252
  C: (f, a) => f(...a)
416
253
  });
417
- const trigger = new import_async2.Trigger();
254
+ const trigger = new import_async.Trigger();
418
255
  this._server.close(async () => {
419
- import_log3.log.info("server stopped", void 0, {
420
- F: __dxlog_file3,
421
- L: 132,
256
+ import_log2.log.info("server stopped", void 0, {
257
+ F: __dxlog_file2,
258
+ L: 130,
422
259
  S: this,
423
260
  C: (f, a) => f(...a)
424
261
  });
425
262
  try {
426
263
  if (this._functionServiceRegistration) {
427
264
  (0, import_invariant.invariant)(this._client.services.services.FunctionRegistryService, void 0, {
428
- F: __dxlog_file3,
429
- L: 135,
265
+ F: __dxlog_file2,
266
+ L: 133,
430
267
  S: this,
431
268
  A: [
432
269
  "this._client.services.services.FunctionRegistryService",
@@ -436,11 +273,11 @@ var DevServer = class {
436
273
  await this._client.services.services.FunctionRegistryService.unregister({
437
274
  registrationId: this._functionServiceRegistration
438
275
  });
439
- import_log3.log.info("unregistered", {
276
+ import_log2.log.info("unregistered", {
440
277
  registrationId: this._functionServiceRegistration
441
278
  }, {
442
- F: __dxlog_file3,
443
- L: 140,
279
+ F: __dxlog_file2,
280
+ L: 138,
444
281
  S: this,
445
282
  C: (f, a) => f(...a)
446
283
  });
@@ -455,9 +292,9 @@ var DevServer = class {
455
292
  await trigger.wait();
456
293
  this._port = void 0;
457
294
  this._server = void 0;
458
- import_log3.log.info("stopped", void 0, {
459
- F: __dxlog_file3,
460
- L: 154,
295
+ import_log2.log.info("stopped", void 0, {
296
+ F: __dxlog_file2,
297
+ L: 152,
461
298
  S: this,
462
299
  C: (f, a) => f(...a)
463
300
  });
@@ -466,14 +303,14 @@ var DevServer = class {
466
303
  * Load function.
467
304
  */
468
305
  async _load(def, force = false) {
469
- const { uri, route, handler } = def;
306
+ const { id, path: path2, handler } = def;
470
307
  const filePath = (0, import_node_path.join)(this._options.baseDir, handler);
471
- import_log3.log.info("loading", {
472
- uri,
308
+ import_log2.log.info("loading", {
309
+ id,
473
310
  force
474
311
  }, {
475
- F: __dxlog_file3,
476
- L: 163,
312
+ F: __dxlog_file2,
313
+ L: 161,
477
314
  S: this,
478
315
  C: (f, a) => f(...a)
479
316
  });
@@ -484,66 +321,39 @@ var DevServer = class {
484
321
  }
485
322
  const module2 = __require(filePath);
486
323
  if (typeof module2.default !== "function") {
487
- throw new Error(`Handler must export default function: ${uri}`);
324
+ throw new Error(`Handler must export default function: ${id}`);
488
325
  }
489
- this._handlers[route] = {
326
+ this._handlers[path2] = {
490
327
  def,
491
328
  handler: module2.default
492
329
  };
493
330
  }
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
- }
521
331
  /**
522
332
  * Invoke function.
523
333
  */
524
334
  async invoke(path2, data) {
525
335
  const seq = ++this._seq;
526
336
  const now = Date.now();
527
- import_log3.log.info("req", {
337
+ import_log2.log.info("req", {
528
338
  seq,
529
339
  path: path2
530
340
  }, {
531
- F: __dxlog_file3,
532
- L: 203,
341
+ F: __dxlog_file2,
342
+ L: 188,
533
343
  S: this,
534
344
  C: (f, a) => f(...a)
535
345
  });
536
346
  const statusCode = await this._invoke(path2, {
537
347
  data
538
348
  });
539
- import_log3.log.info("res", {
349
+ import_log2.log.info("res", {
540
350
  seq,
541
351
  path: path2,
542
352
  statusCode,
543
353
  duration: Date.now() - now
544
354
  }, {
545
- F: __dxlog_file3,
546
- L: 206,
355
+ F: __dxlog_file2,
356
+ L: 191,
547
357
  S: this,
548
358
  C: (f, a) => f(...a)
549
359
  });
@@ -553,8 +363,8 @@ var DevServer = class {
553
363
  async _invoke(path2, event) {
554
364
  const { handler } = this._handlers[path2] ?? {};
555
365
  (0, import_invariant.invariant)(handler, `invalid path: ${path2}`, {
556
- F: __dxlog_file3,
557
- L: 213,
366
+ F: __dxlog_file2,
367
+ L: 198,
558
368
  S: this,
559
369
  A: [
560
370
  "handler",
@@ -580,98 +390,111 @@ var DevServer = class {
580
390
  return statusCode;
581
391
  }
582
392
  };
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";
393
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/scheduler.ts";
587
394
  var Scheduler = class {
588
- constructor(functions, triggers, _options = {}) {
589
- this.functions = functions;
590
- this.triggers = triggers;
395
+ constructor(_client, _manifest, _options = {}) {
396
+ this._client = _client;
397
+ this._manifest = _manifest;
591
398
  this._options = _options;
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
- });
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
+ }, []);
600
406
  }
601
407
  async start() {
602
- await this._ctx.dispose();
603
- this._ctx = createContext2();
604
- await this.functions.open(this._ctx);
605
- await this.triggers.open(this._ctx);
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
+ });
606
416
  }
607
417
  async stop() {
608
- await this._ctx.dispose();
609
- await this.functions.close();
610
- await this.triggers.close();
611
- }
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);
418
+ for (const { id, spaceKey } of this._mounts.keys()) {
419
+ await this.unmount(id, spaceKey);
420
+ }
616
421
  }
617
- async _safeActivateTriggers(space, triggers, functions) {
618
- const mountTasks = triggers.map((trigger) => {
619
- return this.activate(space, functions, trigger);
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
+ ]
620
439
  });
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
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
628
449
  }, {
629
- F: __dxlog_file4,
630
- L: 78,
450
+ F: __dxlog_file3,
451
+ L: 82,
631
452
  S: this,
632
453
  C: (f, a) => f(...a)
633
454
  });
634
- return;
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();
635
481
  }
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
- });
658
482
  }
659
- async _execFunction(def, trigger, { data, meta }) {
483
+ async _execFunction(def, trigger, data) {
660
484
  let status = 0;
661
485
  try {
662
- const payload = Object.assign({}, meta && {
663
- meta
486
+ const payload = Object.assign({}, {
487
+ meta: trigger.meta
664
488
  }, data);
665
489
  const { endpoint, callback } = this._options;
666
490
  if (endpoint) {
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
491
+ const url = import_node_path2.default.join(endpoint, def.path);
492
+ import_log3.log.info("exec", {
493
+ function: def.id,
494
+ url
672
495
  }, {
673
- F: __dxlog_file4,
674
- L: 108,
496
+ F: __dxlog_file3,
497
+ L: 128,
675
498
  S: this,
676
499
  C: (f, a) => f(...a)
677
500
  });
@@ -684,11 +507,11 @@ var Scheduler = class {
684
507
  });
685
508
  status = response.status;
686
509
  } else if (callback) {
687
- import_log4.log.info("exec", {
688
- function: def.uri
510
+ import_log3.log.info("exec", {
511
+ function: def.id
689
512
  }, {
690
- F: __dxlog_file4,
691
- L: 119,
513
+ F: __dxlog_file3,
514
+ L: 139,
692
515
  S: this,
693
516
  C: (f, a) => f(...a)
694
517
  });
@@ -697,22 +520,22 @@ var Scheduler = class {
697
520
  if (status && status >= 400) {
698
521
  throw new Error(`Response: ${status}`);
699
522
  }
700
- import_log4.log.info("done", {
701
- function: def.uri,
523
+ import_log3.log.info("done", {
524
+ function: def.id,
702
525
  status
703
526
  }, {
704
- F: __dxlog_file4,
705
- L: 129,
527
+ F: __dxlog_file3,
528
+ L: 149,
706
529
  S: this,
707
530
  C: (f, a) => f(...a)
708
531
  });
709
532
  } catch (err) {
710
- import_log4.log.error("error", {
711
- function: def.uri,
533
+ import_log3.log.error("error", {
534
+ function: def.id,
712
535
  error: err.message
713
536
  }, {
714
- F: __dxlog_file4,
715
- L: 131,
537
+ F: __dxlog_file3,
538
+ L: 151,
716
539
  S: this,
717
540
  C: (f, a) => f(...a)
718
541
  });
@@ -720,408 +543,334 @@ var Scheduler = class {
720
543
  }
721
544
  return status;
722
545
  }
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
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
839
556
  }, {
840
- F: __dxlog_file7,
841
- L: 40,
842
- S: void 0,
557
+ F: __dxlog_file3,
558
+ L: 166,
559
+ S: this,
843
560
  C: (f, a) => f(...a)
844
561
  });
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
865
- }, {
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,
908
- C: (f, a) => f(...a)
909
- });
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
- }
933
- }
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
+ });
934
567
  });
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
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
943
582
  }, {
944
- F: __dxlog_file8,
945
- L: 82,
946
- S: void 0,
583
+ F: __dxlog_file3,
584
+ L: 186,
585
+ S: this,
947
586
  C: (f, a) => f(...a)
948
587
  });
949
- await (0, import_async7.sleep)(wait * 1e3);
588
+ task.schedule();
950
589
  }
951
- }
952
- }
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);
590
+ });
591
+ job.start();
592
+ ctx.onDispose(() => job.stop());
978
593
  }
979
- async activate(triggerCtx, trigger, callback) {
980
- (0, import_log5.log)("activate", {
981
- space: triggerCtx.space.key,
594
+ /**
595
+ * Webhook.
596
+ */
597
+ async _createWebhook(ctx, space, def, trigger) {
598
+ import_log3.log.info("webhook", {
599
+ space: space.key,
982
600
  trigger
983
601
  }, {
984
- F: __dxlog_file9,
985
- L: 75,
602
+ F: __dxlog_file3,
603
+ L: 199,
986
604
  S: this,
987
605
  C: (f, a) => f(...a)
988
606
  });
989
- const activationCtx = new import_context4.Context({
990
- name: `trigger_${trigger.function}`
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();
991
617
  });
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
- ]
618
+ const port = await (0, import_get_port_please2.getPort)({
619
+ random: true
620
+ });
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();
1002
634
  });
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
- }
1011
635
  }
1012
636
  /**
1013
- * Loads triggers from the manifest into the space.
637
+ * Websocket.
638
+ * NOTE: The port must be unique, so the same hook cannot be used for multiple spaces.
1014
639
  */
1015
- async register(space, manifest) {
1016
- (0, import_log5.log)("register", {
1017
- space: space.key
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
1018
647
  }, {
1019
- F: __dxlog_file9,
1020
- L: 97,
648
+ F: __dxlog_file3,
649
+ L: 249,
1021
650
  S: this,
1022
651
  C: (f, a) => f(...a)
1023
652
  });
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;
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
+ }
1051
726
  }
1052
- const registered = [];
1053
- this._triggersBySpaceKey.set(space.key, registered);
1054
- await space.waitUntilReady();
1055
- if (this._ctx.disposed) {
1056
- break;
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
736
+ }, {
737
+ F: __dxlog_file3,
738
+ L: 302,
739
+ S: this,
740
+ C: (f, a) => f(...a)
741
+ });
742
+ await (0, import_async2.sleep)(wait * 1e3);
1057
743
  }
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);
1063
744
  }
745
+ }
746
+ ctx.onDispose(() => {
747
+ ws?.close();
1064
748
  });
1065
- this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
1066
- }
1067
- async _close(_) {
1068
- this._triggersBySpaceKey.clear();
1069
749
  }
1070
- _handleNewTriggers(space, allTriggers, registered) {
1071
- const newTriggers = allTriggers.filter((candidate) => {
1072
- return registered.find((reg) => reg.trigger.id === candidate.id) == null;
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,
760
+ S: this,
761
+ C: (f, a) => f(...a)
1073
762
  });
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", () => ({
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, {
1080
767
  spaceKey: space.key,
1081
- functions: newTriggers.map((t) => t.function)
1082
- }), {
1083
- F: __dxlog_file9,
1084
- L: 159,
768
+ objects: Array.from(objectIds)
769
+ });
770
+ });
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,
1085
779
  S: this,
1086
780
  C: (f, a) => f(...a)
1087
781
  });
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);
782
+ for (const object of added) {
783
+ objectIds.add(object.id);
1102
784
  }
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);
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
+ }
810
+ }
811
+ }
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
+ });
1114
818
  }
1115
819
  };
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
+ });
1116
869
  // Annotate the CommonJS export names for ESM import in node:
1117
870
  0 && (module.exports = {
1118
871
  DevServer,
1119
- FunctionDef,
1120
872
  FunctionManifestSchema,
1121
- FunctionRegistry,
1122
- FunctionTrigger,
1123
873
  Scheduler,
1124
- TriggerRegistry,
1125
874
  subscriptionHandler
1126
875
  });
1127
876
  //# sourceMappingURL=index.cjs.map