@dxos/functions 0.5.3-main.a3ea5f1 → 0.5.3-main.a7bd8e4

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 (87) hide show
  1. package/dist/lib/browser/index.mjs +493 -692
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +486 -675
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/index.d.ts +0 -2
  8. package/dist/types/src/index.d.ts.map +1 -1
  9. package/dist/types/src/runtime/dev-server.d.ts +10 -7
  10. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  11. package/dist/types/src/runtime/scheduler.d.ts +59 -11
  12. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  13. package/dist/types/src/testing/test/handler.d.ts +0 -1
  14. package/dist/types/src/testing/test/handler.d.ts.map +1 -1
  15. package/dist/types/src/types.d.ts +111 -131
  16. package/dist/types/src/types.d.ts.map +1 -1
  17. package/package.json +14 -30
  18. package/schema/functions.json +116 -139
  19. package/src/index.ts +0 -2
  20. package/src/runtime/dev-server.test.ts +35 -15
  21. package/src/runtime/dev-server.ts +20 -37
  22. package/src/runtime/scheduler.test.ts +75 -54
  23. package/src/runtime/scheduler.ts +298 -66
  24. package/src/testing/test/handler.ts +2 -8
  25. package/src/types.ts +42 -58
  26. package/dist/lib/browser/chunk-366QG6IX.mjs +0 -81
  27. package/dist/lib/browser/chunk-366QG6IX.mjs.map +0 -7
  28. package/dist/lib/browser/types.mjs +0 -12
  29. package/dist/lib/browser/types.mjs.map +0 -7
  30. package/dist/lib/node/chunk-3VSJ57ZZ.cjs +0 -97
  31. package/dist/lib/node/chunk-3VSJ57ZZ.cjs.map +0 -7
  32. package/dist/lib/node/types.cjs +0 -33
  33. package/dist/lib/node/types.cjs.map +0 -7
  34. package/dist/types/src/function/function-registry.d.ts +0 -24
  35. package/dist/types/src/function/function-registry.d.ts.map +0 -1
  36. package/dist/types/src/function/function-registry.test.d.ts +0 -2
  37. package/dist/types/src/function/function-registry.test.d.ts.map +0 -1
  38. package/dist/types/src/function/index.d.ts +0 -2
  39. package/dist/types/src/function/index.d.ts.map +0 -1
  40. package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
  41. package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
  42. package/dist/types/src/testing/index.d.ts +0 -4
  43. package/dist/types/src/testing/index.d.ts.map +0 -1
  44. package/dist/types/src/testing/setup.d.ts +0 -5
  45. package/dist/types/src/testing/setup.d.ts.map +0 -1
  46. package/dist/types/src/testing/types.d.ts +0 -9
  47. package/dist/types/src/testing/types.d.ts.map +0 -1
  48. package/dist/types/src/testing/util.d.ts +0 -3
  49. package/dist/types/src/testing/util.d.ts.map +0 -1
  50. package/dist/types/src/trigger/index.d.ts +0 -2
  51. package/dist/types/src/trigger/index.d.ts.map +0 -1
  52. package/dist/types/src/trigger/trigger-registry.d.ts +0 -40
  53. package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
  54. package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
  55. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
  56. package/dist/types/src/trigger/type/index.d.ts +0 -5
  57. package/dist/types/src/trigger/type/index.d.ts.map +0 -1
  58. package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
  59. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
  60. package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
  61. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
  62. package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
  63. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
  64. package/dist/types/src/trigger/type/websocket-trigger.d.ts +0 -13
  65. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +0 -1
  66. package/dist/types/src/util.d.ts +0 -15
  67. package/dist/types/src/util.d.ts.map +0 -1
  68. package/dist/types/src/util.test.d.ts +0 -2
  69. package/dist/types/src/util.test.d.ts.map +0 -1
  70. package/src/function/function-registry.test.ts +0 -105
  71. package/src/function/function-registry.ts +0 -90
  72. package/src/function/index.ts +0 -5
  73. package/src/testing/functions-integration.test.ts +0 -99
  74. package/src/testing/index.ts +0 -7
  75. package/src/testing/setup.ts +0 -45
  76. package/src/testing/types.ts +0 -9
  77. package/src/testing/util.ts +0 -16
  78. package/src/trigger/index.ts +0 -5
  79. package/src/trigger/trigger-registry.test.ts +0 -255
  80. package/src/trigger/trigger-registry.ts +0 -189
  81. package/src/trigger/type/index.ts +0 -8
  82. package/src/trigger/type/subscription-trigger.ts +0 -80
  83. package/src/trigger/type/timer-trigger.ts +0 -44
  84. package/src/trigger/type/webhook-trigger.ts +0 -47
  85. package/src/trigger/type/websocket-trigger.ts +0 -91
  86. package/src/util.test.ts +0 -43
  87. package/src/util.ts +0 -48
@@ -1,135 +1,44 @@
1
1
  import "@dxos/node-std/globals";
2
- import {
3
- FunctionDef,
4
- FunctionManifestSchema,
5
- FunctionTrigger,
6
- __require
7
- } from "./chunk-366QG6IX.mjs";
8
-
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";
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);
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
+ });
39
9
 
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
- };
10
+ // inject-globals:@inject-globals
11
+ import {
12
+ global,
13
+ Buffer as Buffer2,
14
+ process
15
+ } from "@dxos/node-std/inject-globals";
107
16
 
108
17
  // packages/core/functions/src/handler.ts
109
- import { PublicKey as PublicKey2 } from "@dxos/client";
110
- import { log as log2 } from "@dxos/log";
18
+ import { PublicKey } from "@dxos/client";
19
+ import { log } from "@dxos/log";
111
20
  import { nonNullable } from "@dxos/util";
112
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/handler.ts";
21
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/handler.ts";
113
22
  var subscriptionHandler = (handler) => {
114
23
  return ({ event: { data }, context, ...rest }) => {
115
24
  const { client } = context;
116
- const space = data.spaceKey ? client.spaces.get(PublicKey2.from(data.spaceKey)) : void 0;
25
+ const space = data.spaceKey ? client.spaces.get(PublicKey.from(data.spaceKey)) : void 0;
117
26
  const objects = space ? data.objects?.map((id) => space.db.getObjectById(id)).filter(nonNullable) : [];
118
27
  if (!!data.spaceKey && !space) {
119
- log2.warn("invalid space", {
28
+ log.warn("invalid space", {
120
29
  data
121
30
  }, {
122
- F: __dxlog_file2,
31
+ F: __dxlog_file,
123
32
  L: 91,
124
33
  S: void 0,
125
34
  C: (f, a) => f(...a)
126
35
  });
127
36
  } else {
128
- log2.info("handler", {
37
+ log.info("handler", {
129
38
  space: space?.key.truncate(),
130
39
  objects: objects?.length
131
40
  }, {
132
- F: __dxlog_file2,
41
+ F: __dxlog_file,
133
42
  L: 93,
134
43
  S: void 0,
135
44
  C: (f, a) => f(...a)
@@ -153,32 +62,18 @@ var subscriptionHandler = (handler) => {
153
62
  import express from "express";
154
63
  import { getPort } from "get-port-please";
155
64
  import { join } from "@dxos/node-std/path";
156
- import { Event as Event2, Trigger } from "@dxos/async";
157
- import { Context } from "@dxos/context";
65
+ import { Event, Trigger } from "@dxos/async";
158
66
  import { invariant } from "@dxos/invariant";
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";
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";
161
69
  var DevServer = class {
162
- constructor(_client, _functionsRegistry, _options) {
70
+ // prettier-ignore
71
+ constructor(_client, _options) {
163
72
  this._client = _client;
164
- this._functionsRegistry = _functionsRegistry;
165
73
  this._options = _options;
166
- this._ctx = createContext();
167
74
  this._handlers = {};
168
75
  this._seq = 0;
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
- });
76
+ this.update = new Event();
182
77
  }
183
78
  get stats() {
184
79
  return {
@@ -187,8 +82,8 @@ var DevServer = class {
187
82
  }
188
83
  get endpoint() {
189
84
  invariant(this._port, void 0, {
190
- F: __dxlog_file3,
191
- L: 63,
85
+ F: __dxlog_file2,
86
+ L: 54,
192
87
  S: this,
193
88
  A: [
194
89
  "this._port",
@@ -203,32 +98,45 @@ var DevServer = class {
203
98
  get functions() {
204
99
  return Object.values(this._handlers);
205
100
  }
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
+ }
206
115
  async start() {
207
116
  invariant(!this._server, void 0, {
208
- F: __dxlog_file3,
209
- L: 76,
117
+ F: __dxlog_file2,
118
+ L: 77,
210
119
  S: this,
211
120
  A: [
212
121
  "!this._server",
213
122
  ""
214
123
  ]
215
124
  });
216
- log3.info("starting...", void 0, {
217
- F: __dxlog_file3,
218
- L: 77,
125
+ log2.info("starting...", void 0, {
126
+ F: __dxlog_file2,
127
+ L: 78,
219
128
  S: this,
220
129
  C: (f, a) => f(...a)
221
130
  });
222
- this._ctx = createContext();
223
131
  const app = express();
224
132
  app.use(express.json());
225
133
  app.post("/:path", async (req, res) => {
226
134
  const { path: path2 } = req.params;
227
135
  try {
228
- log3.info("calling", {
136
+ log2.info("calling", {
229
137
  path: path2
230
138
  }, {
231
- F: __dxlog_file3,
139
+ F: __dxlog_file2,
232
140
  L: 87,
233
141
  S: this,
234
142
  C: (f, a) => f(...a)
@@ -240,8 +148,8 @@ var DevServer = class {
240
148
  res.statusCode = await this.invoke("/" + path2, req.body);
241
149
  res.end();
242
150
  } catch (err) {
243
- log3.catch(err, void 0, {
244
- F: __dxlog_file3,
151
+ log2.catch(err, void 0, {
152
+ F: __dxlog_file2,
245
153
  L: 97,
246
154
  S: this,
247
155
  C: (f, a) => f(...a)
@@ -261,61 +169,64 @@ var DevServer = class {
261
169
  this._server = app.listen(this._port);
262
170
  try {
263
171
  const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService.register({
264
- endpoint: this.endpoint
172
+ endpoint: this.endpoint,
173
+ functions: this.functions.map(({ def: { id, path: path2 } }) => ({
174
+ id,
175
+ path: path2
176
+ }))
265
177
  });
266
- log3.info("registered", {
178
+ log2.info("registered", {
267
179
  endpoint
268
180
  }, {
269
- F: __dxlog_file3,
270
- L: 112,
181
+ F: __dxlog_file2,
182
+ L: 113,
271
183
  S: this,
272
184
  C: (f, a) => f(...a)
273
185
  });
274
186
  this._proxy = endpoint;
275
187
  this._functionServiceRegistration = registrationId;
276
- await this._functionsRegistry.open(this._ctx);
277
188
  } catch (err) {
278
189
  await this.stop();
279
190
  throw new Error("FunctionRegistryService not available (check plugin is configured).");
280
191
  }
281
- log3.info("started", {
192
+ log2.info("started", {
282
193
  port: this._port
283
194
  }, {
284
- F: __dxlog_file3,
285
- L: 123,
195
+ F: __dxlog_file2,
196
+ L: 121,
286
197
  S: this,
287
198
  C: (f, a) => f(...a)
288
199
  });
289
200
  }
290
201
  async stop() {
291
202
  invariant(this._server, void 0, {
292
- F: __dxlog_file3,
293
- L: 127,
203
+ F: __dxlog_file2,
204
+ L: 125,
294
205
  S: this,
295
206
  A: [
296
207
  "this._server",
297
208
  ""
298
209
  ]
299
210
  });
300
- log3.info("stopping...", void 0, {
301
- F: __dxlog_file3,
302
- L: 128,
211
+ log2.info("stopping...", void 0, {
212
+ F: __dxlog_file2,
213
+ L: 126,
303
214
  S: this,
304
215
  C: (f, a) => f(...a)
305
216
  });
306
217
  const trigger = new Trigger();
307
218
  this._server.close(async () => {
308
- log3.info("server stopped", void 0, {
309
- F: __dxlog_file3,
310
- L: 132,
219
+ log2.info("server stopped", void 0, {
220
+ F: __dxlog_file2,
221
+ L: 130,
311
222
  S: this,
312
223
  C: (f, a) => f(...a)
313
224
  });
314
225
  try {
315
226
  if (this._functionServiceRegistration) {
316
227
  invariant(this._client.services.services.FunctionRegistryService, void 0, {
317
- F: __dxlog_file3,
318
- L: 135,
228
+ F: __dxlog_file2,
229
+ L: 133,
319
230
  S: this,
320
231
  A: [
321
232
  "this._client.services.services.FunctionRegistryService",
@@ -325,11 +236,11 @@ var DevServer = class {
325
236
  await this._client.services.services.FunctionRegistryService.unregister({
326
237
  registrationId: this._functionServiceRegistration
327
238
  });
328
- log3.info("unregistered", {
239
+ log2.info("unregistered", {
329
240
  registrationId: this._functionServiceRegistration
330
241
  }, {
331
- F: __dxlog_file3,
332
- L: 140,
242
+ F: __dxlog_file2,
243
+ L: 138,
333
244
  S: this,
334
245
  C: (f, a) => f(...a)
335
246
  });
@@ -344,9 +255,9 @@ var DevServer = class {
344
255
  await trigger.wait();
345
256
  this._port = void 0;
346
257
  this._server = void 0;
347
- log3.info("stopped", void 0, {
348
- F: __dxlog_file3,
349
- L: 154,
258
+ log2.info("stopped", void 0, {
259
+ F: __dxlog_file2,
260
+ L: 152,
350
261
  S: this,
351
262
  C: (f, a) => f(...a)
352
263
  });
@@ -354,15 +265,15 @@ var DevServer = class {
354
265
  /**
355
266
  * Load function.
356
267
  */
357
- async _load(def, force) {
358
- const { uri, route, handler } = def;
268
+ async _load(def, force = false) {
269
+ const { id, path: path2, handler } = def;
359
270
  const filePath = join(this._options.baseDir, handler);
360
- log3.info("loading", {
361
- uri,
271
+ log2.info("loading", {
272
+ id,
362
273
  force
363
274
  }, {
364
- F: __dxlog_file3,
365
- L: 163,
275
+ F: __dxlog_file2,
276
+ L: 161,
366
277
  S: this,
367
278
  C: (f, a) => f(...a)
368
279
  });
@@ -373,66 +284,39 @@ var DevServer = class {
373
284
  }
374
285
  const module = __require(filePath);
375
286
  if (typeof module.default !== "function") {
376
- throw new Error(`Handler must export default function: ${uri}`);
287
+ throw new Error(`Handler must export default function: ${id}`);
377
288
  }
378
- this._handlers[route] = {
289
+ this._handlers[path2] = {
379
290
  def,
380
291
  handler: module.default
381
292
  };
382
293
  }
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
- }
410
294
  /**
411
295
  * Invoke function.
412
296
  */
413
297
  async invoke(path2, data) {
414
298
  const seq = ++this._seq;
415
299
  const now = Date.now();
416
- log3.info("req", {
300
+ log2.info("req", {
417
301
  seq,
418
302
  path: path2
419
303
  }, {
420
- F: __dxlog_file3,
421
- L: 203,
304
+ F: __dxlog_file2,
305
+ L: 188,
422
306
  S: this,
423
307
  C: (f, a) => f(...a)
424
308
  });
425
309
  const statusCode = await this._invoke(path2, {
426
310
  data
427
311
  });
428
- log3.info("res", {
312
+ log2.info("res", {
429
313
  seq,
430
314
  path: path2,
431
315
  statusCode,
432
316
  duration: Date.now() - now
433
317
  }, {
434
- F: __dxlog_file3,
435
- L: 206,
318
+ F: __dxlog_file2,
319
+ L: 191,
436
320
  S: this,
437
321
  C: (f, a) => f(...a)
438
322
  });
@@ -442,8 +326,8 @@ var DevServer = class {
442
326
  async _invoke(path2, event) {
443
327
  const { handler } = this._handlers[path2] ?? {};
444
328
  invariant(handler, `invalid path: ${path2}`, {
445
- F: __dxlog_file3,
446
- L: 213,
329
+ F: __dxlog_file2,
330
+ L: 198,
447
331
  S: this,
448
332
  A: [
449
333
  "handler",
@@ -469,104 +353,125 @@ var DevServer = class {
469
353
  return statusCode;
470
354
  }
471
355
  };
472
- var createContext = () => new Context({
473
- name: "DevServer"
474
- });
475
356
 
476
357
  // 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";
477
361
  import path from "@dxos/node-std/path";
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";
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";
482
371
  var Scheduler = class {
483
- constructor(functions, triggers, _options = {}) {
484
- this.functions = functions;
485
- this.triggers = triggers;
372
+ constructor(_client, _manifest, _options = {}) {
373
+ this._client = _client;
374
+ this._manifest = _manifest;
486
375
  this._options = _options;
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
- });
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
+ }, []);
495
383
  }
496
384
  async start() {
497
- await this._ctx.dispose();
498
- this._ctx = createContext2();
499
- await this.functions.open(this._ctx);
500
- await this.triggers.open(this._ctx);
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
+ });
501
393
  }
502
394
  async stop() {
503
- await this._ctx.dispose();
504
- await this.functions.close();
505
- await this.triggers.close();
506
- }
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);
395
+ for (const { id, spaceKey } of this._mounts.keys()) {
396
+ await this.unmount(id, spaceKey);
397
+ }
511
398
  }
512
- async _safeActivateTriggers(space, triggers, functions) {
513
- const mountTasks = triggers.map((trigger) => {
514
- return this.activate(space, functions, trigger);
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
+ ]
515
416
  });
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
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,
425
+ trigger
523
426
  }, {
524
- F: __dxlog_file4,
525
- L: 78,
427
+ F: __dxlog_file3,
428
+ L: 82,
526
429
  S: this,
527
430
  C: (f, a) => f(...a)
528
431
  });
529
- return;
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
+ }
530
447
  }
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
- });
553
448
  }
554
- async _execFunction(def, trigger, { data, meta }) {
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();
458
+ }
459
+ }
460
+ async _execFunction(def, trigger, data) {
555
461
  let status = 0;
556
462
  try {
557
- const payload = Object.assign({}, meta && {
558
- meta
463
+ const payload = Object.assign({}, {
464
+ meta: trigger.meta
559
465
  }, data);
560
466
  const { endpoint, callback } = this._options;
561
467
  if (endpoint) {
562
- const url = path.join(endpoint, def.route);
563
- log4.info("exec", {
564
- function: def.uri,
565
- url,
566
- triggerType: trigger.spec.type
468
+ const url = path.join(endpoint, def.path);
469
+ log3.info("exec", {
470
+ function: def.id,
471
+ url
567
472
  }, {
568
- F: __dxlog_file4,
569
- L: 108,
473
+ F: __dxlog_file3,
474
+ L: 128,
570
475
  S: this,
571
476
  C: (f, a) => f(...a)
572
477
  });
@@ -579,11 +484,11 @@ var Scheduler = class {
579
484
  });
580
485
  status = response.status;
581
486
  } else if (callback) {
582
- log4.info("exec", {
583
- function: def.uri
487
+ log3.info("exec", {
488
+ function: def.id
584
489
  }, {
585
- F: __dxlog_file4,
586
- L: 119,
490
+ F: __dxlog_file3,
491
+ L: 139,
587
492
  S: this,
588
493
  C: (f, a) => f(...a)
589
494
  });
@@ -592,22 +497,22 @@ var Scheduler = class {
592
497
  if (status && status >= 400) {
593
498
  throw new Error(`Response: ${status}`);
594
499
  }
595
- log4.info("done", {
596
- function: def.uri,
500
+ log3.info("done", {
501
+ function: def.id,
597
502
  status
598
503
  }, {
599
- F: __dxlog_file4,
600
- L: 129,
504
+ F: __dxlog_file3,
505
+ L: 149,
601
506
  S: this,
602
507
  C: (f, a) => f(...a)
603
508
  });
604
509
  } catch (err) {
605
- log4.error("error", {
606
- function: def.uri,
510
+ log3.error("error", {
511
+ function: def.id,
607
512
  error: err.message
608
513
  }, {
609
- F: __dxlog_file4,
610
- L: 131,
514
+ F: __dxlog_file3,
515
+ L: 151,
611
516
  S: this,
612
517
  C: (f, a) => f(...a)
613
518
  });
@@ -615,440 +520,336 @@ var Scheduler = class {
615
520
  }
616
521
  return status;
617
522
  }
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
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
760
533
  }, {
761
- F: __dxlog_file7,
762
- L: 40,
763
- S: void 0,
534
+ F: __dxlog_file3,
535
+ L: 166,
536
+ S: this,
764
537
  C: (f, a) => f(...a)
765
538
  });
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
791
- }, {
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,
834
- C: (f, a) => f(...a)
835
- });
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
- }
859
- }
539
+ const spec = trigger.timer;
540
+ const task = new DeferredTask(ctx, async () => {
541
+ await this._execFunction(def, trigger, {
542
+ spaceKey: space.key
543
+ });
860
544
  });
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
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
869
559
  }, {
870
- F: __dxlog_file8,
871
- L: 82,
872
- S: void 0,
560
+ F: __dxlog_file3,
561
+ L: 186,
562
+ S: this,
873
563
  C: (f, a) => f(...a)
874
564
  });
875
- await sleep(wait * 1e3);
565
+ task.schedule();
876
566
  }
877
- }
878
- }
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);
567
+ });
568
+ job.start();
569
+ ctx.onDispose(() => job.stop());
906
570
  }
907
- async activate(triggerCtx, trigger, callback) {
908
- log9("activate", {
909
- space: triggerCtx.space.key,
571
+ /**
572
+ * Webhook.
573
+ */
574
+ async _createWebhook(ctx, space, def, trigger) {
575
+ log3.info("webhook", {
576
+ space: space.key,
910
577
  trigger
911
578
  }, {
912
- F: __dxlog_file9,
913
- L: 75,
579
+ F: __dxlog_file3,
580
+ L: 199,
914
581
  S: this,
915
582
  C: (f, a) => f(...a)
916
583
  });
917
- const activationCtx = new Context3({
918
- name: `trigger_${trigger.function}`
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();
589
+ }
590
+ res.statusCode = await this._execFunction(def, trigger, {
591
+ spaceKey: space.key
592
+ });
593
+ res.end();
919
594
  });
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
- ]
595
+ const port = await getPort2({
596
+ random: true
597
+ });
598
+ server.listen(port, () => {
599
+ log3.info("started webhook", {
600
+ port
601
+ }, {
602
+ F: __dxlog_file3,
603
+ L: 223,
604
+ S: this,
605
+ C: (f, a) => f(...a)
606
+ });
607
+ spec.port = port;
608
+ });
609
+ ctx.onDispose(() => {
610
+ server.close();
930
611
  });
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
- }
939
612
  }
940
613
  /**
941
- * Loads triggers from the manifest into the space.
614
+ * Websocket.
615
+ * NOTE: The port must be unique, so the same hook cannot be used for multiple spaces.
942
616
  */
943
- async register(space, manifest) {
944
- log9("register", {
945
- space: space.key
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
946
624
  }, {
947
- F: __dxlog_file9,
948
- L: 97,
625
+ F: __dxlog_file3,
626
+ L: 249,
949
627
  S: this,
950
628
  C: (f, a) => f(...a)
951
629
  });
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;
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
+ }
979
703
  }
980
- const registered = [];
981
- this._triggersBySpaceKey.set(space.key, registered);
982
- await space.waitUntilReady();
983
- if (this._ctx.disposed) {
984
- break;
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
713
+ }, {
714
+ F: __dxlog_file3,
715
+ L: 302,
716
+ S: this,
717
+ C: (f, a) => f(...a)
718
+ });
719
+ await sleep(wait * 1e3);
985
720
  }
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);
991
721
  }
722
+ }
723
+ ctx.onDispose(() => {
724
+ ws?.close();
992
725
  });
993
- this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
994
- }
995
- async _close(_) {
996
- this._triggersBySpaceKey.clear();
997
726
  }
998
- _handleNewTriggers(space, allTriggers, registered) {
999
- const newTriggers = allTriggers.filter((candidate) => {
1000
- return registered.find((reg) => reg.trigger.id === candidate.id) == null;
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,
737
+ S: this,
738
+ C: (f, a) => f(...a)
1001
739
  });
1002
- if (newTriggers.length > 0) {
1003
- const newRegisteredTriggers = newTriggers.map((trigger) => ({
1004
- trigger
1005
- }));
1006
- registered.push(...newRegisteredTriggers);
1007
- log9("registered new triggers", () => ({
740
+ const spec = trigger.subscription;
741
+ const objectIds = /* @__PURE__ */ new Set();
742
+ const task = new DeferredTask(ctx, async () => {
743
+ await this._execFunction(def, trigger, {
1008
744
  spaceKey: space.key,
1009
- functions: newTriggers.map((t) => t.function)
1010
- }), {
1011
- F: __dxlog_file9,
1012
- L: 159,
745
+ objects: Array.from(objectIds)
746
+ });
747
+ });
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,
1013
756
  S: this,
1014
757
  C: (f, a) => f(...a)
1015
758
  });
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);
759
+ for (const object of added) {
760
+ objectIds.add(object.id);
1030
761
  }
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);
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
+ }
788
+ }
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
+ });
1042
795
  }
1043
796
  };
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
+ });
1044
849
  export {
1045
850
  DevServer,
1046
- FunctionDef,
1047
851
  FunctionManifestSchema,
1048
- FunctionRegistry,
1049
- FunctionTrigger,
1050
852
  Scheduler,
1051
- TriggerRegistry,
1052
853
  subscriptionHandler
1053
854
  };
1054
855
  //# sourceMappingURL=index.mjs.map