@dxos/functions 0.5.3-main.bc67fdb → 0.5.3-main.c8ad1bb

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 (73) hide show
  1. package/dist/lib/browser/index.mjs +642 -424
  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 +627 -421
  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 +2 -0
  8. package/dist/types/src/index.d.ts.map +1 -1
  9. package/dist/types/src/registry/function-registry.d.ts +24 -0
  10. package/dist/types/src/registry/function-registry.d.ts.map +1 -0
  11. package/dist/types/src/registry/function-registry.test.d.ts +2 -0
  12. package/dist/types/src/registry/function-registry.test.d.ts.map +1 -0
  13. package/dist/types/src/registry/index.d.ts +2 -0
  14. package/dist/types/src/registry/index.d.ts.map +1 -0
  15. package/dist/types/src/runtime/dev-server.d.ts +7 -10
  16. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  17. package/dist/types/src/runtime/scheduler.d.ts +10 -59
  18. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  19. package/dist/types/src/testing/functions-integration.test.d.ts +2 -0
  20. package/dist/types/src/testing/functions-integration.test.d.ts.map +1 -0
  21. package/dist/types/src/testing/index.d.ts +4 -0
  22. package/dist/types/src/testing/index.d.ts.map +1 -0
  23. package/dist/types/src/testing/setup.d.ts +5 -0
  24. package/dist/types/src/testing/setup.d.ts.map +1 -0
  25. package/dist/types/src/testing/test/handler.d.ts +1 -0
  26. package/dist/types/src/testing/test/handler.d.ts.map +1 -1
  27. package/dist/types/src/testing/types.d.ts +9 -0
  28. package/dist/types/src/testing/types.d.ts.map +1 -0
  29. package/dist/types/src/testing/util.d.ts +3 -0
  30. package/dist/types/src/testing/util.d.ts.map +1 -0
  31. package/dist/types/src/trigger/index.d.ts +2 -0
  32. package/dist/types/src/trigger/index.d.ts.map +1 -0
  33. package/dist/types/src/trigger/trigger-registry.d.ts +40 -0
  34. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -0
  35. package/dist/types/src/trigger/trigger-registry.test.d.ts +2 -0
  36. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +1 -0
  37. package/dist/types/src/trigger/type/index.d.ts +5 -0
  38. package/dist/types/src/trigger/type/index.d.ts.map +1 -0
  39. package/dist/types/src/trigger/type/subscription-trigger.d.ts +4 -0
  40. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -0
  41. package/dist/types/src/trigger/type/timer-trigger.d.ts +4 -0
  42. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +1 -0
  43. package/dist/types/src/trigger/type/webhook-trigger.d.ts +4 -0
  44. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -0
  45. package/dist/types/src/trigger/type/websocket-trigger.d.ts +13 -0
  46. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -0
  47. package/dist/types/src/types.d.ts +118 -112
  48. package/dist/types/src/types.d.ts.map +1 -1
  49. package/package.json +16 -13
  50. package/schema/functions.json +121 -107
  51. package/src/index.ts +2 -0
  52. package/src/registry/function-registry.test.ts +105 -0
  53. package/src/registry/function-registry.ts +84 -0
  54. package/src/registry/index.ts +5 -0
  55. package/src/runtime/dev-server.test.ts +15 -35
  56. package/src/runtime/dev-server.ts +36 -18
  57. package/src/runtime/scheduler.test.ts +54 -75
  58. package/src/runtime/scheduler.ts +58 -298
  59. package/src/testing/functions-integration.test.ts +99 -0
  60. package/src/testing/index.ts +7 -0
  61. package/src/testing/setup.ts +45 -0
  62. package/src/testing/test/handler.ts +8 -2
  63. package/src/testing/types.ts +9 -0
  64. package/src/testing/util.ts +16 -0
  65. package/src/trigger/index.ts +5 -0
  66. package/src/trigger/trigger-registry.test.ts +229 -0
  67. package/src/trigger/trigger-registry.ts +176 -0
  68. package/src/trigger/type/index.ts +8 -0
  69. package/src/trigger/type/subscription-trigger.ts +73 -0
  70. package/src/trigger/type/timer-trigger.ts +44 -0
  71. package/src/trigger/type/webhook-trigger.ts +47 -0
  72. package/src/trigger/type/websocket-trigger.ts +91 -0
  73. package/src/types.ts +56 -42
@@ -9,14 +9,15 @@ import { join } from 'node:path';
9
9
 
10
10
  import { Event, Trigger } from '@dxos/async';
11
11
  import { type Client } from '@dxos/client';
12
+ import { Context } from '@dxos/context';
12
13
  import { invariant } from '@dxos/invariant';
13
14
  import { log } from '@dxos/log';
14
15
 
15
16
  import { type FunctionContext, type FunctionEvent, type FunctionHandler, type FunctionResponse } from '../handler';
16
- import { type FunctionDef, type FunctionManifest } from '../types';
17
+ import { type FunctionRegistry } from '../registry';
18
+ import { type FunctionDef } from '../types';
17
19
 
18
20
  export type DevServerOptions = {
19
- manifest: FunctionManifest;
20
21
  baseDir: string;
21
22
  port?: number;
22
23
  reload?: boolean;
@@ -27,6 +28,8 @@ export type DevServerOptions = {
27
28
  * Functions dev server provides a local HTTP server for testing functions.
28
29
  */
29
30
  export class DevServer {
31
+ private _ctx = createContext();
32
+
30
33
  // Function handlers indexed by name (URL path).
31
34
  private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};
32
35
 
@@ -41,8 +44,15 @@ export class DevServer {
41
44
  // prettier-ignore
42
45
  constructor(
43
46
  private readonly _client: Client,
47
+ private readonly _functionsRegistry: FunctionRegistry,
44
48
  private readonly _options: DevServerOptions,
45
- ) {}
49
+ ) {
50
+ this._functionsRegistry.onFunctionsRegistered.on(async ({ newFunctions }) => {
51
+ newFunctions.forEach((def) => this._load(def));
52
+ await this._safeUpdateRegistration();
53
+ log('new functions loaded', { newFunctions });
54
+ });
55
+ }
46
56
 
47
57
  get stats() {
48
58
  return {
@@ -63,19 +73,10 @@ export class DevServer {
63
73
  return Object.values(this._handlers);
64
74
  }
65
75
 
66
- async initialize() {
67
- for (const def of this._options.manifest.functions) {
68
- try {
69
- await this._load(def);
70
- } catch (err) {
71
- log.error('parsing function (check manifest)', err);
72
- }
73
- }
74
- }
75
-
76
76
  async start() {
77
77
  invariant(!this._server);
78
78
  log.info('starting...');
79
+ this._ctx = createContext();
79
80
 
80
81
  // TODO(burdon): Move to hono.
81
82
  const app = express();
@@ -107,12 +108,14 @@ export class DevServer {
107
108
  // Register functions.
108
109
  const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
109
110
  endpoint: this.endpoint,
110
- functions: this.functions.map(({ def: { id, path } }) => ({ id, path })),
111
111
  });
112
112
 
113
113
  log.info('registered', { endpoint });
114
114
  this._proxy = endpoint;
115
115
  this._functionServiceRegistration = registrationId;
116
+
117
+ // Open after registration, so that it can be updated with the list of function definitions.
118
+ await this._functionsRegistry.open(this._ctx);
116
119
  } catch (err: any) {
117
120
  await this.stop();
118
121
  throw new Error('FunctionRegistryService not available (check plugin is configured).');
@@ -156,9 +159,9 @@ export class DevServer {
156
159
  * Load function.
157
160
  */
158
161
  private async _load(def: FunctionDef, force = false) {
159
- const { id, path, handler } = def;
162
+ const { uri, route, handler } = def;
160
163
  const filePath = join(this._options.baseDir, handler);
161
- log.info('loading', { id, force });
164
+ log.info('loading', { uri, force });
162
165
 
163
166
  // Remove from cache.
164
167
  if (force) {
@@ -169,13 +172,26 @@ export class DevServer {
169
172
  });
170
173
  }
171
174
 
175
+ // TODO(burdon): Import types.
172
176
  // eslint-disable-next-line @typescript-eslint/no-var-requires
173
177
  const module = require(filePath);
174
178
  if (typeof module.default !== 'function') {
175
- throw new Error(`Handler must export default function: ${id}`);
179
+ throw new Error(`Handler must export default function: ${uri}`);
176
180
  }
177
181
 
178
- this._handlers[path] = { def, handler: module.default };
182
+ this._handlers[route] = { def, handler: module.default };
183
+ }
184
+
185
+ private async _safeUpdateRegistration(): Promise<void> {
186
+ invariant(this._functionServiceRegistration);
187
+ try {
188
+ await this._client.services.services.FunctionRegistryService!.updateRegistration({
189
+ registrationId: this._functionServiceRegistration,
190
+ functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
191
+ });
192
+ } catch (e) {
193
+ log.catch(e);
194
+ }
179
195
  }
180
196
 
181
197
  /**
@@ -214,3 +230,5 @@ export class DevServer {
214
230
  return statusCode;
215
231
  }
216
232
  }
233
+
234
+ const createContext = () => new Context({ name: 'DevServer' });
@@ -6,40 +6,43 @@ import { expect } from 'chai';
6
6
  import WebSocket from 'ws';
7
7
 
8
8
  import { Trigger } from '@dxos/async';
9
- import { Client } from '@dxos/client';
9
+ import { type Client } from '@dxos/client';
10
10
  import { TestBuilder } from '@dxos/client/testing';
11
- import { create, S, TypedObject } from '@dxos/echo-schema';
11
+ import { create } from '@dxos/echo-schema';
12
12
  import { describe, test } from '@dxos/test';
13
13
 
14
- import { Scheduler } from './scheduler';
15
- import { type FunctionManifest, type WebhookTrigger } from '../types';
14
+ import { Scheduler, type SchedulerOptions } from './scheduler';
15
+ import { FunctionRegistry } from '../registry';
16
+ import { createInitializedClients, TestType, triggerWebhook } from '../testing';
17
+ import { TriggerRegistry } from '../trigger';
18
+ import { type FunctionManifest } from '../types';
16
19
 
17
20
  // TODO(burdon): Test we can add and remove triggers.
18
21
  describe('scheduler', () => {
22
+ let testBuilder: TestBuilder;
19
23
  let client: Client;
20
24
  before(async () => {
21
- const testBuilder = new TestBuilder();
22
- client = new Client({ services: testBuilder.createLocalClientServices() });
23
- await client.initialize();
24
- await client.halo.createIdentity();
25
+ testBuilder = new TestBuilder();
26
+ client = (await createInitializedClients(testBuilder, 1))[0];
25
27
  });
26
28
  after(async () => {
27
- await client.destroy();
29
+ await testBuilder.destroy();
28
30
  });
29
31
 
30
32
  test('timer', async () => {
31
33
  const manifest: FunctionManifest = {
32
34
  functions: [
33
35
  {
34
- id: 'example.com/function/test',
35
- path: '/test',
36
+ uri: 'example.com/function/test',
37
+ route: '/test',
36
38
  handler: 'test',
37
39
  },
38
40
  ],
39
41
  triggers: [
40
42
  {
41
43
  function: 'example.com/function/test',
42
- timer: {
44
+ spec: {
45
+ type: 'timer',
43
46
  cron: '0/1 * * * * *', // Every 1s.
44
47
  },
45
48
  },
@@ -48,18 +51,13 @@ describe('scheduler', () => {
48
51
 
49
52
  let count = 0;
50
53
  const done = new Trigger();
51
- const scheduler = new Scheduler(client, manifest, {
52
- callback: async () => {
53
- if (++count === 3) {
54
- done.wake();
55
- }
56
- },
54
+ const scheduler = createScheduler(async () => {
55
+ if (++count === 3) {
56
+ done.wake();
57
+ }
57
58
  });
58
-
59
+ await scheduler.register(client.spaces.default, manifest);
59
60
  await scheduler.start();
60
- after(async () => {
61
- await scheduler.stop();
62
- });
63
61
 
64
62
  await done.wait({ timeout: 5_000 });
65
63
  expect(count).to.equal(3);
@@ -69,15 +67,16 @@ describe('scheduler', () => {
69
67
  const manifest: FunctionManifest = {
70
68
  functions: [
71
69
  {
72
- id: 'example.com/function/test',
73
- path: '/test',
70
+ uri: 'example.com/function/test',
71
+ route: '/test',
74
72
  handler: 'test',
75
73
  },
76
74
  ],
77
75
  triggers: [
78
76
  {
79
77
  function: 'example.com/function/test',
80
- webhook: {
78
+ spec: {
79
+ type: 'webhook',
81
80
  method: 'GET',
82
81
  },
83
82
  },
@@ -85,23 +84,14 @@ describe('scheduler', () => {
85
84
  };
86
85
 
87
86
  const done = new Trigger();
88
- const scheduler = new Scheduler(client, manifest, {
89
- callback: async () => {
90
- done.wake();
91
- },
87
+ const scheduler = createScheduler(async () => {
88
+ done.wake();
92
89
  });
93
-
90
+ const space = await client.spaces.create();
91
+ await scheduler.register(space, manifest);
94
92
  await scheduler.start();
95
- after(async () => {
96
- await scheduler.stop();
97
- });
98
93
 
99
- setTimeout(() => {
100
- const mount: WebhookTrigger = scheduler.mounts.find(
101
- (mount) => mount.function === 'example.com/function/test',
102
- )!.webhook!;
103
- void fetch(`http://localhost:${mount.port}`);
104
- });
94
+ setTimeout(async () => triggerWebhook(space, manifest.functions![0].uri));
105
95
 
106
96
  await done.wait();
107
97
  });
@@ -110,15 +100,16 @@ describe('scheduler', () => {
110
100
  const manifest: FunctionManifest = {
111
101
  functions: [
112
102
  {
113
- id: 'example.com/function/test',
114
- path: '/test',
103
+ uri: 'example.com/function/test',
104
+ route: '/test',
115
105
  handler: 'test',
116
106
  },
117
107
  ],
118
108
  triggers: [
119
109
  {
120
110
  function: 'example.com/function/test',
121
- websocket: {
111
+ spec: {
112
+ type: 'websocket',
122
113
  // url: 'https://hub.dxos.network/api/mailbox/test',
123
114
  url: 'http://localhost:8081',
124
115
  init: {
@@ -130,16 +121,11 @@ describe('scheduler', () => {
130
121
  };
131
122
 
132
123
  const done = new Trigger();
133
- const scheduler = new Scheduler(client, manifest, {
134
- callback: async (data) => {
135
- done.wake();
136
- },
124
+ const scheduler = createScheduler(async () => {
125
+ done.wake();
137
126
  });
138
-
127
+ await scheduler.register(client.spaces.default, manifest);
139
128
  await scheduler.start();
140
- after(async () => {
141
- await scheduler.stop();
142
- });
143
129
 
144
130
  // Test server.
145
131
  setTimeout(() => {
@@ -157,29 +143,20 @@ describe('scheduler', () => {
157
143
  });
158
144
 
159
145
  test('subscription', async () => {
160
- class TestType extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
161
- title: S.string,
162
- }) {}
163
- client.addSchema(TestType);
164
-
165
146
  const manifest: FunctionManifest = {
166
147
  functions: [
167
148
  {
168
- id: 'example.com/function/test',
169
- path: '/test',
149
+ uri: 'example.com/function/test',
150
+ route: '/test',
170
151
  handler: 'test',
171
152
  },
172
153
  ],
173
154
  triggers: [
174
155
  {
175
156
  function: 'example.com/function/test',
176
- subscription: {
177
- spaceKey: client.spaces.default.key.toHex(),
178
- filter: [
179
- {
180
- type: TestType.typename,
181
- },
182
- ],
157
+ spec: {
158
+ type: 'subscription',
159
+ filter: [{ type: TestType.typename }],
183
160
  },
184
161
  },
185
162
  ],
@@ -187,20 +164,14 @@ describe('scheduler', () => {
187
164
 
188
165
  let count = 0;
189
166
  const done = new Trigger();
190
- const scheduler = new Scheduler(client, manifest, {
191
- callback: async () => {
192
- if (++count === 2) {
193
- done.wake();
194
- }
195
- },
167
+ const scheduler = createScheduler(async () => {
168
+ if (++count === 2) {
169
+ done.wake();
170
+ }
196
171
  });
197
-
172
+ await scheduler.register(client.spaces.default, manifest);
198
173
  await scheduler.start();
199
- after(async () => {
200
- await scheduler.stop();
201
- });
202
174
 
203
- // TODO(burdon): Query for Expando?
204
175
  setTimeout(() => {
205
176
  const space = client.spaces.default;
206
177
  const object = create(TestType, { title: 'Hello world!' });
@@ -209,4 +180,12 @@ describe('scheduler', () => {
209
180
 
210
181
  await done.wait();
211
182
  });
183
+
184
+ const createScheduler = (callback: SchedulerOptions['callback']) => {
185
+ const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
186
+ after(async () => {
187
+ await scheduler.stop();
188
+ });
189
+ return scheduler;
190
+ };
212
191
  });