@dxos/functions 0.5.3-main.2ad0afc → 0.5.3-main.3456876

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