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

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 (85) hide show
  1. package/dist/lib/browser/chunk-4D4I3YMJ.mjs +86 -0
  2. package/dist/lib/browser/chunk-4D4I3YMJ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +752 -480
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types.mjs +14 -0
  7. package/dist/lib/browser/types.mjs.map +7 -0
  8. package/dist/lib/node/chunk-3UYUR5N5.cjs +103 -0
  9. package/dist/lib/node/chunk-3UYUR5N5.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +739 -473
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/types.cjs +35 -0
  14. package/dist/lib/node/types.cjs.map +7 -0
  15. package/dist/types/src/browser/index.d.ts +2 -0
  16. package/dist/types/src/browser/index.d.ts.map +1 -0
  17. package/dist/types/src/function/function-registry.d.ts +24 -0
  18. package/dist/types/src/function/function-registry.d.ts.map +1 -0
  19. package/dist/types/src/function/function-registry.test.d.ts +2 -0
  20. package/dist/types/src/function/function-registry.test.d.ts.map +1 -0
  21. package/dist/types/src/function/index.d.ts +2 -0
  22. package/dist/types/src/function/index.d.ts.map +1 -0
  23. package/dist/types/src/handler.d.ts.map +1 -1
  24. package/dist/types/src/index.d.ts +2 -0
  25. package/dist/types/src/index.d.ts.map +1 -1
  26. package/dist/types/src/runtime/dev-server.d.ts +7 -10
  27. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  28. package/dist/types/src/runtime/scheduler.d.ts +11 -59
  29. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  30. package/dist/types/src/testing/functions-integration.test.d.ts +2 -0
  31. package/dist/types/src/testing/functions-integration.test.d.ts.map +1 -0
  32. package/dist/types/src/testing/index.d.ts +4 -0
  33. package/dist/types/src/testing/index.d.ts.map +1 -0
  34. package/dist/types/src/testing/setup.d.ts +5 -0
  35. package/dist/types/src/testing/setup.d.ts.map +1 -0
  36. package/dist/types/src/testing/test/handler.d.ts +1 -0
  37. package/dist/types/src/testing/test/handler.d.ts.map +1 -1
  38. package/dist/types/src/testing/types.d.ts +9 -0
  39. package/dist/types/src/testing/types.d.ts.map +1 -0
  40. package/dist/types/src/testing/util.d.ts +3 -0
  41. package/dist/types/src/testing/util.d.ts.map +1 -0
  42. package/dist/types/src/trigger/index.d.ts +2 -0
  43. package/dist/types/src/trigger/index.d.ts.map +1 -0
  44. package/dist/types/src/trigger/trigger-registry.d.ts +37 -0
  45. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -0
  46. package/dist/types/src/trigger/trigger-registry.test.d.ts +2 -0
  47. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +1 -0
  48. package/dist/types/src/trigger/type/index.d.ts +5 -0
  49. package/dist/types/src/trigger/type/index.d.ts.map +1 -0
  50. package/dist/types/src/trigger/type/subscription-trigger.d.ts +4 -0
  51. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -0
  52. package/dist/types/src/trigger/type/timer-trigger.d.ts +4 -0
  53. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +1 -0
  54. package/dist/types/src/trigger/type/webhook-trigger.d.ts +4 -0
  55. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -0
  56. package/dist/types/src/trigger/type/websocket-trigger.d.ts +13 -0
  57. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -0
  58. package/dist/types/src/types.d.ts +152 -119
  59. package/dist/types/src/types.d.ts.map +1 -1
  60. package/package.json +30 -14
  61. package/schema/functions.json +144 -116
  62. package/src/browser/index.ts +5 -0
  63. package/src/function/function-registry.test.ts +105 -0
  64. package/src/function/function-registry.ts +90 -0
  65. package/src/function/index.ts +5 -0
  66. package/src/index.ts +2 -0
  67. package/src/runtime/dev-server.test.ts +15 -35
  68. package/src/runtime/dev-server.ts +38 -21
  69. package/src/runtime/scheduler.test.ts +59 -75
  70. package/src/runtime/scheduler.ts +70 -297
  71. package/src/testing/functions-integration.test.ts +100 -0
  72. package/src/testing/index.ts +7 -0
  73. package/src/testing/setup.ts +43 -0
  74. package/src/testing/test/handler.ts +8 -2
  75. package/src/testing/types.ts +9 -0
  76. package/src/testing/util.ts +16 -0
  77. package/src/trigger/index.ts +5 -0
  78. package/src/trigger/trigger-registry.test.ts +272 -0
  79. package/src/trigger/trigger-registry.ts +211 -0
  80. package/src/trigger/type/index.ts +8 -0
  81. package/src/trigger/type/subscription-trigger.ts +86 -0
  82. package/src/trigger/type/timer-trigger.ts +45 -0
  83. package/src/trigger/type/webhook-trigger.ts +48 -0
  84. package/src/trigger/type/websocket-trigger.ts +92 -0
  85. package/src/types.ts +82 -54
@@ -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
 
16
+ import { type FunctionRegistry } from '../function';
15
17
  import { type FunctionContext, type FunctionEvent, type FunctionHandler, type FunctionResponse } from '../handler';
16
- import { type FunctionDef, type FunctionManifest } from '../types';
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
 
@@ -38,11 +41,18 @@ export class DevServer {
38
41
 
39
42
  public readonly update = new Event<number>();
40
43
 
41
- // prettier-ignore
42
44
  constructor(
43
45
  private readonly _client: Client,
46
+ private readonly _functionsRegistry: FunctionRegistry,
44
47
  private readonly _options: DevServerOptions,
45
- ) {}
48
+ ) {
49
+ // TODO(burdon): Add/remove listener in start/stop.
50
+ this._functionsRegistry.registered.on(async ({ added }) => {
51
+ added.forEach((def) => this._load(def));
52
+ await this._safeUpdateRegistration();
53
+ log('new functions loaded', { added });
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).');
@@ -155,10 +158,10 @@ export class DevServer {
155
158
  /**
156
159
  * Load function.
157
160
  */
158
- private async _load(def: FunctionDef, force = false) {
159
- const { id, path, handler } = def;
161
+ private async _load(def: FunctionDef, force?: boolean | undefined) {
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 (err) {
193
+ log.catch(err);
194
+ }
179
195
  }
180
196
 
181
197
  /**
@@ -196,7 +212,6 @@ export class DevServer {
196
212
  private async _invoke(path: string, event: FunctionEvent) {
197
213
  const { handler } = this._handlers[path] ?? {};
198
214
  invariant(handler, `invalid path: ${path}`);
199
-
200
215
  const context: FunctionContext = {
201
216
  client: this._client,
202
217
  dataDir: this._options.dataDir,
@@ -214,3 +229,5 @@ export class DevServer {
214
229
  return statusCode;
215
230
  }
216
231
  }
232
+
233
+ const createContext = () => new Context({ name: 'DevServer' });
@@ -6,40 +6,53 @@ 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 '../function';
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
 
32
+ const createScheduler = (callback: SchedulerOptions['callback']) => {
33
+ const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
34
+ after(async () => {
35
+ await scheduler.stop();
36
+ });
37
+
38
+ return scheduler;
39
+ };
40
+
30
41
  test('timer', async () => {
31
42
  const manifest: FunctionManifest = {
32
43
  functions: [
33
44
  {
34
- id: 'example.com/function/test',
35
- path: '/test',
45
+ uri: 'example.com/function/test',
46
+ route: '/test',
36
47
  handler: 'test',
37
48
  },
38
49
  ],
39
50
  triggers: [
40
51
  {
41
52
  function: 'example.com/function/test',
42
- timer: {
53
+ enabled: true,
54
+ spec: {
55
+ type: 'timer',
43
56
  cron: '0/1 * * * * *', // Every 1s.
44
57
  },
45
58
  },
@@ -48,18 +61,13 @@ describe('scheduler', () => {
48
61
 
49
62
  let count = 0;
50
63
  const done = new Trigger();
51
- const scheduler = new Scheduler(client, manifest, {
52
- callback: async () => {
53
- if (++count === 3) {
54
- done.wake();
55
- }
56
- },
64
+ const scheduler = createScheduler(async () => {
65
+ if (++count === 3) {
66
+ done.wake();
67
+ }
57
68
  });
58
-
69
+ await scheduler.register(client.spaces.default, manifest);
59
70
  await scheduler.start();
60
- after(async () => {
61
- await scheduler.stop();
62
- });
63
71
 
64
72
  await done.wait({ timeout: 5_000 });
65
73
  expect(count).to.equal(3);
@@ -69,15 +77,17 @@ describe('scheduler', () => {
69
77
  const manifest: FunctionManifest = {
70
78
  functions: [
71
79
  {
72
- id: 'example.com/function/test',
73
- path: '/test',
80
+ uri: 'example.com/function/test',
81
+ route: '/test',
74
82
  handler: 'test',
75
83
  },
76
84
  ],
77
85
  triggers: [
78
86
  {
79
87
  function: 'example.com/function/test',
80
- webhook: {
88
+ enabled: true,
89
+ spec: {
90
+ type: 'webhook',
81
91
  method: 'GET',
82
92
  },
83
93
  },
@@ -85,23 +95,14 @@ describe('scheduler', () => {
85
95
  };
86
96
 
87
97
  const done = new Trigger();
88
- const scheduler = new Scheduler(client, manifest, {
89
- callback: async () => {
90
- done.wake();
91
- },
98
+ const scheduler = createScheduler(async () => {
99
+ done.wake();
92
100
  });
93
-
101
+ const space = await client.spaces.create();
102
+ await scheduler.register(space, manifest);
94
103
  await scheduler.start();
95
- after(async () => {
96
- await scheduler.stop();
97
- });
98
104
 
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
- });
105
+ setTimeout(async () => triggerWebhook(space, manifest.functions![0].uri));
105
106
 
106
107
  await done.wait();
107
108
  });
@@ -110,15 +111,17 @@ describe('scheduler', () => {
110
111
  const manifest: FunctionManifest = {
111
112
  functions: [
112
113
  {
113
- id: 'example.com/function/test',
114
- path: '/test',
114
+ uri: 'example.com/function/test',
115
+ route: '/test',
115
116
  handler: 'test',
116
117
  },
117
118
  ],
118
119
  triggers: [
119
120
  {
120
121
  function: 'example.com/function/test',
121
- websocket: {
122
+ enabled: true,
123
+ spec: {
124
+ type: 'websocket',
122
125
  // url: 'https://hub.dxos.network/api/mailbox/test',
123
126
  url: 'http://localhost:8081',
124
127
  init: {
@@ -130,16 +133,11 @@ describe('scheduler', () => {
130
133
  };
131
134
 
132
135
  const done = new Trigger();
133
- const scheduler = new Scheduler(client, manifest, {
134
- callback: async (data) => {
135
- done.wake();
136
- },
136
+ const scheduler = createScheduler(async () => {
137
+ done.wake();
137
138
  });
138
-
139
+ await scheduler.register(client.spaces.default, manifest);
139
140
  await scheduler.start();
140
- after(async () => {
141
- await scheduler.stop();
142
- });
143
141
 
144
142
  // Test server.
145
143
  setTimeout(() => {
@@ -157,29 +155,21 @@ describe('scheduler', () => {
157
155
  });
158
156
 
159
157
  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
158
  const manifest: FunctionManifest = {
166
159
  functions: [
167
160
  {
168
- id: 'example.com/function/test',
169
- path: '/test',
161
+ uri: 'example.com/function/test',
162
+ route: '/test',
170
163
  handler: 'test',
171
164
  },
172
165
  ],
173
166
  triggers: [
174
167
  {
175
168
  function: 'example.com/function/test',
176
- subscription: {
177
- spaceKey: client.spaces.default.key.toHex(),
178
- filter: [
179
- {
180
- type: TestType.typename,
181
- },
182
- ],
169
+ enabled: true,
170
+ spec: {
171
+ type: 'subscription',
172
+ filter: [{ type: TestType.typename }],
183
173
  },
184
174
  },
185
175
  ],
@@ -187,20 +177,14 @@ describe('scheduler', () => {
187
177
 
188
178
  let count = 0;
189
179
  const done = new Trigger();
190
- const scheduler = new Scheduler(client, manifest, {
191
- callback: async () => {
192
- if (++count === 2) {
193
- done.wake();
194
- }
195
- },
180
+ const scheduler = createScheduler(async () => {
181
+ if (++count === 1) {
182
+ done.wake();
183
+ }
196
184
  });
197
-
185
+ await scheduler.register(client.spaces.default, manifest);
198
186
  await scheduler.start();
199
- after(async () => {
200
- await scheduler.stop();
201
- });
202
187
 
203
- // TODO(burdon): Query for Expando?
204
188
  setTimeout(() => {
205
189
  const space = client.spaces.default;
206
190
  const object = create(TestType, { title: 'Hello world!' });