@dxos/functions 0.5.3-main.83788ff → 0.5.3-main.8ffbbae

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