@dxos/functions 0.5.3-main.83788ff → 0.5.3-main.8ffbbae

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