@dxos/functions 0.6.11-staging.e6894a4 → 0.6.12-main.5cc132e

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 (41) hide show
  1. package/dist/lib/browser/{chunk-XOBJR3A6.mjs → chunk-2I75VGHZ.mjs} +2 -3
  2. package/dist/lib/browser/{chunk-XOBJR3A6.mjs.map → chunk-2I75VGHZ.mjs.map} +1 -1
  3. package/dist/lib/browser/{chunk-563WM5C2.mjs → chunk-E7GXBW5N.mjs} +20 -12
  4. package/dist/lib/browser/chunk-E7GXBW5N.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +4 -2
  6. package/dist/lib/browser/index.mjs.map +1 -1
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +2 -2
  9. package/dist/lib/browser/types.mjs +1 -1
  10. package/dist/lib/node/{chunk-GGTHSME4.cjs → chunk-JV3VNH5X.cjs} +5 -6
  11. package/dist/lib/node/{chunk-GGTHSME4.cjs.map → chunk-JV3VNH5X.cjs.map} +1 -1
  12. package/dist/lib/node/{chunk-S4S6FO6X.cjs → chunk-S5VUBTIA.cjs} +34 -35
  13. package/dist/lib/node/chunk-S5VUBTIA.cjs.map +7 -0
  14. package/dist/lib/node/index.cjs +13 -11
  15. package/dist/lib/node/index.cjs.map +1 -1
  16. package/dist/lib/node/meta.json +1 -1
  17. package/dist/lib/node/testing/index.cjs +10 -10
  18. package/dist/lib/node/types.cjs +5 -5
  19. package/dist/lib/node/types.cjs.map +1 -1
  20. package/dist/lib/node-esm/chunk-KFC4XAIY.mjs +78 -0
  21. package/dist/lib/node-esm/chunk-KFC4XAIY.mjs.map +7 -0
  22. package/dist/lib/node-esm/chunk-ZZQ234R7.mjs +597 -0
  23. package/dist/lib/node-esm/chunk-ZZQ234R7.mjs.map +7 -0
  24. package/dist/lib/node-esm/index.mjs +93 -0
  25. package/dist/lib/node-esm/index.mjs.map +7 -0
  26. package/dist/lib/node-esm/meta.json +1 -0
  27. package/dist/lib/node-esm/testing/index.mjs +665 -0
  28. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  29. package/dist/lib/node-esm/types.mjs +13 -0
  30. package/dist/lib/node-esm/types.mjs.map +7 -0
  31. package/dist/types/src/trigger/type/websocket-trigger.d.ts +1 -0
  32. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -1
  33. package/package.json +20 -17
  34. package/src/function/function-registry.test.ts +3 -2
  35. package/src/runtime/dev-server.test.ts +7 -6
  36. package/src/runtime/scheduler.test.ts +9 -6
  37. package/src/testing/functions-integration.test.ts +7 -5
  38. package/src/trigger/trigger-registry.test.ts +3 -5
  39. package/src/trigger/type/websocket-trigger.ts +15 -4
  40. package/dist/lib/browser/chunk-563WM5C2.mjs.map +0 -7
  41. package/dist/lib/node/chunk-S4S6FO6X.cjs.map +0 -7
@@ -0,0 +1,78 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // packages/core/functions/src/types.ts
9
+ import { RawObject, S, TypedObject } from "@dxos/echo-schema";
10
+ var SubscriptionTriggerSchema = S.mutable(S.Struct({
11
+ type: S.Literal("subscription"),
12
+ // TODO(burdon): Define query DSL (from ECHO).
13
+ filter: S.Array(S.Struct({
14
+ type: S.String,
15
+ props: S.optional(S.Record(S.String, S.Any))
16
+ })),
17
+ options: S.optional(S.Struct({
18
+ // Watch changes to object (not just creation).
19
+ deep: S.optional(S.Boolean),
20
+ // Debounce changes (delay in ms).
21
+ delay: S.optional(S.Number)
22
+ }))
23
+ }));
24
+ var TimerTriggerSchema = S.mutable(S.Struct({
25
+ type: S.Literal("timer"),
26
+ cron: S.String
27
+ }));
28
+ var WebhookTriggerSchema = S.mutable(S.Struct({
29
+ type: S.Literal("webhook"),
30
+ method: S.String,
31
+ // Assigned port.
32
+ port: S.optional(S.Number)
33
+ }));
34
+ var WebsocketTriggerSchema = S.mutable(S.Struct({
35
+ type: S.Literal("websocket"),
36
+ url: S.String,
37
+ init: S.optional(S.Record(S.String, S.Any))
38
+ }));
39
+ var TriggerSpecSchema = S.Union(TimerTriggerSchema, WebhookTriggerSchema, WebsocketTriggerSchema, SubscriptionTriggerSchema);
40
+ var FunctionDef = class extends TypedObject({
41
+ typename: "dxos.org/type/FunctionDef",
42
+ version: "0.1.0"
43
+ })({
44
+ uri: S.String,
45
+ description: S.optional(S.String),
46
+ route: S.String,
47
+ handler: S.String
48
+ }) {
49
+ };
50
+ var FunctionTrigger = class extends TypedObject({
51
+ typename: "dxos.org/type/FunctionTrigger",
52
+ version: "0.1.0"
53
+ })({
54
+ name: S.optional(S.String),
55
+ enabled: S.optional(S.Boolean),
56
+ function: S.String.pipe(S.description("Function URI.")),
57
+ // The `meta` property is merged into the event data passed to the function.
58
+ meta: S.optional(S.mutable(S.Record(S.String, S.Any))),
59
+ spec: TriggerSpecSchema
60
+ }) {
61
+ };
62
+ var FunctionManifestSchema = S.Struct({
63
+ functions: S.optional(S.mutable(S.Array(RawObject(FunctionDef)))),
64
+ triggers: S.optional(S.mutable(S.Array(RawObject(FunctionTrigger))))
65
+ });
66
+ var FUNCTION_SCHEMA = [
67
+ FunctionDef,
68
+ FunctionTrigger
69
+ ];
70
+
71
+ export {
72
+ __require,
73
+ FunctionDef,
74
+ FunctionTrigger,
75
+ FunctionManifestSchema,
76
+ FUNCTION_SCHEMA
77
+ };
78
+ //# sourceMappingURL=chunk-KFC4XAIY.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/types.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { RawObject, S, TypedObject } from '@dxos/echo-schema';\n\n/**\n * Type discriminator for TriggerSpec.\n * Every spec has a type field of type FunctionTriggerType that we can use to understand which\n * type we're working with.\n * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions\n */\nexport type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websocket';\n\nconst SubscriptionTriggerSchema = S.mutable(\n S.Struct({\n type: S.Literal('subscription'),\n // TODO(burdon): Define query DSL (from ECHO).\n filter: S.Array(\n S.Struct({\n type: S.String,\n props: S.optional(S.Record(S.String, S.Any)),\n }),\n ),\n options: S.optional(\n S.Struct({\n // Watch changes to object (not just creation).\n deep: S.optional(S.Boolean),\n // Debounce changes (delay in ms).\n delay: S.optional(S.Number),\n }),\n ),\n }),\n);\n\nexport type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;\n\nconst TimerTriggerSchema = S.mutable(\n S.Struct({\n type: S.Literal('timer'),\n cron: S.String,\n }),\n);\n\nexport type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;\n\nconst WebhookTriggerSchema = S.mutable(\n S.Struct({\n type: S.Literal('webhook'),\n method: S.String,\n // Assigned port.\n port: S.optional(S.Number),\n }),\n);\n\nexport type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;\n\nconst WebsocketTriggerSchema = S.mutable(\n S.Struct({\n type: S.Literal('websocket'),\n url: S.String,\n init: S.optional(S.Record(S.String, S.Any)),\n }),\n);\n\nexport type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;\n\nconst TriggerSpecSchema = S.Union(\n TimerTriggerSchema,\n WebhookTriggerSchema,\n WebsocketTriggerSchema,\n SubscriptionTriggerSchema,\n);\n\nexport type TriggerSpec = TimerTrigger | WebhookTrigger | WebsocketTrigger | SubscriptionTrigger;\n\n/**\n * Function definition.\n */\nexport class FunctionDef extends TypedObject({\n typename: 'dxos.org/type/FunctionDef',\n version: '0.1.0',\n})({\n uri: S.String,\n description: S.optional(S.String),\n route: S.String,\n handler: S.String,\n}) {}\n\n/**\n * Function trigger.\n */\nexport class FunctionTrigger extends TypedObject({\n typename: 'dxos.org/type/FunctionTrigger',\n version: '0.1.0',\n})({\n name: S.optional(S.String),\n enabled: S.optional(S.Boolean),\n function: S.String.pipe(S.description('Function URI.')),\n // The `meta` property is merged into the event data passed to the function.\n meta: S.optional(S.mutable(S.Record(S.String, S.Any))),\n spec: TriggerSpecSchema,\n}) {}\n\n/**\n * Function manifest file.\n */\nexport const FunctionManifestSchema = S.Struct({\n functions: S.optional(S.mutable(S.Array(RawObject(FunctionDef)))),\n triggers: S.optional(S.mutable(S.Array(RawObject(FunctionTrigger)))),\n});\n\nexport type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;\n\n// TODO(burdon): Standards?\nexport const FUNCTION_SCHEMA = [FunctionDef, FunctionTrigger];\n"],
5
+ "mappings": ";;;;;;;;AAIA,SAASA,WAAWC,GAAGC,mBAAmB;AAU1C,IAAMC,4BAA4BC,EAAEC,QAClCD,EAAEE,OAAO;EACPC,MAAMH,EAAEI,QAAQ,cAAA;;EAEhBC,QAAQL,EAAEM,MACRN,EAAEE,OAAO;IACPC,MAAMH,EAAEO;IACRC,OAAOR,EAAES,SAAST,EAAEU,OAAOV,EAAEO,QAAQP,EAAEW,GAAG,CAAA;EAC5C,CAAA,CAAA;EAEFC,SAASZ,EAAES,SACTT,EAAEE,OAAO;;IAEPW,MAAMb,EAAES,SAAST,EAAEc,OAAO;;IAE1BC,OAAOf,EAAES,SAAST,EAAEgB,MAAM;EAC5B,CAAA,CAAA;AAEJ,CAAA,CAAA;AAKF,IAAMC,qBAAqBjB,EAAEC,QAC3BD,EAAEE,OAAO;EACPC,MAAMH,EAAEI,QAAQ,OAAA;EAChBc,MAAMlB,EAAEO;AACV,CAAA,CAAA;AAKF,IAAMY,uBAAuBnB,EAAEC,QAC7BD,EAAEE,OAAO;EACPC,MAAMH,EAAEI,QAAQ,SAAA;EAChBgB,QAAQpB,EAAEO;;EAEVc,MAAMrB,EAAES,SAAST,EAAEgB,MAAM;AAC3B,CAAA,CAAA;AAKF,IAAMM,yBAAyBtB,EAAEC,QAC/BD,EAAEE,OAAO;EACPC,MAAMH,EAAEI,QAAQ,WAAA;EAChBmB,KAAKvB,EAAEO;EACPiB,MAAMxB,EAAES,SAAST,EAAEU,OAAOV,EAAEO,QAAQP,EAAEW,GAAG,CAAA;AAC3C,CAAA,CAAA;AAKF,IAAMc,oBAAoBzB,EAAE0B,MAC1BT,oBACAE,sBACAG,wBACAvB,yBAAAA;AAQK,IAAM4B,cAAN,cAA0BC,YAAY;EAC3CC,UAAU;EACVC,SAAS;AACX,CAAA,EAAG;EACDC,KAAK/B,EAAEO;EACPyB,aAAahC,EAAES,SAAST,EAAEO,MAAM;EAChC0B,OAAOjC,EAAEO;EACT2B,SAASlC,EAAEO;AACb,CAAA,EAAA;AAAI;AAKG,IAAM4B,kBAAN,cAA8BP,YAAY;EAC/CC,UAAU;EACVC,SAAS;AACX,CAAA,EAAG;EACDM,MAAMpC,EAAES,SAAST,EAAEO,MAAM;EACzB8B,SAASrC,EAAES,SAAST,EAAEc,OAAO;EAC7BwB,UAAUtC,EAAEO,OAAOgC,KAAKvC,EAAEgC,YAAY,eAAA,CAAA;;EAEtCQ,MAAMxC,EAAES,SAAST,EAAEC,QAAQD,EAAEU,OAAOV,EAAEO,QAAQP,EAAEW,GAAG,CAAA,CAAA;EACnD8B,MAAMhB;AACR,CAAA,EAAA;AAAI;AAKG,IAAMiB,yBAAyB1C,EAAEE,OAAO;EAC7CyC,WAAW3C,EAAES,SAAST,EAAEC,QAAQD,EAAEM,MAAMsC,UAAUjB,WAAAA,CAAAA,CAAAA,CAAAA;EAClDkB,UAAU7C,EAAES,SAAST,EAAEC,QAAQD,EAAEM,MAAMsC,UAAUT,eAAAA,CAAAA,CAAAA,CAAAA;AACnD,CAAA;AAKO,IAAMW,kBAAkB;EAACnB;EAAaQ;;",
6
+ "names": ["RawObject", "S", "TypedObject", "SubscriptionTriggerSchema", "S", "mutable", "Struct", "type", "Literal", "filter", "Array", "String", "props", "optional", "Record", "Any", "options", "deep", "Boolean", "delay", "Number", "TimerTriggerSchema", "cron", "WebhookTriggerSchema", "method", "port", "WebsocketTriggerSchema", "url", "init", "TriggerSpecSchema", "Union", "FunctionDef", "TypedObject", "typename", "version", "uri", "description", "route", "handler", "FunctionTrigger", "name", "enabled", "function", "pipe", "meta", "spec", "FunctionManifestSchema", "functions", "RawObject", "triggers", "FUNCTION_SCHEMA"]
7
+ }
@@ -0,0 +1,597 @@
1
+ import {
2
+ FunctionDef,
3
+ FunctionTrigger
4
+ } from "./chunk-KFC4XAIY.mjs";
5
+
6
+ // packages/core/functions/src/function/function-registry.ts
7
+ import { Event } from "@dxos/async";
8
+ import { create, Filter } from "@dxos/client/echo";
9
+ import { Resource } from "@dxos/context";
10
+ import { PublicKey } from "@dxos/keys";
11
+ import { log } from "@dxos/log";
12
+ import { ComplexMap, diff } from "@dxos/util";
13
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/function/function-registry.ts";
14
+ var FunctionRegistry = class extends Resource {
15
+ constructor(_client) {
16
+ super();
17
+ this._client = _client;
18
+ this._functionBySpaceKey = new ComplexMap(PublicKey.hash);
19
+ this.registered = new Event();
20
+ }
21
+ getFunctions(space) {
22
+ return this._functionBySpaceKey.get(space.key) ?? [];
23
+ }
24
+ getUniqueByUri() {
25
+ const uniqueByUri = [
26
+ ...this._functionBySpaceKey.values()
27
+ ].flatMap((defs) => defs).reduce((acc, v) => {
28
+ acc.set(v.uri, v);
29
+ return acc;
30
+ }, /* @__PURE__ */ new Map());
31
+ return [
32
+ ...uniqueByUri.values()
33
+ ];
34
+ }
35
+ /**
36
+ * Loads function definitions from the manifest into the space.
37
+ * We first load all the definitions from the space to deduplicate by functionId.
38
+ */
39
+ async register(space, functions) {
40
+ log("register", {
41
+ space: space.key,
42
+ functions: functions?.length ?? 0
43
+ }, {
44
+ F: __dxlog_file,
45
+ L: 48,
46
+ S: this,
47
+ C: (f, a) => f(...a)
48
+ });
49
+ if (!functions?.length) {
50
+ return;
51
+ }
52
+ if (!space.db.graph.schemaRegistry.hasSchema(FunctionDef)) {
53
+ space.db.graph.schemaRegistry.addSchema([
54
+ FunctionDef
55
+ ]);
56
+ }
57
+ const { objects: existing } = await space.db.query(Filter.schema(FunctionDef)).run();
58
+ const { added } = diff(existing, functions, (a, b) => a.uri === b.uri);
59
+ added.forEach((def) => space.db.add(create(FunctionDef, def)));
60
+ if (added.length > 0) {
61
+ await space.db.flush({
62
+ indexes: true,
63
+ updates: true
64
+ });
65
+ }
66
+ }
67
+ async _open() {
68
+ log.info("opening...", void 0, {
69
+ F: __dxlog_file,
70
+ L: 68,
71
+ S: this,
72
+ C: (f, a) => f(...a)
73
+ });
74
+ const spacesSubscription = this._client.spaces.subscribe(async (spaces) => {
75
+ for (const space of spaces) {
76
+ if (this._functionBySpaceKey.has(space.key)) {
77
+ continue;
78
+ }
79
+ const registered = [];
80
+ this._functionBySpaceKey.set(space.key, registered);
81
+ await space.waitUntilReady();
82
+ if (this._ctx.disposed) {
83
+ break;
84
+ }
85
+ this._ctx.onDispose(space.db.query(Filter.schema(FunctionDef)).subscribe(({ objects }) => {
86
+ const { added } = diff(registered, objects, (a, b) => a.uri === b.uri);
87
+ if (added.length > 0) {
88
+ registered.push(...added);
89
+ this.registered.emit({
90
+ space,
91
+ added
92
+ });
93
+ }
94
+ }));
95
+ }
96
+ });
97
+ this._ctx.onDispose(() => spacesSubscription.unsubscribe());
98
+ }
99
+ async _close(_) {
100
+ log.info("closing...", void 0, {
101
+ F: __dxlog_file,
102
+ L: 101,
103
+ S: this,
104
+ C: (f, a) => f(...a)
105
+ });
106
+ this._functionBySpaceKey.clear();
107
+ }
108
+ };
109
+
110
+ // packages/core/functions/src/trigger/type/subscription-trigger.ts
111
+ import { debounce, UpdateScheduler } from "@dxos/async";
112
+ import { Filter as Filter2 } from "@dxos/client/echo";
113
+ import { createSubscription } from "@dxos/echo-db";
114
+ import { log as log2 } from "@dxos/log";
115
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/subscription-trigger.ts";
116
+ var createSubscriptionTrigger = async (ctx, space, spec, callback) => {
117
+ const objectIds = /* @__PURE__ */ new Set();
118
+ const task = new UpdateScheduler(ctx, async () => {
119
+ if (objectIds.size > 0) {
120
+ const objects = Array.from(objectIds);
121
+ objectIds.clear();
122
+ await callback({
123
+ objects
124
+ });
125
+ }
126
+ }, {
127
+ maxFrequency: 4
128
+ });
129
+ const subscriptions = [];
130
+ const subscription = createSubscription(({ added, updated }) => {
131
+ const sizeBefore = objectIds.size;
132
+ for (const object of added) {
133
+ objectIds.add(object.id);
134
+ }
135
+ for (const object of updated) {
136
+ objectIds.add(object.id);
137
+ }
138
+ if (objectIds.size > sizeBefore) {
139
+ log2.info("updated", {
140
+ added: added.length,
141
+ updated: updated.length
142
+ }, {
143
+ F: __dxlog_file2,
144
+ L: 46,
145
+ S: void 0,
146
+ C: (f, a) => f(...a)
147
+ });
148
+ task.trigger();
149
+ }
150
+ });
151
+ subscriptions.push(() => subscription.unsubscribe());
152
+ const { filter, options: { deep, delay } = {} } = spec;
153
+ const update = ({ objects }) => {
154
+ log2.info("update", {
155
+ objects: objects.length
156
+ }, {
157
+ F: __dxlog_file2,
158
+ L: 56,
159
+ S: void 0,
160
+ C: (f, a) => f(...a)
161
+ });
162
+ subscription.update(objects);
163
+ if (deep) {
164
+ }
165
+ };
166
+ log2.info("subscription", {
167
+ filter
168
+ }, {
169
+ F: __dxlog_file2,
170
+ L: 74,
171
+ S: void 0,
172
+ C: (f, a) => f(...a)
173
+ });
174
+ if (filter) {
175
+ const query = space.db.query(Filter2.typename(filter[0].type, filter[0].props));
176
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
177
+ }
178
+ ctx.onDispose(() => {
179
+ subscriptions.forEach((unsubscribe) => unsubscribe());
180
+ });
181
+ };
182
+
183
+ // packages/core/functions/src/trigger/type/timer-trigger.ts
184
+ import { CronJob } from "cron";
185
+ import { DeferredTask } from "@dxos/async";
186
+ import { log as log3 } from "@dxos/log";
187
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/timer-trigger.ts";
188
+ var createTimerTrigger = async (ctx, space, spec, callback) => {
189
+ const task = new DeferredTask(ctx, async () => {
190
+ await callback({});
191
+ });
192
+ let last = 0;
193
+ let run = 0;
194
+ const job = CronJob.from({
195
+ cronTime: spec.cron,
196
+ runOnInit: false,
197
+ onTick: () => {
198
+ const now = Date.now();
199
+ const delta = last ? now - last : 0;
200
+ last = now;
201
+ run++;
202
+ log3.info("tick", {
203
+ space: space.key.truncate(),
204
+ count: run,
205
+ delta
206
+ }, {
207
+ F: __dxlog_file3,
208
+ L: 38,
209
+ S: void 0,
210
+ C: (f, a) => f(...a)
211
+ });
212
+ task.schedule();
213
+ }
214
+ });
215
+ job.start();
216
+ ctx.onDispose(() => job.stop());
217
+ };
218
+
219
+ // packages/core/functions/src/trigger/type/websocket-trigger.ts
220
+ import { sleep, Trigger } from "@dxos/async";
221
+ import { log as log4 } from "@dxos/log";
222
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/type/websocket-trigger.ts";
223
+ var createWebsocketTrigger = async (ctx, space, spec, callback, options = {
224
+ retryDelay: 2,
225
+ maxAttempts: 5
226
+ }) => {
227
+ const { url, init } = spec;
228
+ let wasOpen = false;
229
+ let ws;
230
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
231
+ const open = new Trigger();
232
+ ws = await createWebSocket(url);
233
+ Object.assign(ws, {
234
+ onopen: () => {
235
+ log4.info("opened", {
236
+ url
237
+ }, {
238
+ F: __dxlog_file4,
239
+ L: 39,
240
+ S: void 0,
241
+ C: (f, a) => f(...a)
242
+ });
243
+ if (spec.init) {
244
+ ws.send(new TextEncoder().encode(JSON.stringify(init)));
245
+ }
246
+ open.wake(true);
247
+ },
248
+ onclose: (event) => {
249
+ log4.info("closed", {
250
+ url,
251
+ code: event.code
252
+ }, {
253
+ F: __dxlog_file4,
254
+ L: 48,
255
+ S: void 0,
256
+ C: (f, a) => f(...a)
257
+ });
258
+ if (event.code === 1006 && wasOpen && !ctx.disposed) {
259
+ setTimeout(async () => {
260
+ log4.info(`reconnecting in ${options.retryDelay}s...`, {
261
+ url
262
+ }, {
263
+ F: __dxlog_file4,
264
+ L: 53,
265
+ S: void 0,
266
+ C: (f, a) => f(...a)
267
+ });
268
+ await createWebsocketTrigger(ctx, space, spec, callback, options);
269
+ }, options.retryDelay * 1e3);
270
+ }
271
+ open.wake(false);
272
+ },
273
+ onerror: (event) => {
274
+ log4.catch(event.error ?? new Error("Unspecified ws error."), {
275
+ url
276
+ }, {
277
+ F: __dxlog_file4,
278
+ L: 61,
279
+ S: void 0,
280
+ C: (f, a) => f(...a)
281
+ });
282
+ open.wake(false);
283
+ },
284
+ onmessage: async (event) => {
285
+ try {
286
+ log4.info("message", void 0, {
287
+ F: __dxlog_file4,
288
+ L: 67,
289
+ S: void 0,
290
+ C: (f, a) => f(...a)
291
+ });
292
+ const data = JSON.parse(new TextDecoder().decode(event.data));
293
+ await callback({
294
+ data
295
+ });
296
+ } catch (err) {
297
+ log4.catch(err, {
298
+ url
299
+ }, {
300
+ F: __dxlog_file4,
301
+ L: 71,
302
+ S: void 0,
303
+ C: (f, a) => f(...a)
304
+ });
305
+ }
306
+ }
307
+ });
308
+ const isOpen = await open.wait();
309
+ if (ctx.disposed) {
310
+ break;
311
+ }
312
+ if (isOpen) {
313
+ wasOpen = true;
314
+ break;
315
+ }
316
+ const wait = Math.pow(attempt, 2) * options.retryDelay;
317
+ if (attempt < options.maxAttempts) {
318
+ log4.warn(`failed to connect; trying again in ${wait}s`, {
319
+ attempt
320
+ }, {
321
+ F: __dxlog_file4,
322
+ L: 86,
323
+ S: void 0,
324
+ C: (f, a) => f(...a)
325
+ });
326
+ await sleep(wait * 1e3);
327
+ }
328
+ }
329
+ ctx.onDispose(() => {
330
+ ws?.close();
331
+ });
332
+ };
333
+ var createNodeWebSocket = async (url) => {
334
+ const importESM = Function("path", "return import(path)");
335
+ const { default: { WebSocket: WebSocket1 } } = await importESM("ws");
336
+ return new WebSocket1(url);
337
+ };
338
+ var createWebSocket = async (url) => {
339
+ return typeof globalThis.WebSocket === "undefined" ? await createNodeWebSocket(url) : new WebSocket(url);
340
+ };
341
+
342
+ // packages/core/functions/src/trigger/trigger-registry.ts
343
+ import { Event as Event2 } from "@dxos/async";
344
+ import { create as create2, Filter as Filter3, getMeta } from "@dxos/client/echo";
345
+ import { Context, Resource as Resource2 } from "@dxos/context";
346
+ import { compareForeignKeys, ECHO_ATTR_META, foreignKey } from "@dxos/echo-schema";
347
+ import { invariant } from "@dxos/invariant";
348
+ import { PublicKey as PublicKey2 } from "@dxos/keys";
349
+ import { log as log5 } from "@dxos/log";
350
+ import { ComplexMap as ComplexMap2, diff as diff2 } from "@dxos/util";
351
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/functions/src/trigger/trigger-registry.ts";
352
+ var triggerFactory = {
353
+ subscription: createSubscriptionTrigger,
354
+ timer: createTimerTrigger,
355
+ // TODO(burdon): Cannot use in browser.
356
+ // webhook: createWebhookTrigger,
357
+ webhook: null,
358
+ websocket: createWebsocketTrigger
359
+ };
360
+ var TriggerRegistry = class extends Resource2 {
361
+ constructor(_client, _options) {
362
+ super();
363
+ this._client = _client;
364
+ this._options = _options;
365
+ this._triggersBySpaceKey = new ComplexMap2(PublicKey2.hash);
366
+ this.registered = new Event2();
367
+ this.removed = new Event2();
368
+ }
369
+ getActiveTriggers(space) {
370
+ return this._getTriggers(space, (t) => t.activationCtx != null);
371
+ }
372
+ getInactiveTriggers(space) {
373
+ return this._getTriggers(space, (t) => t.activationCtx == null);
374
+ }
375
+ /**
376
+ * Set callback for trigger.
377
+ */
378
+ async activate(space, trigger, callback) {
379
+ log5("activate", {
380
+ space: space.key,
381
+ trigger
382
+ }, {
383
+ F: __dxlog_file5,
384
+ L: 77,
385
+ S: this,
386
+ C: (f, a) => f(...a)
387
+ });
388
+ const activationCtx = new Context({
389
+ name: `FunctionTrigger-${trigger.function}`
390
+ }, {
391
+ F: __dxlog_file5,
392
+ L: 79
393
+ });
394
+ this._ctx.onDispose(() => activationCtx.dispose());
395
+ const registeredTrigger = this._triggersBySpaceKey.get(space.key)?.find((reg) => reg.trigger.id === trigger.id);
396
+ invariant(registeredTrigger, `Trigger is not registered: ${trigger.function}`, {
397
+ F: __dxlog_file5,
398
+ L: 82,
399
+ S: this,
400
+ A: [
401
+ "registeredTrigger",
402
+ "`Trigger is not registered: ${trigger.function}`"
403
+ ]
404
+ });
405
+ registeredTrigger.activationCtx = activationCtx;
406
+ try {
407
+ const options = this._options?.[trigger.spec.type];
408
+ const createTrigger = triggerFactory[trigger.spec.type];
409
+ invariant(createTrigger, `Trigger factory not found: ${trigger.spec.type}`, {
410
+ F: __dxlog_file5,
411
+ L: 89,
412
+ S: this,
413
+ A: [
414
+ "createTrigger",
415
+ "`Trigger factory not found: ${trigger.spec.type}`"
416
+ ]
417
+ });
418
+ await createTrigger(activationCtx, space, trigger.spec, callback, options);
419
+ } catch (err) {
420
+ delete registeredTrigger.activationCtx;
421
+ throw err;
422
+ }
423
+ }
424
+ /**
425
+ * Loads triggers from the manifest into the space.
426
+ */
427
+ async register(space, manifest) {
428
+ log5("register", {
429
+ space: space.key
430
+ }, {
431
+ F: __dxlog_file5,
432
+ L: 101,
433
+ S: this,
434
+ C: (f, a) => f(...a)
435
+ });
436
+ if (!manifest.triggers?.length) {
437
+ return;
438
+ }
439
+ if (!space.db.graph.schemaRegistry.hasSchema(FunctionTrigger)) {
440
+ space.db.graph.schemaRegistry.addSchema([
441
+ FunctionTrigger
442
+ ]);
443
+ }
444
+ const manifestTriggers = manifest.triggers.map((trigger) => {
445
+ let keys = trigger[ECHO_ATTR_META]?.keys;
446
+ delete trigger[ECHO_ATTR_META];
447
+ if (!keys?.length) {
448
+ keys = [
449
+ foreignKey("manifest", [
450
+ trigger.function,
451
+ trigger.spec.type
452
+ ].join(":"))
453
+ ];
454
+ }
455
+ return create2(FunctionTrigger, trigger, {
456
+ keys
457
+ });
458
+ });
459
+ const { objects: existing } = await space.db.query(Filter3.schema(FunctionTrigger)).run();
460
+ const { added } = diff2(existing, manifestTriggers, compareForeignKeys);
461
+ added.forEach((trigger) => {
462
+ space.db.add(trigger);
463
+ log5.info("added", {
464
+ meta: getMeta(trigger)
465
+ }, {
466
+ F: __dxlog_file5,
467
+ L: 128,
468
+ S: this,
469
+ C: (f, a) => f(...a)
470
+ });
471
+ });
472
+ if (added.length > 0) {
473
+ await space.db.flush();
474
+ }
475
+ }
476
+ async _open() {
477
+ log5.info("open...", void 0, {
478
+ F: __dxlog_file5,
479
+ L: 137,
480
+ S: this,
481
+ C: (f, a) => f(...a)
482
+ });
483
+ const spaceListSubscription = this._client.spaces.subscribe(async (spaces) => {
484
+ for (const space of spaces) {
485
+ if (this._triggersBySpaceKey.has(space.key)) {
486
+ continue;
487
+ }
488
+ const registered = [];
489
+ this._triggersBySpaceKey.set(space.key, registered);
490
+ await space.waitUntilReady();
491
+ if (this._ctx.disposed) {
492
+ break;
493
+ }
494
+ this._ctx.onDispose(space.db.query(Filter3.schema(FunctionTrigger)).subscribe(async ({ objects: current }) => {
495
+ log5.info("update", {
496
+ space: space.key,
497
+ registered: registered.length,
498
+ current: current.length
499
+ }, {
500
+ F: __dxlog_file5,
501
+ L: 154,
502
+ S: this,
503
+ C: (f, a) => f(...a)
504
+ });
505
+ await this._handleRemovedTriggers(space, current, registered);
506
+ this._handleNewTriggers(space, current, registered);
507
+ }));
508
+ }
509
+ });
510
+ this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
511
+ log5.info("opened", void 0, {
512
+ F: __dxlog_file5,
513
+ L: 163,
514
+ S: this,
515
+ C: (f, a) => f(...a)
516
+ });
517
+ }
518
+ async _close(_) {
519
+ log5.info("close...", void 0, {
520
+ F: __dxlog_file5,
521
+ L: 167,
522
+ S: this,
523
+ C: (f, a) => f(...a)
524
+ });
525
+ this._triggersBySpaceKey.clear();
526
+ log5.info("closed", void 0, {
527
+ F: __dxlog_file5,
528
+ L: 169,
529
+ S: this,
530
+ C: (f, a) => f(...a)
531
+ });
532
+ }
533
+ _handleNewTriggers(space, current, registered) {
534
+ const added = current.filter((candidate) => {
535
+ return candidate.enabled && registered.find((reg) => reg.trigger.id === candidate.id) == null;
536
+ });
537
+ if (added.length > 0) {
538
+ const newRegisteredTriggers = added.map((trigger) => ({
539
+ trigger
540
+ }));
541
+ registered.push(...newRegisteredTriggers);
542
+ log5.info("added", () => ({
543
+ spaceKey: space.key,
544
+ triggers: added.map((trigger) => trigger.function)
545
+ }), {
546
+ F: __dxlog_file5,
547
+ L: 180,
548
+ S: this,
549
+ C: (f, a) => f(...a)
550
+ });
551
+ this.registered.emit({
552
+ space,
553
+ triggers: added
554
+ });
555
+ }
556
+ }
557
+ async _handleRemovedTriggers(space, current, registered) {
558
+ const removed = [];
559
+ for (let i = registered.length - 1; i >= 0; i--) {
560
+ const wasRemoved = current.filter((trigger) => trigger.enabled).find((trigger) => trigger.id === registered[i].trigger.id) == null;
561
+ if (wasRemoved) {
562
+ const unregistered = registered.splice(i, 1)[0];
563
+ await unregistered.activationCtx?.dispose();
564
+ removed.push(unregistered.trigger);
565
+ }
566
+ }
567
+ if (removed.length > 0) {
568
+ log5.info("removed", () => ({
569
+ spaceKey: space.key,
570
+ triggers: removed.map((trigger) => trigger.function)
571
+ }), {
572
+ F: __dxlog_file5,
573
+ L: 206,
574
+ S: this,
575
+ C: (f, a) => f(...a)
576
+ });
577
+ this.removed.emit({
578
+ space,
579
+ triggers: removed
580
+ });
581
+ }
582
+ }
583
+ _getTriggers(space, predicate) {
584
+ const allSpaceTriggers = this._triggersBySpaceKey.get(space.key) ?? [];
585
+ return allSpaceTriggers.filter(predicate).map((trigger) => trigger.trigger);
586
+ }
587
+ };
588
+
589
+ export {
590
+ FunctionRegistry,
591
+ createSubscriptionTrigger,
592
+ createTimerTrigger,
593
+ createWebsocketTrigger,
594
+ createWebSocket,
595
+ TriggerRegistry
596
+ };
597
+ //# sourceMappingURL=chunk-ZZQ234R7.mjs.map