@dxos/functions 0.5.3-main.37bbd91 → 0.5.3-main.3b535c7

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