@dxos/functions 0.5.3-main.d7fe7b5 → 0.5.3-main.e76d664

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