@dxos/functions 0.5.3-main.6621d8f → 0.5.3-main.6f2dfea

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