@dxos/functions 0.8.1 → 0.8.2-main.10c050d

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 (204) hide show
  1. package/dist/lib/browser/bundler/index.mjs +0 -3
  2. package/dist/lib/browser/bundler/index.mjs.map +3 -3
  3. package/dist/lib/browser/edge/index.mjs +63 -7
  4. package/dist/lib/browser/edge/index.mjs.map +4 -4
  5. package/dist/lib/browser/index.mjs +513 -100
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/node/bundler/index.cjs +0 -1
  9. package/dist/lib/node/bundler/index.cjs.map +3 -3
  10. package/dist/lib/node/edge/index.cjs +65 -5
  11. package/dist/lib/node/edge/index.cjs.map +4 -4
  12. package/dist/lib/node/index.cjs +519 -92
  13. package/dist/lib/node/index.cjs.map +4 -4
  14. package/dist/lib/node/meta.json +1 -1
  15. package/dist/lib/node-esm/bundler/index.mjs +0 -1
  16. package/dist/lib/node-esm/bundler/index.mjs.map +3 -3
  17. package/dist/lib/node-esm/edge/index.mjs +64 -6
  18. package/dist/lib/node-esm/edge/index.mjs.map +4 -4
  19. package/dist/lib/node-esm/index.mjs +513 -98
  20. package/dist/lib/node-esm/index.mjs.map +4 -4
  21. package/dist/lib/node-esm/meta.json +1 -1
  22. package/dist/types/src/bundler/bundler.d.ts.map +1 -1
  23. package/dist/types/src/edge/functions.d.ts +3 -3
  24. package/dist/types/src/edge/functions.d.ts.map +1 -1
  25. package/dist/types/src/edge/index.d.ts.map +1 -1
  26. package/dist/types/src/executor/executor.d.ts +8 -0
  27. package/dist/types/src/executor/executor.d.ts.map +1 -0
  28. package/dist/types/src/executor/index.d.ts +2 -0
  29. package/dist/types/src/executor/index.d.ts.map +1 -0
  30. package/dist/types/src/handler.d.ts +23 -67
  31. package/dist/types/src/handler.d.ts.map +1 -1
  32. package/dist/types/src/index.d.ts +5 -3
  33. package/dist/types/src/index.d.ts.map +1 -1
  34. package/dist/types/src/schema.d.ts +57 -0
  35. package/dist/types/src/schema.d.ts.map +1 -0
  36. package/dist/types/src/services/ai.d.ts +9 -0
  37. package/dist/types/src/services/ai.d.ts.map +1 -0
  38. package/dist/types/src/services/credentials.d.ts +30 -0
  39. package/dist/types/src/services/credentials.d.ts.map +1 -0
  40. package/dist/types/src/services/database.d.ts +9 -0
  41. package/dist/types/src/services/database.d.ts.map +1 -0
  42. package/dist/types/src/services/index.d.ts +7 -0
  43. package/dist/types/src/services/index.d.ts.map +1 -0
  44. package/dist/types/src/services/queues.d.ts +10 -0
  45. package/dist/types/src/services/queues.d.ts.map +1 -0
  46. package/dist/types/src/services/service-container.d.ts +25 -0
  47. package/dist/types/src/services/service-container.d.ts.map +1 -0
  48. package/dist/types/src/services/tracing.d.ts +15 -0
  49. package/dist/types/src/services/tracing.d.ts.map +1 -0
  50. package/dist/types/src/trace.d.ts +149 -0
  51. package/dist/types/src/trace.d.ts.map +1 -0
  52. package/dist/types/src/translations.d.ts +2 -1
  53. package/dist/types/src/translations.d.ts.map +1 -1
  54. package/dist/types/src/types.d.ts +407 -0
  55. package/dist/types/src/types.d.ts.map +1 -0
  56. package/dist/types/src/{types/url.d.ts → url.d.ts} +6 -0
  57. package/dist/types/src/url.d.ts.map +1 -0
  58. package/dist/types/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +21 -36
  60. package/src/bundler/bundler.ts +7 -1
  61. package/src/edge/functions.ts +7 -4
  62. package/src/edge/index.ts +4 -0
  63. package/src/executor/executor.ts +47 -0
  64. package/src/executor/index.ts +5 -0
  65. package/src/handler.ts +29 -125
  66. package/src/index.ts +8 -5
  67. package/src/schema.ts +52 -0
  68. package/src/services/ai.ts +15 -0
  69. package/src/services/credentials.ts +55 -0
  70. package/src/services/database.ts +14 -0
  71. package/src/services/index.ts +10 -0
  72. package/src/services/queues.ts +16 -0
  73. package/src/services/service-container.ts +58 -0
  74. package/src/services/tracing.ts +27 -0
  75. package/src/{types/trace.ts → trace.ts} +37 -35
  76. package/src/translations.ts +1 -1
  77. package/src/types.ts +211 -0
  78. package/src/{types/url.ts → url.ts} +5 -0
  79. package/dist/lib/browser/chunk-HI7YZO2K.mjs +0 -482
  80. package/dist/lib/browser/chunk-HI7YZO2K.mjs.map +0 -7
  81. package/dist/lib/browser/chunk-LT4LR4VU.mjs +0 -72
  82. package/dist/lib/browser/chunk-LT4LR4VU.mjs.map +0 -7
  83. package/dist/lib/browser/chunk-RVSG6WTL.mjs +0 -358
  84. package/dist/lib/browser/chunk-RVSG6WTL.mjs.map +0 -7
  85. package/dist/lib/browser/chunk-XRCXIG74.mjs +0 -12
  86. package/dist/lib/browser/chunk-XRCXIG74.mjs.map +0 -7
  87. package/dist/lib/browser/testing/index.mjs +0 -670
  88. package/dist/lib/browser/testing/index.mjs.map +0 -7
  89. package/dist/lib/browser/types/index.mjs +0 -49
  90. package/dist/lib/browser/types/index.mjs.map +0 -7
  91. package/dist/lib/node/chunk-DSUGRAAL.cjs +0 -392
  92. package/dist/lib/node/chunk-DSUGRAAL.cjs.map +0 -7
  93. package/dist/lib/node/chunk-JEQ2X3Z6.cjs +0 -34
  94. package/dist/lib/node/chunk-JEQ2X3Z6.cjs.map +0 -7
  95. package/dist/lib/node/chunk-NXZNXVT3.cjs +0 -94
  96. package/dist/lib/node/chunk-NXZNXVT3.cjs.map +0 -7
  97. package/dist/lib/node/chunk-RXMCVAMJ.cjs +0 -496
  98. package/dist/lib/node/chunk-RXMCVAMJ.cjs.map +0 -7
  99. package/dist/lib/node/testing/index.cjs +0 -687
  100. package/dist/lib/node/testing/index.cjs.map +0 -7
  101. package/dist/lib/node/types/index.cjs +0 -70
  102. package/dist/lib/node/types/index.cjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-DHGBFXSZ.mjs +0 -12
  104. package/dist/lib/node-esm/chunk-DHGBFXSZ.mjs.map +0 -7
  105. package/dist/lib/node-esm/chunk-HBD2FZXO.mjs +0 -358
  106. package/dist/lib/node-esm/chunk-HBD2FZXO.mjs.map +0 -7
  107. package/dist/lib/node-esm/chunk-O2SXVYU5.mjs +0 -72
  108. package/dist/lib/node-esm/chunk-O2SXVYU5.mjs.map +0 -7
  109. package/dist/lib/node-esm/chunk-SQSJO5HI.mjs +0 -482
  110. package/dist/lib/node-esm/chunk-SQSJO5HI.mjs.map +0 -7
  111. package/dist/lib/node-esm/testing/index.mjs +0 -670
  112. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  113. package/dist/lib/node-esm/types/index.mjs +0 -49
  114. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  115. package/dist/types/src/browser/index.d.ts +0 -2
  116. package/dist/types/src/browser/index.d.ts.map +0 -1
  117. package/dist/types/src/function/function-registry.d.ts +0 -25
  118. package/dist/types/src/function/function-registry.d.ts.map +0 -1
  119. package/dist/types/src/function/function-registry.test.d.ts +0 -2
  120. package/dist/types/src/function/function-registry.test.d.ts.map +0 -1
  121. package/dist/types/src/function/index.d.ts +0 -2
  122. package/dist/types/src/function/index.d.ts.map +0 -1
  123. package/dist/types/src/runtime/dev-server.d.ts +0 -52
  124. package/dist/types/src/runtime/dev-server.d.ts.map +0 -1
  125. package/dist/types/src/runtime/dev-server.test.d.ts +0 -2
  126. package/dist/types/src/runtime/dev-server.test.d.ts.map +0 -1
  127. package/dist/types/src/runtime/index.d.ts +0 -3
  128. package/dist/types/src/runtime/index.d.ts.map +0 -1
  129. package/dist/types/src/runtime/scheduler.d.ts +0 -34
  130. package/dist/types/src/runtime/scheduler.d.ts.map +0 -1
  131. package/dist/types/src/runtime/scheduler.test.d.ts +0 -2
  132. package/dist/types/src/runtime/scheduler.test.d.ts.map +0 -1
  133. package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
  134. package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
  135. package/dist/types/src/testing/index.d.ts +0 -5
  136. package/dist/types/src/testing/index.d.ts.map +0 -1
  137. package/dist/types/src/testing/manifest.d.ts +0 -3
  138. package/dist/types/src/testing/manifest.d.ts.map +0 -1
  139. package/dist/types/src/testing/plugin-init.d.ts +0 -6
  140. package/dist/types/src/testing/plugin-init.d.ts.map +0 -1
  141. package/dist/types/src/testing/setup.d.ts +0 -15
  142. package/dist/types/src/testing/setup.d.ts.map +0 -1
  143. package/dist/types/src/testing/test/handler.d.ts +0 -4
  144. package/dist/types/src/testing/test/handler.d.ts.map +0 -1
  145. package/dist/types/src/testing/test/index.d.ts +0 -3
  146. package/dist/types/src/testing/test/index.d.ts.map +0 -1
  147. package/dist/types/src/testing/types.d.ts +0 -10
  148. package/dist/types/src/testing/types.d.ts.map +0 -1
  149. package/dist/types/src/testing/util.d.ts +0 -5
  150. package/dist/types/src/testing/util.d.ts.map +0 -1
  151. package/dist/types/src/trigger/index.d.ts +0 -3
  152. package/dist/types/src/trigger/index.d.ts.map +0 -1
  153. package/dist/types/src/trigger/trigger-registry.d.ts +0 -38
  154. package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
  155. package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
  156. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
  157. package/dist/types/src/trigger/type/index.d.ts +0 -3
  158. package/dist/types/src/trigger/type/index.d.ts.map +0 -1
  159. package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
  160. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
  161. package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
  162. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
  163. package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
  164. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
  165. package/dist/types/src/types/index.d.ts +0 -5
  166. package/dist/types/src/types/index.d.ts.map +0 -1
  167. package/dist/types/src/types/schema.d.ts +0 -53
  168. package/dist/types/src/types/schema.d.ts.map +0 -1
  169. package/dist/types/src/types/trace.d.ts +0 -146
  170. package/dist/types/src/types/trace.d.ts.map +0 -1
  171. package/dist/types/src/types/types.d.ts +0 -265
  172. package/dist/types/src/types/types.d.ts.map +0 -1
  173. package/dist/types/src/types/url.d.ts.map +0 -1
  174. package/dist/types/tools/schema.d.ts +0 -2
  175. package/dist/types/tools/schema.d.ts.map +0 -1
  176. package/schema/functions.json +0 -211
  177. package/src/browser/index.ts +0 -5
  178. package/src/function/function-registry.test.ts +0 -118
  179. package/src/function/function-registry.ts +0 -104
  180. package/src/function/index.ts +0 -5
  181. package/src/runtime/dev-server.test.ts +0 -79
  182. package/src/runtime/dev-server.ts +0 -240
  183. package/src/runtime/index.ts +0 -6
  184. package/src/runtime/scheduler.test.ts +0 -152
  185. package/src/runtime/scheduler.ts +0 -170
  186. package/src/testing/functions-integration.test.ts +0 -65
  187. package/src/testing/index.ts +0 -8
  188. package/src/testing/manifest.ts +0 -15
  189. package/src/testing/plugin-init.ts +0 -20
  190. package/src/testing/setup.ts +0 -109
  191. package/src/testing/test/handler.ts +0 -15
  192. package/src/testing/test/index.ts +0 -7
  193. package/src/testing/types.ts +0 -9
  194. package/src/testing/util.ts +0 -26
  195. package/src/trigger/index.ts +0 -6
  196. package/src/trigger/trigger-registry.test.ts +0 -278
  197. package/src/trigger/trigger-registry.ts +0 -218
  198. package/src/trigger/type/index.ts +0 -7
  199. package/src/trigger/type/subscription-trigger.ts +0 -84
  200. package/src/trigger/type/timer-trigger.ts +0 -48
  201. package/src/trigger/type/webhook-trigger.ts +0 -48
  202. package/src/types/index.ts +0 -8
  203. package/src/types/schema.ts +0 -46
  204. package/src/types/types.ts +0 -163
@@ -1,278 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
6
-
7
- import { sleep, Trigger, waitForCondition } from '@dxos/async';
8
- import { type Client } from '@dxos/client';
9
- import { type Space } from '@dxos/client/echo';
10
- import { TestBuilder } from '@dxos/client/testing';
11
- import { Context } from '@dxos/context';
12
- import { Filter } from '@dxos/echo-db';
13
- import { splitMeta } from '@dxos/echo-schema';
14
- import { create } from '@dxos/live-object';
15
- import { range } from '@dxos/util';
16
-
17
- import { TriggerRegistry } from './trigger-registry';
18
- import { createInitializedClients, TestType, triggerWebhook } from '../testing';
19
- import { type FunctionManifest, FunctionTrigger, TriggerKind } from '../types';
20
-
21
- const manifest: FunctionManifest = {
22
- triggers: [
23
- {
24
- '@meta': {
25
- keys: [
26
- {
27
- source: 'example.com',
28
- id: 'trigger-1',
29
- },
30
- ],
31
- },
32
- function: 'example.com/function/webhook-test',
33
- enabled: true,
34
- spec: {
35
- type: TriggerKind.Webhook,
36
- method: 'GET',
37
- },
38
- },
39
- {
40
- '@meta': {
41
- keys: [
42
- {
43
- source: 'example.com',
44
- id: 'trigger-2',
45
- },
46
- ],
47
- },
48
- function: 'example.com/function/subscription-test',
49
- enabled: true,
50
- spec: {
51
- type: TriggerKind.Subscription,
52
- filter: {
53
- type: TestType.typename,
54
- },
55
- },
56
- },
57
- ],
58
- };
59
-
60
- describe('trigger registry', () => {
61
- let ctx: Context;
62
- let testBuilder: TestBuilder;
63
-
64
- beforeEach(async () => {
65
- ctx = new Context();
66
- testBuilder = new TestBuilder();
67
- });
68
-
69
- afterEach(async () => {
70
- await ctx.dispose();
71
- await testBuilder.destroy();
72
- });
73
-
74
- describe('register', () => {
75
- test('creates new triggers', async () => {
76
- const [client] = await createInitializedClients(testBuilder);
77
- const registry = createRegistry(client);
78
- const space = await client.spaces.create();
79
- await registry.register(space, manifest);
80
- const { objects } = await space.db.query(Filter.schema(FunctionTrigger)).run();
81
- expect(objects.length).to.eq(manifest.triggers?.length);
82
-
83
- const expected = manifest.triggers?.map((trigger) => trigger.function).sort();
84
- expect(objects.map((object: FunctionTrigger) => object.function).sort()).to.deep.eq(expected);
85
- });
86
-
87
- test('set meta', () => {
88
- const trigger = create(FunctionTrigger, {
89
- function: 'example.com/function/webhook-test',
90
- spec: {
91
- type: TriggerKind.Webhook,
92
- method: 'GET',
93
- },
94
- });
95
-
96
- (trigger.meta ??= {}).test = 100;
97
- expect(trigger.meta.test).to.eq(100);
98
- });
99
- });
100
-
101
- // TODO(burdon): Server-only.
102
- describe.skip('activate', () => {
103
- test('invokes the provided callback', async () => {
104
- const [client] = await createInitializedClients(testBuilder);
105
- const space = await client.spaces.create();
106
- const registry = createRegistry(client);
107
- await registry.register(space, manifest);
108
- await registry.open(ctx);
109
- await waitForInactiveTriggers(registry, space);
110
-
111
- const callbackInvoked = new Trigger();
112
- const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
113
- const webhookTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec?.type === 'webhook')!;
114
- await registry.activate(space, webhookTrigger, async () => {
115
- callbackInvoked.wake();
116
- return 200;
117
- });
118
-
119
- setTimeout(() => triggerWebhook(space, webhookTrigger.function!));
120
- await callbackInvoked.wait();
121
- });
122
-
123
- test('removes from inactive list', async () => {
124
- const [client] = await createInitializedClients(testBuilder);
125
- const space = await client.spaces.create();
126
- const registry = createRegistry(client);
127
- await registry.register(space, manifest);
128
- await registry.open(ctx);
129
- await waitForInactiveTriggers(registry, space);
130
-
131
- const inactiveTrigger = registry.getInactiveTriggers(space)[0];
132
- await registry.activate(space, inactiveTrigger, async () => 200);
133
-
134
- const updatedInactiveList = registry.getInactiveTriggers(space);
135
- expect(updatedInactiveList.find((trigger: FunctionTrigger) => trigger.function === inactiveTrigger.function)).to
136
- .be.undefined;
137
- });
138
-
139
- // TODO(burdon): Test enable/disable trigger.
140
- });
141
-
142
- describe('deactivate', () => {
143
- test('trigger object deletion deactivates a trigger', async () => {
144
- const [client] = await createInitializedClients(testBuilder);
145
- const space = await client.spaces.create();
146
- const registry = createRegistry(client);
147
- await registry.register(space, manifest);
148
- await registry.open(ctx);
149
- await waitForInactiveTriggers(registry, space);
150
-
151
- const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
152
- const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec?.type === 'subscription')!;
153
- let count = 0;
154
- await registry.activate(space, echoTrigger, async () => {
155
- count++;
156
- return 200;
157
- });
158
-
159
- space.db.add(create(TestType, { title: '1' }));
160
- await sleep(110);
161
- expect(count).to.eq(1);
162
-
163
- space.db.remove(echoTrigger);
164
- await sleep(110);
165
- space.db.add(create(TestType, { title: '2' }));
166
- await sleep(20);
167
- expect(count).to.eq(1);
168
- });
169
-
170
- test('registry closing deactivates a trigger', async () => {
171
- const [client] = await createInitializedClients(testBuilder);
172
- const space = await client.spaces.create();
173
- const registry = createRegistry(client);
174
- await registry.register(space, manifest);
175
- await registry.open(ctx);
176
- await waitForInactiveTriggers(registry, space);
177
-
178
- const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
179
- const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec?.type === 'subscription')!;
180
- let count = 0;
181
- await registry.activate(space, echoTrigger, async () => {
182
- count++;
183
- return 200;
184
- });
185
-
186
- await registry.close();
187
-
188
- space.db.add(create(TestType, { title: '1' }));
189
- await sleep(20);
190
- expect(count).to.eq(0);
191
- });
192
- });
193
-
194
- describe('trigger events', () => {
195
- test('event fired when all registered are opened', async () => {
196
- const [client] = await createInitializedClients(testBuilder);
197
- const registry = createRegistry(client);
198
- await client.spaces.default.waitUntilReady();
199
- const triggers = createTriggers(client.spaces.default, 3);
200
-
201
- const triggersRegistered = new Trigger<FunctionTrigger[]>();
202
- registry.registered.on((fn) => {
203
- expect(fn.space.key.toHex()).to.eq(client.spaces.default.key.toHex());
204
- triggersRegistered.wake(fn.triggers);
205
- });
206
-
207
- void registry.open(ctx);
208
- const functions = await triggersRegistered.wait();
209
- const expected = triggers.map((object) => object.id).sort();
210
- expect(functions.map((fn) => fn.id).sort()).to.deep.eq(expected);
211
- });
212
-
213
- test('event fired when a new trigger is added', async () => {
214
- const [client] = await createInitializedClients(testBuilder);
215
- const registry = createRegistry(client);
216
- const space = await client.spaces.create();
217
- await space.waitUntilReady();
218
-
219
- const triggerRegistered = new Trigger<FunctionTrigger>();
220
- registry.registered.on((fn) => {
221
- expect(fn.triggers.length).to.eq(1);
222
- triggerRegistered.wake(fn.triggers[0]);
223
- });
224
-
225
- await registry.open(ctx);
226
- await registry.register(space, { triggers: manifest?.triggers?.slice(0, 1) });
227
- const registered = await triggerRegistered.wait();
228
- expect(registered.function).to.eq(manifest.triggers![0].function);
229
- });
230
-
231
- test('event fired when a new trigger is removed', async () => {
232
- const [client] = await createInitializedClients(testBuilder);
233
- const registry = createRegistry(client);
234
- const space = await client.spaces.create();
235
- await space.waitUntilReady();
236
- const triggers = createTriggers(space, 3);
237
-
238
- const triggerLoaded = new Trigger();
239
- registry.registered.on(() => {
240
- triggerLoaded.wake();
241
- });
242
-
243
- const triggerRemoved = new Trigger<FunctionTrigger>();
244
- registry.removed.on((fn) => {
245
- expect(fn.triggers.length).to.eq(1);
246
- triggerRemoved.wake(fn.triggers[0]);
247
- });
248
-
249
- await registry.register(space, manifest);
250
- await registry.open(ctx);
251
- await triggerLoaded.wait();
252
-
253
- space.db.remove(triggers[0]);
254
- const removedTrigger = await triggerRemoved.wait();
255
- expect(removedTrigger.id).to.eq(triggers[0].id);
256
- });
257
- });
258
-
259
- const createRegistry = (client: Client) => {
260
- const registry = new TriggerRegistry(client);
261
- ctx.onDispose(() => registry.close());
262
- return registry;
263
- };
264
-
265
- const createTriggers = (space: Space, count: number) => {
266
- const triggers = range(count, () => {
267
- const { meta, object } = splitMeta(manifest.triggers![0]);
268
- return create(FunctionTrigger, object, meta);
269
- });
270
-
271
- triggers.forEach((trigger) => space.db.add(trigger));
272
- return triggers;
273
- };
274
-
275
- const waitForInactiveTriggers = async (registry: TriggerRegistry, space: Space) => {
276
- await waitForCondition({ condition: () => registry.getInactiveTriggers(space).length > 0 });
277
- };
278
- });
@@ -1,218 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { Event } from '@dxos/async';
6
- import { type Client } from '@dxos/client';
7
- import { create, Filter, getMeta, type Space, compareForeignKeys } from '@dxos/client/echo';
8
- import { Context, Resource } from '@dxos/context';
9
- import { ECHO_ATTR_META, foreignKey } from '@dxos/echo-schema';
10
- import { invariant } from '@dxos/invariant';
11
- import { PublicKey } from '@dxos/keys';
12
- import { log } from '@dxos/log';
13
- import { ComplexMap, diff } from '@dxos/util';
14
-
15
- import { createSubscriptionTrigger, createTimerTrigger } from './type';
16
- import { type FunctionManifest, FunctionTrigger, type TriggerKind, type TriggerType } from '../types';
17
-
18
- type ResponseCode = number;
19
-
20
- export type TriggerCallback = (args: object) => Promise<ResponseCode>;
21
-
22
- // TODO(burdon): Make object?
23
- export type TriggerFactory<Spec extends TriggerType, Options = any> = (
24
- ctx: Context,
25
- space: Space,
26
- spec: Spec,
27
- callback: TriggerCallback,
28
- options?: Options,
29
- ) => Promise<void>;
30
-
31
- export type TriggerFactoryMap = Partial<Record<TriggerKind, TriggerFactory<any>>>;
32
-
33
- const triggerFactory: TriggerFactoryMap = {
34
- timer: createTimerTrigger,
35
- // TODO(burdon): Cannot use in browser.
36
- // webhook: createWebhookTrigger,
37
- subscription: createSubscriptionTrigger,
38
- };
39
-
40
- export type TriggerEvent = {
41
- space: Space;
42
- triggers: FunctionTrigger[];
43
- };
44
-
45
- type RegisteredTrigger = {
46
- activationCtx?: Context;
47
- trigger: FunctionTrigger;
48
- };
49
-
50
- export class TriggerRegistry extends Resource {
51
- private readonly _triggersBySpaceKey = new ComplexMap<PublicKey, RegisteredTrigger[]>(PublicKey.hash);
52
-
53
- public readonly registered = new Event<TriggerEvent>();
54
- public readonly removed = new Event<TriggerEvent>();
55
-
56
- constructor(
57
- private readonly _client: Client,
58
- private readonly _options?: TriggerFactoryMap,
59
- ) {
60
- super();
61
- }
62
-
63
- public getActiveTriggers(space: Space): FunctionTrigger[] {
64
- return this._getTriggers(space, (t) => t.activationCtx != null);
65
- }
66
-
67
- public getInactiveTriggers(space: Space): FunctionTrigger[] {
68
- return this._getTriggers(space, (t) => t.activationCtx == null);
69
- }
70
-
71
- /**
72
- * Set callback for trigger.
73
- */
74
- public async activate(space: Space, trigger: FunctionTrigger, callback: TriggerCallback): Promise<void> {
75
- log('activate', { space: space.key, trigger });
76
-
77
- const activationCtx = new Context({ name: `FunctionTrigger-${trigger.function}` });
78
- this._ctx.onDispose(() => activationCtx.dispose());
79
- const registeredTrigger = this._triggersBySpaceKey.get(space.key)?.find((reg) => reg.trigger.id === trigger.id);
80
- invariant(registeredTrigger, `Trigger is not registered: ${trigger.function}`);
81
- registeredTrigger.activationCtx = activationCtx;
82
-
83
- try {
84
- // Create trigger.
85
- invariant(trigger.spec);
86
- const options = this._options?.[trigger.spec.type];
87
- const createTrigger = triggerFactory[trigger.spec.type];
88
- invariant(createTrigger, `Trigger factory not found: ${trigger.spec.type}`);
89
- await createTrigger(activationCtx, space, trigger.spec, callback, options);
90
- } catch (err) {
91
- delete registeredTrigger.activationCtx;
92
- throw err;
93
- }
94
- }
95
-
96
- /**
97
- * Loads triggers from the manifest into the space.
98
- */
99
- public async register(space: Space, manifest: FunctionManifest): Promise<void> {
100
- log('register', { space: space.key });
101
- if (!manifest.triggers?.length) {
102
- return;
103
- }
104
-
105
- if (!space.db.graph.schemaRegistry.hasSchema(FunctionTrigger)) {
106
- space.db.graph.schemaRegistry.addSchema([FunctionTrigger]);
107
- }
108
-
109
- // Create FK to enable syncing if none are set (NOTE: Possible collision).
110
- const manifestTriggers = manifest.triggers.map((trigger) => {
111
- let keys = trigger[ECHO_ATTR_META]?.keys;
112
- delete trigger[ECHO_ATTR_META];
113
- if (!keys?.length) {
114
- keys = [foreignKey('manifest', [trigger.function, trigger.spec?.type].join(':'))];
115
- }
116
-
117
- return create(FunctionTrigger, trigger, { keys });
118
- });
119
-
120
- // Sync triggers.
121
- const { objects: existing } = await space.db.query(Filter.schema(FunctionTrigger)).run();
122
- const { added } = diff(existing, manifestTriggers, compareForeignKeys);
123
-
124
- // TODO(burdon): Update existing.
125
- added.forEach((trigger) => {
126
- space.db.add(trigger);
127
- log.info('added', { meta: getMeta(trigger) });
128
- });
129
-
130
- if (added.length > 0) {
131
- await space.db.flush();
132
- }
133
- }
134
-
135
- protected override async _open(): Promise<void> {
136
- log.info('open...');
137
- const spaceListSubscription = this._client.spaces.subscribe(async (spaces) => {
138
- for (const space of spaces) {
139
- if (this._triggersBySpaceKey.has(space.key)) {
140
- continue;
141
- }
142
-
143
- const registered: RegisteredTrigger[] = [];
144
- this._triggersBySpaceKey.set(space.key, registered);
145
- await space.waitUntilReady();
146
- if (this._ctx.disposed) {
147
- break;
148
- }
149
-
150
- // Subscribe to updates.
151
- this._ctx.onDispose(
152
- space.db.query(Filter.schema(FunctionTrigger)).subscribe(async ({ objects: current }) => {
153
- log.info('update', { space: space.key, registered: registered.length, current: current.length });
154
- await this._handleRemovedTriggers(space, current, registered);
155
- this._handleNewTriggers(space, current, registered);
156
- }),
157
- );
158
- }
159
- });
160
-
161
- this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
162
- log.info('opened');
163
- }
164
-
165
- protected override async _close(_: Context): Promise<void> {
166
- log.info('close...');
167
- this._triggersBySpaceKey.clear();
168
- log.info('closed');
169
- }
170
-
171
- private _handleNewTriggers(space: Space, current: FunctionTrigger[], registered: RegisteredTrigger[]) {
172
- const added = current.filter((candidate) => {
173
- return candidate.enabled && registered.find((reg) => reg.trigger.id === candidate.id) == null;
174
- });
175
-
176
- if (added.length > 0) {
177
- const newRegisteredTriggers: RegisteredTrigger[] = added.map((trigger) => ({ trigger }));
178
- registered.push(...newRegisteredTriggers);
179
- log.info('added', () => ({
180
- spaceKey: space.key,
181
- triggers: added.map((trigger) => trigger.function),
182
- }));
183
-
184
- this.registered.emit({ space, triggers: added });
185
- }
186
- }
187
-
188
- private async _handleRemovedTriggers(
189
- space: Space,
190
- current: FunctionTrigger[],
191
- registered: RegisteredTrigger[],
192
- ): Promise<void> {
193
- const removed: FunctionTrigger[] = [];
194
- for (let i = registered.length - 1; i >= 0; i--) {
195
- const wasRemoved =
196
- current.filter((trigger) => trigger.enabled).find((trigger) => trigger.id === registered[i].trigger.id) == null;
197
- if (wasRemoved) {
198
- const unregistered = registered.splice(i, 1)[0];
199
- await unregistered.activationCtx?.dispose();
200
- removed.push(unregistered.trigger);
201
- }
202
- }
203
-
204
- if (removed.length > 0) {
205
- log.info('removed', () => ({
206
- spaceKey: space.key,
207
- triggers: removed.map((trigger) => trigger.function),
208
- }));
209
-
210
- this.removed.emit({ space, triggers: removed });
211
- }
212
- }
213
-
214
- private _getTriggers(space: Space, predicate: (trigger: RegisteredTrigger) => boolean): FunctionTrigger[] {
215
- const allSpaceTriggers = this._triggersBySpaceKey.get(space.key) ?? [];
216
- return allSpaceTriggers.filter(predicate).map((trigger) => trigger.trigger);
217
- }
218
- }
@@ -1,7 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './subscription-trigger';
6
- export * from './timer-trigger';
7
- // export * from './webhook-trigger';
@@ -1,84 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { debounce, UpdateScheduler } from '@dxos/async';
6
- import { Filter, type Space } from '@dxos/client/echo';
7
- import { type Context } from '@dxos/context';
8
- import { createSubscription, type Query } from '@dxos/echo-db';
9
- import { log } from '@dxos/log';
10
-
11
- import type { SubscriptionTrigger } from '../../types';
12
- import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
13
-
14
- export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = async (
15
- ctx: Context,
16
- space: Space,
17
- spec: SubscriptionTrigger,
18
- callback: TriggerCallback,
19
- ) => {
20
- const objectIds = new Set<string>();
21
- const task = new UpdateScheduler(
22
- ctx,
23
- async () => {
24
- if (objectIds.size > 0) {
25
- const objects = Array.from(objectIds);
26
- objectIds.clear();
27
- await callback({ objects });
28
- }
29
- },
30
- { maxFrequency: 4 },
31
- );
32
-
33
- // TODO(burdon): Factor out diff.
34
- // TODO(burdon): Don't fire initially?
35
- // TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
36
- const subscriptions: (() => void)[] = [];
37
- const subscription = createSubscription(({ added, updated }) => {
38
- const sizeBefore = objectIds.size;
39
- for (const object of added) {
40
- objectIds.add(object.id);
41
- }
42
- for (const object of updated) {
43
- objectIds.add(object.id);
44
- }
45
- if (objectIds.size > sizeBefore) {
46
- log.info('updated', { added: added.length, updated: updated.length });
47
- task.trigger();
48
- }
49
- });
50
-
51
- subscriptions.push(() => subscription.unsubscribe());
52
-
53
- // TODO(burdon): Disable trigger if keeps failing.
54
- const { filter, options: { deep, delay } = {} } = spec;
55
- const update = ({ objects }: Query) => {
56
- log.info('update', { objects: objects.length });
57
- subscription.update(objects);
58
-
59
- // TODO(burdon): Hack to monitor changes to Document's text object.
60
- if (deep) {
61
- // TODO(dmaretskyi): Removed to not have dependency on markdown-plugin.
62
- // for (const object of objects) {
63
- // const content = object.content;
64
- // if (content instanceof TextType) {
65
- // subscriptions.push(getObjectCore(content).updates.on(debounce(() => subscription.update([object]), 1_000)));
66
- // }
67
- // }
68
- }
69
- };
70
-
71
- // TODO(burdon): OR not working.
72
- // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
73
- // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).
74
- log.info('subscription', { filter });
75
- // const query = triggerCtx.space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
76
- if (filter.type) {
77
- const query = space.db.query(Filter.typename(filter.type, filter.props));
78
- subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
79
- }
80
-
81
- ctx.onDispose(() => {
82
- subscriptions.forEach((unsubscribe) => unsubscribe());
83
- });
84
- };
@@ -1,48 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { parseCronExpression } from 'cron-schedule';
6
-
7
- import { DeferredTask } from '@dxos/async';
8
- import { type Space } from '@dxos/client/echo';
9
- import { type Context } from '@dxos/context';
10
- import { log } from '@dxos/log';
11
-
12
- import type { TimerTrigger } from '../../types';
13
- import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
14
-
15
- export const createTimerTrigger: TriggerFactory<TimerTrigger> = async (
16
- ctx: Context,
17
- space: Space,
18
- spec: TimerTrigger,
19
- callback: TriggerCallback,
20
- ) => {
21
- const task = new DeferredTask(ctx, async () => {
22
- await callback({});
23
- });
24
-
25
- let last = 0;
26
- let run = 0;
27
- const schedule = parseCronExpression(spec.cron);
28
- const getRunTimeout = () => Date.now() - schedule.getNextDate().getTime();
29
- const runCron = () => {
30
- if (ctx.disposed) {
31
- return;
32
- }
33
- // TODO(burdon): Check greater than 30s (use cron-parser).
34
- const now = Date.now();
35
- const delta = last ? now - last : 0;
36
- last = now;
37
-
38
- run++;
39
- log.info('tick', { space: space.key.truncate(), count: run, delta });
40
- task.schedule();
41
-
42
- timeout = setTimeout(runCron, getRunTimeout());
43
- };
44
-
45
- let timeout = setTimeout(runCron, getRunTimeout());
46
-
47
- ctx.onDispose(() => clearTimeout(timeout));
48
- };
@@ -1,48 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { getPort } from 'get-port-please';
6
- import http from 'node:http';
7
-
8
- import { type Space } from '@dxos/client/echo';
9
- import { type Context } from '@dxos/context';
10
- import { log } from '@dxos/log';
11
-
12
- import type { WebhookTrigger } from '../../types';
13
- import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
14
-
15
- export const createWebhookTrigger: TriggerFactory<WebhookTrigger> = async (
16
- ctx: Context,
17
- space: Space,
18
- spec: WebhookTrigger,
19
- callback: TriggerCallback,
20
- ) => {
21
- // TODO(burdon): Enable POST hook with payload.
22
- const server = http.createServer(async (req, res) => {
23
- if (req.method !== spec.method) {
24
- res.statusCode = 405;
25
- return res.end();
26
- }
27
- res.statusCode = await callback({});
28
- res.end();
29
- });
30
-
31
- // TODO(burdon): Not used.
32
- // const DEF_PORT_RANGE = { min: 7500, max: 7599 };
33
- // const portRange = Object.assign({}, trigger.port, DEF_PORT_RANGE) as WebhookTrigger['port'];
34
- const port = await getPort({
35
- random: true,
36
- // portRange: [portRange!.min, portRange!.max],
37
- });
38
-
39
- // TODO(burdon): Update trigger object with actual port.
40
- server.listen(port, () => {
41
- log.info('started webhook', { port });
42
- spec.port = port;
43
- });
44
-
45
- ctx.onDispose(() => {
46
- server.close();
47
- });
48
- };
@@ -1,8 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './schema';
6
- export * from './types';
7
- export * from './trace';
8
- export * from './url';