@dxos/functions 0.5.3-main.bc67fdb → 0.5.3-main.bfb5bca

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