@dxos/functions 0.5.3-main.b41a319 → 0.5.3-main.bbd33a9

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-P3HPDHNI.mjs +86 -0
  2. package/dist/lib/browser/chunk-P3HPDHNI.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +722 -487
  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-KTLM3JNV.cjs +103 -0
  9. package/dist/lib/node/chunk-KTLM3JNV.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +709 -480
  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 +40 -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 +143 -122
  59. package/dist/types/src/types.d.ts.map +1 -1
  60. package/package.json +30 -14
  61. package/schema/functions.json +139 -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 +37 -20
  69. package/src/runtime/scheduler.test.ts +54 -75
  70. package/src/runtime/scheduler.ts +70 -297
  71. package/src/testing/functions-integration.test.ts +99 -0
  72. package/src/testing/index.ts +7 -0
  73. package/src/testing/setup.ts +45 -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 +255 -0
  79. package/src/trigger/trigger-registry.ts +201 -0
  80. package/src/trigger/type/index.ts +8 -0
  81. package/src/trigger/type/subscription-trigger.ts +80 -0
  82. package/src/trigger/type/timer-trigger.ts +44 -0
  83. package/src/trigger/type/webhook-trigger.ts +47 -0
  84. package/src/trigger/type/websocket-trigger.ts +91 -0
  85. package/src/types.ts +78 -53
@@ -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,17 @@ 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
+ this._functionsRegistry.registered.on(async ({ added }) => {
50
+ added.forEach((def) => this._load(def));
51
+ await this._safeUpdateRegistration();
52
+ log('new functions loaded', { added });
53
+ });
54
+ }
46
55
 
47
56
  get stats() {
48
57
  return {
@@ -63,19 +72,10 @@ export class DevServer {
63
72
  return Object.values(this._handlers);
64
73
  }
65
74
 
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
75
  async start() {
77
76
  invariant(!this._server);
78
77
  log.info('starting...');
78
+ this._ctx = createContext();
79
79
 
80
80
  // TODO(burdon): Move to hono.
81
81
  const app = express();
@@ -107,12 +107,14 @@ export class DevServer {
107
107
  // Register functions.
108
108
  const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
109
109
  endpoint: this.endpoint,
110
- functions: this.functions.map(({ def: { id, path } }) => ({ id, path })),
111
110
  });
112
111
 
113
112
  log.info('registered', { endpoint });
114
113
  this._proxy = endpoint;
115
114
  this._functionServiceRegistration = registrationId;
115
+
116
+ // Open after registration, so that it can be updated with the list of function definitions.
117
+ await this._functionsRegistry.open(this._ctx);
116
118
  } catch (err: any) {
117
119
  await this.stop();
118
120
  throw new Error('FunctionRegistryService not available (check plugin is configured).');
@@ -155,10 +157,10 @@ export class DevServer {
155
157
  /**
156
158
  * Load function.
157
159
  */
158
- private async _load(def: FunctionDef, force = false) {
159
- const { id, path, handler } = def;
160
+ private async _load(def: FunctionDef, force?: boolean | undefined) {
161
+ const { uri, route, handler } = def;
160
162
  const filePath = join(this._options.baseDir, handler);
161
- log.info('loading', { id, force });
163
+ log.info('loading', { uri, force });
162
164
 
163
165
  // Remove from cache.
164
166
  if (force) {
@@ -169,13 +171,26 @@ export class DevServer {
169
171
  });
170
172
  }
171
173
 
174
+ // TODO(burdon): Import types.
172
175
  // eslint-disable-next-line @typescript-eslint/no-var-requires
173
176
  const module = require(filePath);
174
177
  if (typeof module.default !== 'function') {
175
- throw new Error(`Handler must export default function: ${id}`);
178
+ throw new Error(`Handler must export default function: ${uri}`);
176
179
  }
177
180
 
178
- this._handlers[path] = { def, handler: module.default };
181
+ this._handlers[route] = { def, handler: module.default };
182
+ }
183
+
184
+ private async _safeUpdateRegistration(): Promise<void> {
185
+ invariant(this._functionServiceRegistration);
186
+ try {
187
+ await this._client.services.services.FunctionRegistryService!.updateRegistration({
188
+ registrationId: this._functionServiceRegistration,
189
+ functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
190
+ });
191
+ } catch (e) {
192
+ log.catch(e);
193
+ }
179
194
  }
180
195
 
181
196
  /**
@@ -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,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 '../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
 
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
  });