@dxos/functions 0.5.3-main.43e79dd → 0.5.3-main.495a683

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