@dxos/functions 0.5.2 → 0.5.3-main.0a95b55

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 (86) hide show
  1. package/dist/lib/browser/index.mjs +829 -265
  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 +817 -266
  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 +33 -12
  8. package/dist/types/src/handler.d.ts.map +1 -1
  9. package/dist/types/src/index.d.ts +3 -1
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/registry/function-registry.d.ts +24 -0
  12. package/dist/types/src/registry/function-registry.d.ts.map +1 -0
  13. package/dist/types/src/registry/function-registry.test.d.ts +2 -0
  14. package/dist/types/src/registry/function-registry.test.d.ts.map +1 -0
  15. package/dist/types/src/registry/index.d.ts +2 -0
  16. package/dist/types/src/registry/index.d.ts.map +1 -0
  17. package/dist/types/src/runtime/dev-server.d.ts +15 -7
  18. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  19. package/dist/types/src/runtime/dev-server.test.d.ts +2 -0
  20. package/dist/types/src/runtime/dev-server.test.d.ts.map +1 -0
  21. package/dist/types/src/runtime/scheduler.d.ts +14 -15
  22. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  23. package/dist/types/src/testing/functions-integration.test.d.ts +2 -0
  24. package/dist/types/src/testing/functions-integration.test.d.ts.map +1 -0
  25. package/dist/types/src/testing/index.d.ts +4 -0
  26. package/dist/types/src/testing/index.d.ts.map +1 -0
  27. package/dist/types/src/testing/setup.d.ts +5 -0
  28. package/dist/types/src/testing/setup.d.ts.map +1 -0
  29. package/dist/types/src/testing/test/handler.d.ts +4 -0
  30. package/dist/types/src/testing/test/handler.d.ts.map +1 -0
  31. package/dist/types/src/testing/test/index.d.ts +3 -0
  32. package/dist/types/src/testing/test/index.d.ts.map +1 -0
  33. package/dist/types/src/testing/types.d.ts +9 -0
  34. package/dist/types/src/testing/types.d.ts.map +1 -0
  35. package/dist/types/src/testing/util.d.ts +3 -0
  36. package/dist/types/src/testing/util.d.ts.map +1 -0
  37. package/dist/types/src/trigger/index.d.ts +2 -0
  38. package/dist/types/src/trigger/index.d.ts.map +1 -0
  39. package/dist/types/src/trigger/trigger-registry.d.ts +40 -0
  40. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -0
  41. package/dist/types/src/trigger/trigger-registry.test.d.ts +2 -0
  42. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +1 -0
  43. package/dist/types/src/trigger/type/index.d.ts +5 -0
  44. package/dist/types/src/trigger/type/index.d.ts.map +1 -0
  45. package/dist/types/src/trigger/type/subscription-trigger.d.ts +4 -0
  46. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -0
  47. package/dist/types/src/trigger/type/timer-trigger.d.ts +4 -0
  48. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +1 -0
  49. package/dist/types/src/trigger/type/webhook-trigger.d.ts +4 -0
  50. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -0
  51. package/dist/types/src/trigger/type/websocket-trigger.d.ts +13 -0
  52. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -0
  53. package/dist/types/src/types.d.ts +188 -0
  54. package/dist/types/src/types.d.ts.map +1 -0
  55. package/dist/types/tools/schema.d.ts +2 -0
  56. package/dist/types/tools/schema.d.ts.map +1 -0
  57. package/package.json +23 -11
  58. package/schema/functions.json +197 -0
  59. package/src/handler.ts +56 -26
  60. package/src/index.ts +3 -1
  61. package/src/registry/function-registry.test.ts +105 -0
  62. package/src/registry/function-registry.ts +84 -0
  63. package/src/registry/index.ts +5 -0
  64. package/src/runtime/dev-server.test.ts +60 -0
  65. package/src/runtime/dev-server.ts +104 -52
  66. package/src/runtime/scheduler.test.ts +154 -21
  67. package/src/runtime/scheduler.ts +76 -152
  68. package/src/testing/functions-integration.test.ts +99 -0
  69. package/src/testing/index.ts +7 -0
  70. package/src/testing/setup.ts +45 -0
  71. package/src/testing/test/handler.ts +15 -0
  72. package/src/testing/test/index.ts +7 -0
  73. package/src/testing/types.ts +9 -0
  74. package/src/testing/util.ts +16 -0
  75. package/src/trigger/index.ts +5 -0
  76. package/src/trigger/trigger-registry.test.ts +229 -0
  77. package/src/trigger/trigger-registry.ts +176 -0
  78. package/src/trigger/type/index.ts +8 -0
  79. package/src/trigger/type/subscription-trigger.ts +73 -0
  80. package/src/trigger/type/timer-trigger.ts +44 -0
  81. package/src/trigger/type/webhook-trigger.ts +47 -0
  82. package/src/trigger/type/websocket-trigger.ts +91 -0
  83. package/src/types.ts +101 -0
  84. package/dist/types/src/manifest.d.ts +0 -26
  85. package/dist/types/src/manifest.d.ts.map +0 -1
  86. package/src/manifest.ts +0 -42
@@ -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, context, ...rest }) => {
23
+ return ({ event: { data }, context, ...rest }) => {
24
24
  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) {
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) {
28
28
  log.warn("invalid space", {
29
- event
29
+ data
30
30
  }, {
31
31
  F: __dxlog_file,
32
- L: 61,
32
+ L: 91,
33
33
  S: void 0,
34
34
  C: (f, a) => f(...a)
35
35
  });
@@ -39,15 +39,18 @@ var subscriptionHandler = (handler) => {
39
39
  objects: objects?.length
40
40
  }, {
41
41
  F: __dxlog_file,
42
- L: 63,
42
+ L: 93,
43
43
  S: void 0,
44
44
  C: (f, a) => f(...a)
45
45
  });
46
46
  }
47
47
  return handler({
48
48
  event: {
49
- space,
50
- objects
49
+ data: {
50
+ ...data,
51
+ space,
52
+ objects
53
+ }
51
54
  },
52
55
  context,
53
56
  ...rest
@@ -55,26 +58,180 @@ var subscriptionHandler = (handler) => {
55
58
  };
56
59
  };
57
60
 
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.hasSchema(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
+
58
194
  // packages/core/functions/src/runtime/dev-server.ts
59
195
  import express from "express";
60
196
  import { getPort } from "get-port-please";
61
197
  import { join } from "@dxos/node-std/path";
62
- import { Trigger } from "@dxos/async";
198
+ import { Event as Event2, Trigger } from "@dxos/async";
199
+ import { Context } from "@dxos/context";
63
200
  import { invariant } from "@dxos/invariant";
64
201
  import { log as log2 } from "@dxos/log";
65
202
  var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
66
203
  var DevServer = class {
67
204
  // prettier-ignore
68
- constructor(_client, _options) {
205
+ constructor(_client, _functionsRegistry, _options) {
69
206
  this._client = _client;
207
+ this._functionsRegistry = _functionsRegistry;
70
208
  this._options = _options;
209
+ this._ctx = createContext();
71
210
  this._handlers = {};
72
211
  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
+ };
73
230
  }
74
231
  get endpoint() {
75
232
  invariant(this._port, void 0, {
76
233
  F: __dxlog_file2,
77
- L: 46,
234
+ L: 64,
78
235
  S: this,
79
236
  A: [
80
237
  "this._port",
@@ -89,44 +246,46 @@ var DevServer = class {
89
246
  get functions() {
90
247
  return Object.values(this._handlers);
91
248
  }
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
249
  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();
107
266
  const app = express();
108
267
  app.use(express.json());
109
- app.post("/:name", async (req, res) => {
110
- const { name } = req.params;
268
+ app.post("/:path", async (req, res) => {
269
+ const { path: path2 } = req.params;
111
270
  try {
112
271
  log2.info("calling", {
113
- name
272
+ path: path2
114
273
  }, {
115
274
  F: __dxlog_file2,
116
- L: 75,
275
+ L: 88,
117
276
  S: this,
118
277
  C: (f, a) => f(...a)
119
278
  });
120
279
  if (this._options.reload) {
121
- const { def } = this._handlers[name];
280
+ const { def } = this._handlers["/" + path2];
122
281
  await this._load(def, true);
123
282
  }
124
- res.statusCode = await this._invoke(name, req.body);
283
+ res.statusCode = await this.invoke("/" + path2, req.body);
125
284
  res.end();
126
285
  } catch (err) {
127
286
  log2.catch(err, void 0, {
128
287
  F: __dxlog_file2,
129
- L: 84,
288
+ L: 98,
130
289
  S: this,
131
290
  C: (f, a) => f(...a)
132
291
  });
@@ -145,93 +304,195 @@ var DevServer = class {
145
304
  this._server = app.listen(this._port);
146
305
  try {
147
306
  const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService.register({
148
- endpoint: this.endpoint,
149
- functions: this.functions.map(({ def: { name } }) => ({
150
- name
151
- }))
307
+ endpoint: this.endpoint
152
308
  });
153
309
  log2.info("registered", {
154
- registrationId,
155
310
  endpoint
156
311
  }, {
157
312
  F: __dxlog_file2,
158
- L: 100,
313
+ L: 113,
159
314
  S: this,
160
315
  C: (f, a) => f(...a)
161
316
  });
162
- this._registrationId = registrationId;
163
317
  this._proxy = endpoint;
318
+ this._functionServiceRegistration = registrationId;
319
+ await this._functionsRegistry.open(this._ctx);
164
320
  } catch (err) {
165
321
  await this.stop();
166
322
  throw new Error("FunctionRegistryService not available (check plugin is configured).");
167
323
  }
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
+ });
168
332
  }
169
333
  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
+ });
170
349
  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;
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);
186
385
  }
187
- trigger.wake();
188
386
  });
189
387
  await trigger.wait();
190
388
  this._port = void 0;
191
389
  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
+ });
192
396
  }
193
397
  /**
194
398
  * Load function.
195
399
  */
196
- async _load(def, flush = false) {
197
- const { id, name, handler } = def;
198
- const path = join(this._options.directory, handler);
400
+ async _load(def, force = false) {
401
+ const { uri, route, handler } = def;
402
+ const filePath = join(this._options.baseDir, handler);
199
403
  log2.info("loading", {
200
- id
404
+ uri,
405
+ force
201
406
  }, {
202
407
  F: __dxlog_file2,
203
- L: 136,
408
+ L: 164,
204
409
  S: this,
205
410
  C: (f, a) => f(...a)
206
411
  });
207
- if (flush) {
208
- Object.keys(__require.cache).filter((key) => key.startsWith(path)).forEach((key) => delete __require.cache[key]);
412
+ if (force) {
413
+ Object.keys(__require.cache).filter((key) => key.startsWith(filePath)).forEach((key) => {
414
+ delete __require.cache[key];
415
+ });
209
416
  }
210
- const module = __require(path);
417
+ const module = __require(filePath);
211
418
  if (typeof module.default !== "function") {
212
- throw new Error(`Handler must export default function: ${id}`);
419
+ throw new Error(`Handler must export default function: ${uri}`);
213
420
  }
214
- this._handlers[name] = {
421
+ this._handlers[route] = {
215
422
  def,
216
423
  handler: module.default
217
424
  };
218
425
  }
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
+ }
219
453
  /**
220
- * Invoke function handler.
454
+ * Invoke function.
221
455
  */
222
- async _invoke(name, event) {
456
+ async invoke(path2, data) {
223
457
  const seq = ++this._seq;
224
458
  const now = Date.now();
225
459
  log2.info("req", {
226
460
  seq,
227
- name
461
+ path: path2
228
462
  }, {
229
463
  F: __dxlog_file2,
230
- L: 161,
464
+ L: 204,
231
465
  S: this,
232
466
  C: (f, a) => f(...a)
233
467
  });
234
- const { handler } = this._handlers[name];
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
+ });
235
496
  const context = {
236
497
  client: this._client,
237
498
  dataDir: this._options.dataDir
@@ -248,262 +509,565 @@ var DevServer = class {
248
509
  event,
249
510
  response
250
511
  });
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
512
  return statusCode;
263
513
  }
264
514
  };
515
+ var createContext = () => new Context({
516
+ name: "DevServer"
517
+ });
265
518
 
266
519
  // 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";
520
+ import path from "@dxos/node-std/path";
521
+ import { Context as Context2 } from "@dxos/context";
273
522
  import { log as log3 } from "@dxos/log";
274
- import { ComplexMap } from "@dxos/util";
275
523
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/scheduler.ts";
276
524
  var Scheduler = class {
277
- constructor(_client, _manifest, _options = {}) {
278
- this._client = _client;
279
- this._manifest = _manifest;
525
+ constructor(functions, triggers, _options = {}) {
526
+ this.functions = functions;
527
+ this.triggers = triggers;
280
528
  this._options = _options;
281
- this._mounts = new ComplexMap(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);
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
+ });
282
536
  }
283
537
  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
- });
538
+ await this._ctx.dispose();
539
+ this._ctx = createContext2();
540
+ await this.functions.open(this._ctx);
541
+ await this.triggers.open(this._ctx);
292
542
  }
293
543
  async stop() {
294
- for (const { id, spaceKey } of this._mounts.keys()) {
295
- await this.unmount(id, spaceKey);
296
- }
544
+ await this._ctx.dispose();
545
+ await this.functions.close();
546
+ await this.triggers.close();
297
547
  }
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
- ]
548
+ async register(space, manifest) {
549
+ await this.functions.register(space, manifest);
550
+ await this.triggers.register(space, manifest);
551
+ }
552
+ async _safeActivateTriggers(space, triggers, functions) {
553
+ const mountTasks = triggers.map((trigger) => {
554
+ return this.activate(space, functions, trigger);
312
555
  });
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
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
322
563
  }, {
323
564
  F: __dxlog_file3,
324
- L: 69,
565
+ L: 74,
325
566
  S: this,
326
567
  C: (f, a) => f(...a)
327
568
  });
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
- }
569
+ return;
337
570
  }
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();
348
- }
349
- }
350
- _createTimer(ctx, space, def, trigger) {
351
- const task = new DeferredTask(ctx, async () => {
352
- await this._execFunction(def, {
353
- space: space.key
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
+ }
354
580
  });
355
581
  });
356
- invariant2(trigger.schedule, void 0, {
582
+ log3("activated trigger", {
583
+ space: space.key,
584
+ trigger: fnTrigger
585
+ }, {
357
586
  F: __dxlog_file3,
358
- L: 102,
587
+ L: 84,
359
588
  S: this,
360
- A: [
361
- "trigger.schedule",
362
- ""
363
- ]
589
+ C: (f, a) => f(...a)
364
590
  });
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
591
+ }
592
+ async _execFunction(def, { data, meta }) {
593
+ let status = 0;
594
+ try {
595
+ const payload = Object.assign({}, meta && {
596
+ meta
597
+ }, data);
598
+ const { endpoint, callback } = this._options;
599
+ if (endpoint) {
600
+ const url = path.join(endpoint, def.route);
601
+ log3.info("exec", {
602
+ function: def.uri,
603
+ url
379
604
  }, {
380
605
  F: __dxlog_file3,
381
- L: 116,
606
+ L: 100,
382
607
  S: this,
383
608
  C: (f, a) => f(...a)
384
609
  });
385
- task.schedule();
610
+ const response = await fetch(url, {
611
+ method: "POST",
612
+ headers: {
613
+ "Content-Type": "application/json"
614
+ },
615
+ body: JSON.stringify(payload)
616
+ });
617
+ status = response.status;
618
+ } 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;
386
628
  }
387
- });
388
- job.start();
389
- ctx.onDispose(() => job.stop());
629
+ if (status && status >= 400) {
630
+ throw new Error(`Response: ${status}`);
631
+ }
632
+ log3.info("done", {
633
+ function: def.uri,
634
+ status
635
+ }, {
636
+ F: __dxlog_file3,
637
+ L: 121,
638
+ S: this,
639
+ C: (f, a) => f(...a)
640
+ });
641
+ } catch (err) {
642
+ log3.error("error", {
643
+ function: def.uri,
644
+ error: err.message
645
+ }, {
646
+ F: __dxlog_file3,
647
+ L: 123,
648
+ S: this,
649
+ C: (f, a) => f(...a)
650
+ });
651
+ status = 500;
652
+ }
653
+ return status;
390
654
  }
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,
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({
405
680
  objects: Array.from(objectIds)
406
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
690
+ }, {
691
+ F: __dxlog_file4,
692
+ L: 32,
693
+ S: void 0,
694
+ C: (f, a) => f(...a)
407
695
  });
408
- const subscriptions = [];
409
- const subscription = createSubscription(({ added, updated }) => {
410
- log3.info("updated", {
411
- added: added.length,
412
- updated: updated.length
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
413
711
  }, {
414
- F: __dxlog_file3,
415
- L: 139,
416
- S: this,
712
+ F: __dxlog_file4,
713
+ L: 52,
714
+ S: void 0,
417
715
  C: (f, a) => f(...a)
418
716
  });
419
- for (const object of added) {
420
- objectIds.add(object.id);
421
- }
422
- for (const object of updated) {
423
- objectIds.add(object.id);
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
+ }
424
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
+ });
425
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)
426
795
  });
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
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
436
821
  }, {
437
- F: __dxlog_file3,
438
- L: 159,
439
- S: this,
822
+ F: __dxlog_file7,
823
+ L: 39,
824
+ S: void 0,
440
825
  C: (f, a) => f(...a)
441
826
  });
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
- }
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,
864
+ C: (f, a) => f(...a)
865
+ });
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
+ });
449
888
  }
450
889
  }
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
890
  });
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();
930
+ }
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);
457
936
  }
458
- async _execFunction(def, data) {
937
+ async activate(triggerCtx, trigger, callback) {
938
+ log8("activate", {
939
+ space: triggerCtx.space.key,
940
+ trigger
941
+ }, {
942
+ F: __dxlog_file8,
943
+ L: 73,
944
+ S: this,
945
+ C: (f, a) => f(...a)
946
+ });
947
+ const activationCtx = new Context3({
948
+ name: `trigger_${trigger.function}`
949
+ });
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
+ ]
960
+ });
961
+ registeredTrigger.activationCtx = activationCtx;
459
962
  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)
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
+ }
970
+ /**
971
+ * Loads triggers from the manifest into the space.
972
+ */
973
+ async register(space, manifest) {
974
+ log8("register", {
975
+ space: space.key
976
+ }, {
977
+ F: __dxlog_file8,
978
+ L: 95,
979
+ S: this,
980
+ C: (f, a) => f(...a)
981
+ });
982
+ if (!manifest.triggers?.length) {
983
+ return;
984
+ }
985
+ if (!space.db.graph.runtimeSchemaRegistry.hasSchema(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;
998
+ }
999
+ const registered = [];
1000
+ this._triggersBySpaceKey.set(space.key, registered);
1001
+ await space.waitUntilReady();
1002
+ if (this._ctx.disposed) {
1003
+ break;
1004
+ }
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);
477
1008
  });
478
- status = response.status;
479
- } else if (callback) {
480
- status = await callback(data);
1009
+ this._ctx.onDispose(functionsSubscription);
481
1010
  }
482
- log3("result", {
483
- function: def.id,
484
- result: status
485
- }, {
486
- F: __dxlog_file3,
487
- L: 202,
1011
+ });
1012
+ this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
1013
+ }
1014
+ async _close(_) {
1015
+ this._triggersBySpaceKey.clear();
1016
+ }
1017
+ _handleNewTriggers(space, allTriggers, registered) {
1018
+ const newTriggers = allTriggers.filter((candidate) => {
1019
+ return registered.find((reg) => reg.trigger.id === candidate.id) == null;
1020
+ });
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,
488
1032
  S: this,
489
1033
  C: (f, a) => f(...a)
490
1034
  });
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)
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);
1049
+ }
1050
+ }
1051
+ if (removed.length > 0) {
1052
+ this.removed.emit({
1053
+ space,
1054
+ triggers: removed
500
1055
  });
501
1056
  }
502
1057
  }
1058
+ _getTriggers(space, predicate) {
1059
+ const allSpaceTriggers = this._triggersBySpaceKey.get(space.key) ?? [];
1060
+ return allSpaceTriggers.filter(predicate).map((trigger) => trigger.trigger);
1061
+ }
503
1062
  };
504
1063
  export {
505
1064
  DevServer,
1065
+ FunctionDef,
1066
+ FunctionManifestSchema,
1067
+ FunctionRegistry,
1068
+ FunctionTrigger,
506
1069
  Scheduler,
1070
+ TriggerRegistry,
507
1071
  subscriptionHandler
508
1072
  };
509
1073
  //# sourceMappingURL=index.mjs.map