@dxos/functions 0.5.3-main.3456876 → 0.5.3-main.37bbd91

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