@dxos/functions 0.5.3-main.6621d8f → 0.5.3-main.6f2dfea

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 (76) hide show
  1. package/dist/lib/browser/index.mjs +422 -663
  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 +419 -648
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/handler.d.ts +12 -32
  8. package/dist/types/src/handler.d.ts.map +1 -1
  9. package/dist/types/src/index.d.ts +0 -2
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/runtime/dev-server.d.ts +10 -7
  12. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  13. package/dist/types/src/runtime/scheduler.d.ts +59 -10
  14. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  15. package/dist/types/src/testing/test/handler.d.ts +0 -1
  16. package/dist/types/src/testing/test/handler.d.ts.map +1 -1
  17. package/dist/types/src/types.d.ts +112 -118
  18. package/dist/types/src/types.d.ts.map +1 -1
  19. package/package.json +13 -16
  20. package/schema/functions.json +103 -122
  21. package/src/handler.ts +27 -50
  22. package/src/index.ts +0 -2
  23. package/src/runtime/dev-server.test.ts +35 -15
  24. package/src/runtime/dev-server.ts +22 -40
  25. package/src/runtime/scheduler.test.ts +75 -54
  26. package/src/runtime/scheduler.ts +300 -67
  27. package/src/testing/test/handler.ts +2 -8
  28. package/src/types.ts +40 -56
  29. package/dist/types/src/registry/function-registry.d.ts +0 -24
  30. package/dist/types/src/registry/function-registry.d.ts.map +0 -1
  31. package/dist/types/src/registry/function-registry.test.d.ts +0 -2
  32. package/dist/types/src/registry/function-registry.test.d.ts.map +0 -1
  33. package/dist/types/src/registry/index.d.ts +0 -2
  34. package/dist/types/src/registry/index.d.ts.map +0 -1
  35. package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
  36. package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
  37. package/dist/types/src/testing/index.d.ts +0 -4
  38. package/dist/types/src/testing/index.d.ts.map +0 -1
  39. package/dist/types/src/testing/setup.d.ts +0 -5
  40. package/dist/types/src/testing/setup.d.ts.map +0 -1
  41. package/dist/types/src/testing/types.d.ts +0 -9
  42. package/dist/types/src/testing/types.d.ts.map +0 -1
  43. package/dist/types/src/testing/util.d.ts +0 -3
  44. package/dist/types/src/testing/util.d.ts.map +0 -1
  45. package/dist/types/src/trigger/index.d.ts +0 -2
  46. package/dist/types/src/trigger/index.d.ts.map +0 -1
  47. package/dist/types/src/trigger/trigger-registry.d.ts +0 -40
  48. package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
  49. package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
  50. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
  51. package/dist/types/src/trigger/type/index.d.ts +0 -5
  52. package/dist/types/src/trigger/type/index.d.ts.map +0 -1
  53. package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
  54. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
  55. package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
  56. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
  57. package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
  58. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
  59. package/dist/types/src/trigger/type/websocket-trigger.d.ts +0 -13
  60. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +0 -1
  61. package/src/registry/function-registry.test.ts +0 -105
  62. package/src/registry/function-registry.ts +0 -84
  63. package/src/registry/index.ts +0 -5
  64. package/src/testing/functions-integration.test.ts +0 -99
  65. package/src/testing/index.ts +0 -7
  66. package/src/testing/setup.ts +0 -45
  67. package/src/testing/types.ts +0 -9
  68. package/src/testing/util.ts +0 -16
  69. package/src/trigger/index.ts +0 -5
  70. package/src/trigger/trigger-registry.test.ts +0 -229
  71. package/src/trigger/trigger-registry.ts +0 -176
  72. package/src/trigger/type/index.ts +0 -8
  73. package/src/trigger/type/subscription-trigger.ts +0 -73
  74. package/src/trigger/type/timer-trigger.ts +0 -44
  75. package/src/trigger/type/webhook-trigger.ts +0 -47
  76. package/src/trigger/type/websocket-trigger.ts +0 -91
@@ -2,16 +2,30 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { CronJob } from 'cron';
6
+ import { getPort } from 'get-port-please';
7
+ import http from 'node:http';
5
8
  import path from 'node:path';
9
+ import WebSocket from 'ws';
6
10
 
7
- import { type Space } from '@dxos/client/echo';
11
+ import { TextV0Type } from '@braneframe/types';
12
+ import { debounce, DeferredTask, sleep, Trigger } from '@dxos/async';
13
+ import { type Client, type PublicKey } from '@dxos/client';
14
+ import { createSubscription, Filter, getAutomergeObjectCore, type Query, type Space } from '@dxos/client/echo';
8
15
  import { Context } from '@dxos/context';
16
+ import { invariant } from '@dxos/invariant';
9
17
  import { log } from '@dxos/log';
18
+ import { ComplexMap } from '@dxos/util';
10
19
 
11
- import { type FunctionEventMeta } from '../handler';
12
- import { type FunctionRegistry } from '../registry';
13
- import { type TriggerRegistry } from '../trigger';
14
- import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../types';
20
+ import {
21
+ type FunctionDef,
22
+ type FunctionManifest,
23
+ type FunctionTrigger,
24
+ type SubscriptionTrigger,
25
+ type TimerTrigger,
26
+ type WebhookTrigger,
27
+ type WebsocketTrigger,
28
+ } from '../types';
15
29
 
16
30
  export type Callback = (data: any) => Promise<void | number>;
17
31
 
@@ -24,92 +38,111 @@ export type SchedulerOptions = {
24
38
  * The scheduler triggers function execution based on various triggers.
25
39
  */
26
40
  export class Scheduler {
27
- private _ctx = createContext();
41
+ // Map of mounted functions.
42
+ private readonly _mounts = new ComplexMap<
43
+ { spaceKey: PublicKey; id: string },
44
+ { ctx: Context; trigger: FunctionTrigger }
45
+ >(({ spaceKey, id }) => `${spaceKey.toHex()}:${id}`);
28
46
 
29
47
  constructor(
30
- public readonly functions: FunctionRegistry,
31
- public readonly triggers: TriggerRegistry,
48
+ private readonly _client: Client,
49
+ private readonly _manifest: FunctionManifest,
32
50
  private readonly _options: SchedulerOptions = {},
33
- ) {
34
- this.functions.onFunctionsRegistered.on(async ({ space, newFunctions }) => {
35
- await this._safeActivateTriggers(space, this.triggers.getInactiveTriggers(space), newFunctions);
36
- });
37
- this.triggers.registered.on(async ({ space, triggers }) => {
38
- await this._safeActivateTriggers(space, triggers, this.functions.getFunctions(space));
39
- });
51
+ ) {}
52
+
53
+ get mounts() {
54
+ return Array.from(this._mounts.values()).reduce<FunctionTrigger[]>((acc, { trigger }) => {
55
+ acc.push(trigger);
56
+ return acc;
57
+ }, []);
40
58
  }
41
59
 
42
60
  async start() {
43
- await this._ctx.dispose();
44
- this._ctx = createContext();
45
- await this.functions.open(this._ctx);
46
- await this.triggers.open(this._ctx);
61
+ this._client.spaces.subscribe(async (spaces) => {
62
+ for (const space of spaces) {
63
+ await space.waitUntilReady();
64
+ for (const trigger of this._manifest.triggers ?? []) {
65
+ await this.mount(new Context(), space, trigger);
66
+ }
67
+ }
68
+ });
47
69
  }
48
70
 
49
71
  async stop() {
50
- await this._ctx.dispose();
51
- await this.functions.close();
52
- await this.triggers.close();
72
+ for (const { id, spaceKey } of this._mounts.keys()) {
73
+ await this.unmount(id, spaceKey);
74
+ }
53
75
  }
54
76
 
55
- public async register(space: Space, manifest: FunctionManifest) {
56
- await this.functions.register(space, manifest);
57
- await this.triggers.register(space, manifest);
58
- }
77
+ /**
78
+ * Mount trigger.
79
+ */
80
+ private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {
81
+ const key = { spaceKey: space.key, id: trigger.function };
82
+ const def = this._manifest.functions.find((config) => config.id === trigger.function);
83
+ invariant(def, `Function not found: ${trigger.function}`);
59
84
 
60
- private async _safeActivateTriggers(
61
- space: Space,
62
- triggers: FunctionTrigger[],
63
- functions: FunctionDef[],
64
- ): Promise<void> {
65
- const mountTasks = triggers.map((trigger) => {
66
- return this.activate(space, functions, trigger);
67
- });
68
- await Promise.all(mountTasks).catch(log.catch);
69
- }
85
+ // TODO(burdon): Currently supports only one trigger declaration per function.
86
+ const exists = this._mounts.get(key);
87
+ if (!exists) {
88
+ this._mounts.set(key, { ctx, trigger });
89
+ log('mount', { space: space.key, trigger });
90
+ if (ctx.disposed) {
91
+ return;
92
+ }
93
+
94
+ //
95
+ // Triggers types.
96
+ //
97
+
98
+ if (trigger.timer) {
99
+ await this._createTimer(ctx, space, def, trigger.timer);
100
+ }
70
101
 
71
- private async activate(space: Space, functions: FunctionDef[], fnTrigger: FunctionTrigger) {
72
- const definition = functions.find((def) => def.uri === fnTrigger.function);
73
- if (!definition) {
74
- log.info('function is not found for trigger', { fnTrigger });
75
- return;
102
+ if (trigger.webhook) {
103
+ await this._createWebhook(ctx, space, def, trigger.webhook);
104
+ }
105
+
106
+ if (trigger.websocket) {
107
+ await this._createWebsocket(ctx, space, def, trigger.websocket);
108
+ }
109
+
110
+ if (trigger.subscription) {
111
+ await this._createSubscription(ctx, space, def, trigger.subscription);
112
+ }
76
113
  }
114
+ }
77
115
 
78
- await this.triggers.activate({ space }, fnTrigger, async (args) => {
79
- return this._execFunction(definition, {
80
- meta: fnTrigger.meta,
81
- data: { ...args, spaceKey: space.key },
82
- });
83
- });
84
- log('activated trigger', { space: space.key, trigger: fnTrigger });
116
+ private async unmount(id: string, spaceKey: PublicKey) {
117
+ const key = { id, spaceKey };
118
+ const { ctx } = this._mounts.get(key) ?? {};
119
+ if (ctx) {
120
+ this._mounts.delete(key);
121
+ await ctx.dispose();
122
+ }
85
123
  }
86
124
 
87
- private async _execFunction<TData, TMeta>(
88
- def: FunctionDef,
89
- { data, meta }: { data: TData; meta?: TMeta },
90
- ): Promise<number> {
91
- let status = 0;
125
+ // TODO(burdon): Pass in Space key (common context).
126
+ private async _execFunction(def: FunctionDef, data: any): Promise<number> {
92
127
  try {
93
- // TODO(burdon): Pass in Space key (common context)?
94
- const payload = Object.assign({}, meta && ({ meta } satisfies FunctionEventMeta<TMeta>), data);
95
-
128
+ let status = 0;
96
129
  const { endpoint, callback } = this._options;
97
130
  if (endpoint) {
98
131
  // TODO(burdon): Move out of scheduler (generalize as callback).
99
- const url = path.join(endpoint, def.route);
100
- log.info('exec', { function: def.uri, url });
132
+ const url = path.join(endpoint, def.path);
133
+ log.info('exec', { function: def.id, url });
101
134
  const response = await fetch(url, {
102
135
  method: 'POST',
103
136
  headers: {
104
137
  'Content-Type': 'application/json',
105
138
  },
106
- body: JSON.stringify(payload),
139
+ body: JSON.stringify(data),
107
140
  });
108
141
 
109
142
  status = response.status;
110
143
  } else if (callback) {
111
- log.info('exec', { function: def.uri });
112
- status = (await callback(payload)) ?? 200;
144
+ log.info('exec', { function: def.id });
145
+ status = (await callback(data)) ?? 200;
113
146
  }
114
147
 
115
148
  // Check errors.
@@ -118,14 +151,214 @@ export class Scheduler {
118
151
  }
119
152
 
120
153
  // const result = await response.json();
121
- log.info('done', { function: def.uri, status });
154
+ log.info('done', { function: def.id, status });
155
+ return status;
122
156
  } catch (err: any) {
123
- log.error('error', { function: def.uri, error: err.message });
124
- status = 500;
157
+ log.error('error', { function: def.id, error: err.message });
158
+ return 500;
125
159
  }
160
+ }
161
+
162
+ //
163
+ // Triggers
164
+ //
165
+
166
+ /**
167
+ * Cron timer.
168
+ */
169
+ private async _createTimer(ctx: Context, space: Space, def: FunctionDef, trigger: TimerTrigger) {
170
+ log.info('timer', { space: space.key, trigger });
171
+ const { cron } = trigger;
172
+
173
+ const task = new DeferredTask(ctx, async () => {
174
+ await this._execFunction(def, { spaceKey: space.key });
175
+ });
126
176
 
127
- return status;
177
+ let last = 0;
178
+ let run = 0;
179
+ // https://www.npmjs.com/package/cron#constructor
180
+ const job = CronJob.from({
181
+ cronTime: cron,
182
+ runOnInit: false,
183
+ onTick: () => {
184
+ // TODO(burdon): Check greater than 30s (use cron-parser).
185
+ const now = Date.now();
186
+ const delta = last ? now - last : 0;
187
+ last = now;
188
+
189
+ run++;
190
+ log.info('tick', { space: space.key.truncate(), count: run, delta });
191
+ task.schedule();
192
+ },
193
+ });
194
+
195
+ job.start();
196
+ ctx.onDispose(() => job.stop());
128
197
  }
129
- }
130
198
 
131
- const createContext = () => new Context({ name: 'FunctionScheduler' });
199
+ /**
200
+ * Webhook.
201
+ */
202
+ private async _createWebhook(ctx: Context, space: Space, def: FunctionDef, trigger: WebhookTrigger) {
203
+ log.info('webhook', { space: space.key, trigger });
204
+
205
+ // TODO(burdon): Enable POST hook with payload.
206
+ const server = http.createServer(async (req, res) => {
207
+ if (req.method !== trigger.method) {
208
+ res.statusCode = 405;
209
+ return res.end();
210
+ }
211
+
212
+ res.statusCode = await this._execFunction(def, { spaceKey: space.key });
213
+ res.end();
214
+ });
215
+
216
+ // TODO(burdon): Not used.
217
+ // const DEF_PORT_RANGE = { min: 7500, max: 7599 };
218
+ // const portRange = Object.assign({}, trigger.port, DEF_PORT_RANGE) as WebhookTrigger['port'];
219
+ const port = await getPort({
220
+ random: true,
221
+ // portRange: [portRange!.min, portRange!.max],
222
+ });
223
+
224
+ // TODO(burdon): Update trigger object with actual port.
225
+ server.listen(port, () => {
226
+ log.info('started webhook', { port });
227
+ trigger.port = port;
228
+ });
229
+
230
+ ctx.onDispose(() => {
231
+ server.close();
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Websocket.
237
+ * NOTE: The port must be unique, so the same hook cannot be used for multiple spaces.
238
+ */
239
+ private async _createWebsocket(
240
+ ctx: Context,
241
+ space: Space,
242
+ def: FunctionDef,
243
+ trigger: WebsocketTrigger,
244
+ options: {
245
+ retryDelay: number;
246
+ maxAttempts: number;
247
+ } = {
248
+ retryDelay: 2,
249
+ maxAttempts: 5,
250
+ },
251
+ ) {
252
+ log.info('websocket', { space: space.key, trigger });
253
+ const { url } = trigger;
254
+
255
+ let ws: WebSocket;
256
+ for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
257
+ const open = new Trigger<boolean>();
258
+
259
+ ws = new WebSocket(url);
260
+ Object.assign(ws, {
261
+ onopen: () => {
262
+ log.info('opened', { url });
263
+ if (trigger.init) {
264
+ ws.send(new TextEncoder().encode(JSON.stringify(trigger.init)));
265
+ }
266
+
267
+ open.wake(true);
268
+ },
269
+
270
+ // TODO(burdon): Config retry if server closes?
271
+ onclose: (event) => {
272
+ log.info('closed', { url, code: event.code });
273
+ open.wake(false);
274
+ },
275
+
276
+ onerror: (event) => {
277
+ log.catch(event.error, { url });
278
+ },
279
+
280
+ onmessage: async (event) => {
281
+ try {
282
+ const data = JSON.parse(new TextDecoder().decode(event.data as Uint8Array));
283
+ await this._execFunction(def, { spaceKey: space.key, data });
284
+ } catch (err) {
285
+ log.catch(err, { url });
286
+ }
287
+ },
288
+ } satisfies Partial<WebSocket>);
289
+
290
+ const isOpen = await open.wait();
291
+ if (isOpen) {
292
+ break;
293
+ } else {
294
+ const wait = Math.pow(attempt, 2) * options.retryDelay;
295
+ if (attempt < options.maxAttempts) {
296
+ log.warn(`failed to connect; trying again in ${wait}s`, { attempt });
297
+ await sleep(wait * 1_000);
298
+ }
299
+ }
300
+ }
301
+
302
+ ctx.onDispose(() => {
303
+ ws?.close();
304
+ });
305
+ }
306
+
307
+ /**
308
+ * ECHO subscription.
309
+ */
310
+ private async _createSubscription(ctx: Context, space: Space, def: FunctionDef, trigger: SubscriptionTrigger) {
311
+ log.info('subscription', { space: space.key, trigger });
312
+ const objectIds = new Set<string>();
313
+ const task = new DeferredTask(ctx, async () => {
314
+ await this._execFunction(def, { spaceKey: space.key, objects: Array.from(objectIds) });
315
+ });
316
+
317
+ // TODO(burdon): Don't fire initially.
318
+ // TODO(burdon): Subscription is called THREE times.
319
+ const subscriptions: (() => void)[] = [];
320
+ const subscription = createSubscription(({ added, updated }) => {
321
+ log.info('updated', { added: added.length, updated: updated.length });
322
+ for (const object of added) {
323
+ objectIds.add(object.id);
324
+ }
325
+ for (const object of updated) {
326
+ objectIds.add(object.id);
327
+ }
328
+
329
+ task.schedule();
330
+ });
331
+
332
+ subscriptions.push(() => subscription.unsubscribe());
333
+
334
+ // TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
335
+ // TODO(burdon): Disable trigger if keeps failing.
336
+ const { filter, options: { deep, delay } = {} } = trigger;
337
+ const update = ({ objects }: Query) => {
338
+ subscription.update(objects);
339
+
340
+ // TODO(burdon): Hack to monitor changes to Document's text object.
341
+ if (deep) {
342
+ log.info('update', { objects: objects.length });
343
+ for (const object of objects) {
344
+ const content = object.content;
345
+ if (content instanceof TextV0Type) {
346
+ subscriptions.push(
347
+ getAutomergeObjectCore(content).updates.on(debounce(() => subscription.update([object]), 1_000)),
348
+ );
349
+ }
350
+ }
351
+ }
352
+ };
353
+
354
+ // TODO(burdon): Is Filter.or implemented?
355
+ // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
356
+ // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).
357
+ const query = space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
358
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
359
+
360
+ ctx.onDispose(() => {
361
+ subscriptions.forEach((unsubscribe) => unsubscribe());
362
+ });
363
+ }
364
+ }
@@ -4,12 +4,6 @@
4
4
 
5
5
  import { type FunctionHandler } from '../../handler';
6
6
 
7
- let callHandler: FunctionHandler<any> = async ({ response }) => response.status(200);
8
-
9
- export const setTestCallHandler = (handler: FunctionHandler<any>) => {
10
- callHandler = handler;
11
- };
12
-
13
- export const handler: FunctionHandler<any> = async (args) => {
14
- return callHandler(args);
7
+ export const handler: FunctionHandler<any> = async ({ response }) => {
8
+ return response.status(200);
15
9
  };
package/src/types.ts CHANGED
@@ -2,21 +2,27 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { AST, S, TypedObject } from '@dxos/echo-schema';
5
+ import * as S from '@effect/schema/Schema';
6
6
 
7
- // TODO(burdon): Factor out.
8
- const omitEchoId = <T>(schema: S.Schema<T>): S.Schema<Omit<T, 'id'>> => S.make(AST.omit(schema.ast, ['id']));
7
+ const TimerTriggerSchema = S.struct({
8
+ cron: S.string,
9
+ });
9
10
 
10
- /**
11
- * Type discriminator for TriggerSpec.
12
- * Every spec has a type field of type FunctionTriggerType that we can use to understand which
13
- * type we're working with.
14
- * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions
15
- */
16
- export type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websocket';
11
+ const WebhookTriggerSchema = S.mutable(
12
+ S.struct({
13
+ method: S.string,
14
+ // Assigned port.
15
+ port: S.optional(S.number),
16
+ }),
17
+ );
18
+
19
+ const WebsocketTriggerSchema = S.struct({
20
+ url: S.string,
21
+ init: S.optional(S.record(S.string, S.any)),
22
+ });
17
23
 
18
24
  const SubscriptionTriggerSchema = S.struct({
19
- type: S.literal('subscription'),
25
+ spaceKey: S.optional(S.string),
20
26
  // TODO(burdon): Define query DSL.
21
27
  filter: S.array(
22
28
  S.struct({
@@ -33,69 +39,47 @@ const SubscriptionTriggerSchema = S.struct({
33
39
  }),
34
40
  ),
35
41
  });
36
- export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
37
42
 
38
- const TimerTriggerSchema = S.struct({
39
- type: S.literal('timer'),
40
- cron: S.string,
41
- });
42
- export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
43
+ const FunctionTriggerSchema = S.struct({
44
+ function: S.string.pipe(S.description('Function ID/URI.')),
43
45
 
44
- const WebhookTriggerSchema = S.mutable(
45
- S.struct({
46
- type: S.literal('webhook'),
47
- method: S.string,
48
- // Assigned port.
49
- port: S.optional(S.number),
50
- }),
51
- );
52
- export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
46
+ // Context passed to function.
47
+ context: S.optional(S.record(S.string, S.any)),
53
48
 
54
- const WebsocketTriggerSchema = S.struct({
55
- type: S.literal('websocket'),
56
- url: S.string,
57
- init: S.optional(S.record(S.string, S.any)),
49
+ // Triggers.
50
+ timer: S.optional(TimerTriggerSchema),
51
+ webhook: S.optional(WebhookTriggerSchema),
52
+ websocket: S.optional(WebsocketTriggerSchema),
53
+ subscription: S.optional(SubscriptionTriggerSchema),
58
54
  });
59
- export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
60
55
 
61
- const TriggerSpecSchema = S.union(
62
- TimerTriggerSchema,
63
- WebhookTriggerSchema,
64
- WebsocketTriggerSchema,
65
- SubscriptionTriggerSchema,
66
- );
67
- export type TriggerSpec = TimerTrigger | WebhookTrigger | WebsocketTrigger | SubscriptionTrigger;
56
+ export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
57
+ export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
58
+ export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
59
+ export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
60
+ export type FunctionTrigger = S.Schema.Type<typeof FunctionTriggerSchema>;
68
61
 
69
62
  /**
70
63
  * Function definition.
71
64
  */
72
- export class FunctionDef extends TypedObject({
73
- typename: 'dxos.org/type/FunctionDef',
74
- version: '0.1.0',
75
- })({
76
- uri: S.string,
65
+ // TODO(burdon): Name vs. path?
66
+ const FunctionDefSchema = S.struct({
67
+ id: S.string,
68
+ // name: S.string,
77
69
  description: S.optional(S.string),
78
- route: S.string,
70
+ path: S.string,
79
71
  // TODO(burdon): NPM/GitHub/Docker/CF URL?
80
72
  handler: S.string,
81
- }) {}
73
+ });
82
74
 
83
- export class FunctionTrigger extends TypedObject({
84
- typename: 'dxos.org/type/FunctionTrigger',
85
- version: '0.1.0',
86
- })({
87
- function: S.string.pipe(S.description('Function ID/URI.')),
88
- // Context passed to a function.
89
- meta: S.optional(S.record(S.string, S.any)),
90
- spec: TriggerSpecSchema,
91
- }) {}
75
+ export type FunctionDef = S.Schema.Type<typeof FunctionDefSchema>;
92
76
 
93
77
  /**
94
78
  * Function manifest file.
95
79
  */
96
80
  export const FunctionManifestSchema = S.struct({
97
- functions: S.optional(S.mutable(S.array(omitEchoId(FunctionDef)))),
98
- triggers: S.optional(S.mutable(S.array(omitEchoId(FunctionTrigger)))),
81
+ functions: S.mutable(S.array(FunctionDefSchema)),
82
+ triggers: S.optional(S.mutable(S.array(FunctionTriggerSchema))),
99
83
  });
100
84
 
101
85
  export type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;
@@ -1,24 +0,0 @@
1
- import { Event } from '@dxos/async';
2
- import { type Client } from '@dxos/client';
3
- import { type Space } from '@dxos/client/echo';
4
- import { type Context, Resource } from '@dxos/context';
5
- import { FunctionDef, type FunctionManifest } from '../types';
6
- export type FunctionsRegisteredEvent = {
7
- space: Space;
8
- newFunctions: FunctionDef[];
9
- };
10
- export declare class FunctionRegistry extends Resource {
11
- private readonly _client;
12
- private readonly _functionBySpaceKey;
13
- readonly onFunctionsRegistered: Event<FunctionsRegisteredEvent>;
14
- constructor(_client: Client);
15
- getFunctions(space: Space): FunctionDef[];
16
- /**
17
- * The method loads function definitions from the manifest into the space.
18
- * We first load all the definitions from the space to deduplicate by functionId.
19
- */
20
- register(space: Space, manifest: FunctionManifest): Promise<void>;
21
- protected _open(): Promise<void>;
22
- protected _close(_: Context): Promise<void>;
23
- }
24
- //# sourceMappingURL=function-registry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"function-registry.d.ts","sourceRoot":"","sources":["../../../../src/registry/function-registry.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAkB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIvD,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE9D,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,QAAQ;IAKhC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA4D;IAEhG,SAAgB,qBAAqB,kCAAyC;gBAEjD,OAAO,EAAE,MAAM;IAIrC,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,EAAE;IAIhD;;;OAGG;IAEU,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;cAcrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;cA0BtB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAG3D"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=function-registry.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"function-registry.test.d.ts","sourceRoot":"","sources":["../../../../src/registry/function-registry.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export * from './function-registry';
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/registry/index.ts"],"names":[],"mappings":"AAIA,cAAc,qBAAqB,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=functions-integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"functions-integration.test.d.ts","sourceRoot":"","sources":["../../../../src/testing/functions-integration.test.ts"],"names":[],"mappings":""}
@@ -1,4 +0,0 @@
1
- export * from './setup';
2
- export * from './types';
3
- export * from './util';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/index.ts"],"names":[],"mappings":"AAIA,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC"}
@@ -1,5 +0,0 @@
1
- import { Client, Config } from '@dxos/client';
2
- import { type TestBuilder } from '@dxos/client/testing';
3
- export declare const createInitializedClients: (testBuilder: TestBuilder, count?: number, config?: Config) => Promise<Client[]>;
4
- export declare const createFunctionRuntime: (testBuilder: TestBuilder) => Promise<Client>;
5
- //# sourceMappingURL=setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../../src/testing/setup.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAQxD,eAAO,MAAM,wBAAwB,gBAAuB,WAAW,UAAS,MAAM,WAAe,MAAM,sBAW1G,CAAC;AAEF,eAAO,MAAM,qBAAqB,gBAAuB,WAAW,KAAG,QAAQ,MAAM,CAiBpF,CAAC"}
@@ -1,9 +0,0 @@
1
- declare const TestType_base: import("@dxos/echo-schema").EchoSchemaClass<{
2
- title: string;
3
- } & {
4
- id: string;
5
- }>;
6
- export declare class TestType extends TestType_base {
7
- }
8
- export {};
9
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/testing/types.ts"],"names":[],"mappings":";;;;;AAMA,qBAAa,QAAS,SAAQ,aAE5B;CAAG"}
@@ -1,3 +0,0 @@
1
- import { type Space } from '@dxos/client/echo';
2
- export declare const triggerWebhook: (space: Space, uri: string) => Promise<void>;
3
- //# sourceMappingURL=util.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../../src/testing/util.ts"],"names":[],"mappings":"AAIA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAKvD,eAAO,MAAM,cAAc,UAAiB,KAAK,OAAO,MAAM,kBAM7D,CAAC"}