@dxos/functions 0.5.3-main.f752aaa → 0.5.3-main.f9b873d

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