@dxos/functions 0.8.1 → 0.8.2-main.10c050d

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 (204) hide show
  1. package/dist/lib/browser/bundler/index.mjs +0 -3
  2. package/dist/lib/browser/bundler/index.mjs.map +3 -3
  3. package/dist/lib/browser/edge/index.mjs +63 -7
  4. package/dist/lib/browser/edge/index.mjs.map +4 -4
  5. package/dist/lib/browser/index.mjs +513 -100
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/node/bundler/index.cjs +0 -1
  9. package/dist/lib/node/bundler/index.cjs.map +3 -3
  10. package/dist/lib/node/edge/index.cjs +65 -5
  11. package/dist/lib/node/edge/index.cjs.map +4 -4
  12. package/dist/lib/node/index.cjs +519 -92
  13. package/dist/lib/node/index.cjs.map +4 -4
  14. package/dist/lib/node/meta.json +1 -1
  15. package/dist/lib/node-esm/bundler/index.mjs +0 -1
  16. package/dist/lib/node-esm/bundler/index.mjs.map +3 -3
  17. package/dist/lib/node-esm/edge/index.mjs +64 -6
  18. package/dist/lib/node-esm/edge/index.mjs.map +4 -4
  19. package/dist/lib/node-esm/index.mjs +513 -98
  20. package/dist/lib/node-esm/index.mjs.map +4 -4
  21. package/dist/lib/node-esm/meta.json +1 -1
  22. package/dist/types/src/bundler/bundler.d.ts.map +1 -1
  23. package/dist/types/src/edge/functions.d.ts +3 -3
  24. package/dist/types/src/edge/functions.d.ts.map +1 -1
  25. package/dist/types/src/edge/index.d.ts.map +1 -1
  26. package/dist/types/src/executor/executor.d.ts +8 -0
  27. package/dist/types/src/executor/executor.d.ts.map +1 -0
  28. package/dist/types/src/executor/index.d.ts +2 -0
  29. package/dist/types/src/executor/index.d.ts.map +1 -0
  30. package/dist/types/src/handler.d.ts +23 -67
  31. package/dist/types/src/handler.d.ts.map +1 -1
  32. package/dist/types/src/index.d.ts +5 -3
  33. package/dist/types/src/index.d.ts.map +1 -1
  34. package/dist/types/src/schema.d.ts +57 -0
  35. package/dist/types/src/schema.d.ts.map +1 -0
  36. package/dist/types/src/services/ai.d.ts +9 -0
  37. package/dist/types/src/services/ai.d.ts.map +1 -0
  38. package/dist/types/src/services/credentials.d.ts +30 -0
  39. package/dist/types/src/services/credentials.d.ts.map +1 -0
  40. package/dist/types/src/services/database.d.ts +9 -0
  41. package/dist/types/src/services/database.d.ts.map +1 -0
  42. package/dist/types/src/services/index.d.ts +7 -0
  43. package/dist/types/src/services/index.d.ts.map +1 -0
  44. package/dist/types/src/services/queues.d.ts +10 -0
  45. package/dist/types/src/services/queues.d.ts.map +1 -0
  46. package/dist/types/src/services/service-container.d.ts +25 -0
  47. package/dist/types/src/services/service-container.d.ts.map +1 -0
  48. package/dist/types/src/services/tracing.d.ts +15 -0
  49. package/dist/types/src/services/tracing.d.ts.map +1 -0
  50. package/dist/types/src/trace.d.ts +149 -0
  51. package/dist/types/src/trace.d.ts.map +1 -0
  52. package/dist/types/src/translations.d.ts +2 -1
  53. package/dist/types/src/translations.d.ts.map +1 -1
  54. package/dist/types/src/types.d.ts +407 -0
  55. package/dist/types/src/types.d.ts.map +1 -0
  56. package/dist/types/src/{types/url.d.ts → url.d.ts} +6 -0
  57. package/dist/types/src/url.d.ts.map +1 -0
  58. package/dist/types/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +21 -36
  60. package/src/bundler/bundler.ts +7 -1
  61. package/src/edge/functions.ts +7 -4
  62. package/src/edge/index.ts +4 -0
  63. package/src/executor/executor.ts +47 -0
  64. package/src/executor/index.ts +5 -0
  65. package/src/handler.ts +29 -125
  66. package/src/index.ts +8 -5
  67. package/src/schema.ts +52 -0
  68. package/src/services/ai.ts +15 -0
  69. package/src/services/credentials.ts +55 -0
  70. package/src/services/database.ts +14 -0
  71. package/src/services/index.ts +10 -0
  72. package/src/services/queues.ts +16 -0
  73. package/src/services/service-container.ts +58 -0
  74. package/src/services/tracing.ts +27 -0
  75. package/src/{types/trace.ts → trace.ts} +37 -35
  76. package/src/translations.ts +1 -1
  77. package/src/types.ts +211 -0
  78. package/src/{types/url.ts → url.ts} +5 -0
  79. package/dist/lib/browser/chunk-HI7YZO2K.mjs +0 -482
  80. package/dist/lib/browser/chunk-HI7YZO2K.mjs.map +0 -7
  81. package/dist/lib/browser/chunk-LT4LR4VU.mjs +0 -72
  82. package/dist/lib/browser/chunk-LT4LR4VU.mjs.map +0 -7
  83. package/dist/lib/browser/chunk-RVSG6WTL.mjs +0 -358
  84. package/dist/lib/browser/chunk-RVSG6WTL.mjs.map +0 -7
  85. package/dist/lib/browser/chunk-XRCXIG74.mjs +0 -12
  86. package/dist/lib/browser/chunk-XRCXIG74.mjs.map +0 -7
  87. package/dist/lib/browser/testing/index.mjs +0 -670
  88. package/dist/lib/browser/testing/index.mjs.map +0 -7
  89. package/dist/lib/browser/types/index.mjs +0 -49
  90. package/dist/lib/browser/types/index.mjs.map +0 -7
  91. package/dist/lib/node/chunk-DSUGRAAL.cjs +0 -392
  92. package/dist/lib/node/chunk-DSUGRAAL.cjs.map +0 -7
  93. package/dist/lib/node/chunk-JEQ2X3Z6.cjs +0 -34
  94. package/dist/lib/node/chunk-JEQ2X3Z6.cjs.map +0 -7
  95. package/dist/lib/node/chunk-NXZNXVT3.cjs +0 -94
  96. package/dist/lib/node/chunk-NXZNXVT3.cjs.map +0 -7
  97. package/dist/lib/node/chunk-RXMCVAMJ.cjs +0 -496
  98. package/dist/lib/node/chunk-RXMCVAMJ.cjs.map +0 -7
  99. package/dist/lib/node/testing/index.cjs +0 -687
  100. package/dist/lib/node/testing/index.cjs.map +0 -7
  101. package/dist/lib/node/types/index.cjs +0 -70
  102. package/dist/lib/node/types/index.cjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-DHGBFXSZ.mjs +0 -12
  104. package/dist/lib/node-esm/chunk-DHGBFXSZ.mjs.map +0 -7
  105. package/dist/lib/node-esm/chunk-HBD2FZXO.mjs +0 -358
  106. package/dist/lib/node-esm/chunk-HBD2FZXO.mjs.map +0 -7
  107. package/dist/lib/node-esm/chunk-O2SXVYU5.mjs +0 -72
  108. package/dist/lib/node-esm/chunk-O2SXVYU5.mjs.map +0 -7
  109. package/dist/lib/node-esm/chunk-SQSJO5HI.mjs +0 -482
  110. package/dist/lib/node-esm/chunk-SQSJO5HI.mjs.map +0 -7
  111. package/dist/lib/node-esm/testing/index.mjs +0 -670
  112. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  113. package/dist/lib/node-esm/types/index.mjs +0 -49
  114. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  115. package/dist/types/src/browser/index.d.ts +0 -2
  116. package/dist/types/src/browser/index.d.ts.map +0 -1
  117. package/dist/types/src/function/function-registry.d.ts +0 -25
  118. package/dist/types/src/function/function-registry.d.ts.map +0 -1
  119. package/dist/types/src/function/function-registry.test.d.ts +0 -2
  120. package/dist/types/src/function/function-registry.test.d.ts.map +0 -1
  121. package/dist/types/src/function/index.d.ts +0 -2
  122. package/dist/types/src/function/index.d.ts.map +0 -1
  123. package/dist/types/src/runtime/dev-server.d.ts +0 -52
  124. package/dist/types/src/runtime/dev-server.d.ts.map +0 -1
  125. package/dist/types/src/runtime/dev-server.test.d.ts +0 -2
  126. package/dist/types/src/runtime/dev-server.test.d.ts.map +0 -1
  127. package/dist/types/src/runtime/index.d.ts +0 -3
  128. package/dist/types/src/runtime/index.d.ts.map +0 -1
  129. package/dist/types/src/runtime/scheduler.d.ts +0 -34
  130. package/dist/types/src/runtime/scheduler.d.ts.map +0 -1
  131. package/dist/types/src/runtime/scheduler.test.d.ts +0 -2
  132. package/dist/types/src/runtime/scheduler.test.d.ts.map +0 -1
  133. package/dist/types/src/testing/functions-integration.test.d.ts +0 -2
  134. package/dist/types/src/testing/functions-integration.test.d.ts.map +0 -1
  135. package/dist/types/src/testing/index.d.ts +0 -5
  136. package/dist/types/src/testing/index.d.ts.map +0 -1
  137. package/dist/types/src/testing/manifest.d.ts +0 -3
  138. package/dist/types/src/testing/manifest.d.ts.map +0 -1
  139. package/dist/types/src/testing/plugin-init.d.ts +0 -6
  140. package/dist/types/src/testing/plugin-init.d.ts.map +0 -1
  141. package/dist/types/src/testing/setup.d.ts +0 -15
  142. package/dist/types/src/testing/setup.d.ts.map +0 -1
  143. package/dist/types/src/testing/test/handler.d.ts +0 -4
  144. package/dist/types/src/testing/test/handler.d.ts.map +0 -1
  145. package/dist/types/src/testing/test/index.d.ts +0 -3
  146. package/dist/types/src/testing/test/index.d.ts.map +0 -1
  147. package/dist/types/src/testing/types.d.ts +0 -10
  148. package/dist/types/src/testing/types.d.ts.map +0 -1
  149. package/dist/types/src/testing/util.d.ts +0 -5
  150. package/dist/types/src/testing/util.d.ts.map +0 -1
  151. package/dist/types/src/trigger/index.d.ts +0 -3
  152. package/dist/types/src/trigger/index.d.ts.map +0 -1
  153. package/dist/types/src/trigger/trigger-registry.d.ts +0 -38
  154. package/dist/types/src/trigger/trigger-registry.d.ts.map +0 -1
  155. package/dist/types/src/trigger/trigger-registry.test.d.ts +0 -2
  156. package/dist/types/src/trigger/trigger-registry.test.d.ts.map +0 -1
  157. package/dist/types/src/trigger/type/index.d.ts +0 -3
  158. package/dist/types/src/trigger/type/index.d.ts.map +0 -1
  159. package/dist/types/src/trigger/type/subscription-trigger.d.ts +0 -4
  160. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +0 -1
  161. package/dist/types/src/trigger/type/timer-trigger.d.ts +0 -4
  162. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +0 -1
  163. package/dist/types/src/trigger/type/webhook-trigger.d.ts +0 -4
  164. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +0 -1
  165. package/dist/types/src/types/index.d.ts +0 -5
  166. package/dist/types/src/types/index.d.ts.map +0 -1
  167. package/dist/types/src/types/schema.d.ts +0 -53
  168. package/dist/types/src/types/schema.d.ts.map +0 -1
  169. package/dist/types/src/types/trace.d.ts +0 -146
  170. package/dist/types/src/types/trace.d.ts.map +0 -1
  171. package/dist/types/src/types/types.d.ts +0 -265
  172. package/dist/types/src/types/types.d.ts.map +0 -1
  173. package/dist/types/src/types/url.d.ts.map +0 -1
  174. package/dist/types/tools/schema.d.ts +0 -2
  175. package/dist/types/tools/schema.d.ts.map +0 -1
  176. package/schema/functions.json +0 -211
  177. package/src/browser/index.ts +0 -5
  178. package/src/function/function-registry.test.ts +0 -118
  179. package/src/function/function-registry.ts +0 -104
  180. package/src/function/index.ts +0 -5
  181. package/src/runtime/dev-server.test.ts +0 -79
  182. package/src/runtime/dev-server.ts +0 -240
  183. package/src/runtime/index.ts +0 -6
  184. package/src/runtime/scheduler.test.ts +0 -152
  185. package/src/runtime/scheduler.ts +0 -170
  186. package/src/testing/functions-integration.test.ts +0 -65
  187. package/src/testing/index.ts +0 -8
  188. package/src/testing/manifest.ts +0 -15
  189. package/src/testing/plugin-init.ts +0 -20
  190. package/src/testing/setup.ts +0 -109
  191. package/src/testing/test/handler.ts +0 -15
  192. package/src/testing/test/index.ts +0 -7
  193. package/src/testing/types.ts +0 -9
  194. package/src/testing/util.ts +0 -26
  195. package/src/trigger/index.ts +0 -6
  196. package/src/trigger/trigger-registry.test.ts +0 -278
  197. package/src/trigger/trigger-registry.ts +0 -218
  198. package/src/trigger/type/index.ts +0 -7
  199. package/src/trigger/type/subscription-trigger.ts +0 -84
  200. package/src/trigger/type/timer-trigger.ts +0 -48
  201. package/src/trigger/type/webhook-trigger.ts +0 -48
  202. package/src/types/index.ts +0 -8
  203. package/src/types/schema.ts +0 -46
  204. package/src/types/types.ts +0 -163
@@ -1,118 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
6
-
7
- import { Trigger } from '@dxos/async';
8
- import { type Client } from '@dxos/client';
9
- import { TestBuilder } from '@dxos/client/testing';
10
- import { Context } from '@dxos/context';
11
- import { Filter } from '@dxos/echo-db';
12
- import { create } from '@dxos/live-object';
13
- import { range } from '@dxos/util';
14
-
15
- import { FunctionRegistry } from './function-registry';
16
- import { createInitializedClients } from '../testing';
17
- import { FunctionDef, type FunctionManifest } from '../types';
18
-
19
- const testManifest: FunctionManifest = {
20
- functions: [
21
- {
22
- uri: 'dxos.functions.test/hello',
23
- route: '/hello',
24
- handler: 'test',
25
- },
26
- ],
27
- };
28
-
29
- describe('function registry', () => {
30
- let ctx: Context;
31
- let testBuilder: TestBuilder;
32
-
33
- beforeEach(async () => {
34
- ctx = new Context();
35
- testBuilder = new TestBuilder();
36
- });
37
-
38
- afterEach(async () => {
39
- await ctx.dispose();
40
- await testBuilder.destroy();
41
- });
42
-
43
- test('getUniqueByUri', async () => {
44
- const client = (await createInitializedClients(testBuilder))[0];
45
- const registry = createRegistry(client);
46
- await registry.open();
47
- for (let i = 0; i < 2; i++) {
48
- const space = await client.spaces.create();
49
- await registry.register(space, testManifest.functions);
50
- }
51
- const definitions = registry.getUniqueByUri();
52
- expect(definitions.length).to.eq(testManifest.functions?.length);
53
- });
54
-
55
- describe('register', () => {
56
- test('creates new functions', async () => {
57
- const client = (await createInitializedClients(testBuilder))[0];
58
- const registry = createRegistry(client);
59
- const space = await client.spaces.create();
60
- await registry.register(space, testManifest.functions);
61
- const { objects: definitions } = await space.db.query(Filter.schema(FunctionDef)).run();
62
- expect(definitions.length).to.eq(1);
63
- expect(definitions[0].uri).to.eq(testManifest.functions?.[0]?.uri);
64
- });
65
-
66
- test('de-duplicates by function URI', async () => {
67
- const client = (await createInitializedClients(testBuilder))[0];
68
- const registry = createRegistry(client);
69
- const space = await client.spaces.create();
70
- const existing = space.db.add(create(FunctionDef, testManifest.functions![0]));
71
- await registry.register(space, testManifest.functions);
72
- const { objects: definitions } = await space.db.query(Filter.schema(FunctionDef)).run();
73
- expect(definitions.length).to.eq(1);
74
- expect(definitions[0].uri).to.eq(existing.uri);
75
- });
76
- });
77
-
78
- describe('onFunctionsRegistered', () => {
79
- test('called with all registered when opened', async () => {
80
- const client = (await createInitializedClients(testBuilder))[0];
81
- const registry = createRegistry(client);
82
- const space = await client.spaces.create();
83
- const definitions = range(3, () => create(FunctionDef, testManifest.functions![0]));
84
- definitions.forEach((def) => space.db.add(def));
85
-
86
- const functionRegistered = new Trigger<FunctionDef[]>();
87
- registry.registered.on((fn) => {
88
- functionRegistered.wake(fn.added);
89
- });
90
- void registry.open(ctx);
91
- const functions = await functionRegistered.wait();
92
- const expected = definitions.map((def) => def.uri).sort();
93
- expect(functions.map((fn) => fn.uri).sort()).to.deep.eq(expected);
94
- });
95
-
96
- test('called when a new functions is added', async () => {
97
- const client = (await createInitializedClients(testBuilder))[0];
98
- const registry = createRegistry(client);
99
- const space = await client.spaces.create();
100
-
101
- const functionRegistered = new Trigger<FunctionDef>();
102
- registry.registered.on((fn) => {
103
- expect(fn.added.length).to.eq(1);
104
- functionRegistered.wake(fn.added[0]);
105
- });
106
- await registry.open(ctx);
107
- await registry.register(space, testManifest.functions);
108
- const registered = await functionRegistered.wait();
109
- expect(registered.uri).to.eq(testManifest.functions![0].uri);
110
- });
111
- });
112
-
113
- const createRegistry = (client: Client) => {
114
- const registry = new FunctionRegistry(client);
115
- ctx.onDispose(() => registry.close());
116
- return registry;
117
- };
118
- });
@@ -1,104 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { Event } from '@dxos/async';
6
- import { type Client } from '@dxos/client';
7
- import { create, Filter, type Space } from '@dxos/client/echo';
8
- import { type Context, Resource } from '@dxos/context';
9
- import { PublicKey } from '@dxos/keys';
10
- import { log } from '@dxos/log';
11
- import { ComplexMap, diff } from '@dxos/util';
12
-
13
- import { FunctionDef, type FunctionManifest } from '../types';
14
-
15
- export type FunctionsRegisteredEvent = {
16
- space: Space;
17
- added: FunctionDef[];
18
- };
19
-
20
- export class FunctionRegistry extends Resource {
21
- private readonly _functionBySpaceKey = new ComplexMap<PublicKey, FunctionDef[]>(PublicKey.hash);
22
-
23
- public readonly registered = new Event<FunctionsRegisteredEvent>();
24
-
25
- constructor(private readonly _client: Client) {
26
- super();
27
- }
28
-
29
- public getFunctions(space: Space): FunctionDef[] {
30
- return this._functionBySpaceKey.get(space.key) ?? [];
31
- }
32
-
33
- public getUniqueByUri(): FunctionDef[] {
34
- const uniqueByUri = [...this._functionBySpaceKey.values()]
35
- .flatMap((defs) => defs)
36
- .reduce((acc, v) => {
37
- acc.set(v.uri, v);
38
- return acc;
39
- }, new Map<string, FunctionDef>());
40
- return [...uniqueByUri.values()];
41
- }
42
-
43
- /**
44
- * Loads function definitions from the manifest into the space.
45
- * We first load all the definitions from the space to deduplicate by functionId.
46
- */
47
- public async register(space: Space, functions: FunctionManifest['functions']): Promise<void> {
48
- log('register', { space: space.key, functions: functions?.length ?? 0 });
49
- if (!functions?.length) {
50
- return;
51
- }
52
- if (!space.db.graph.schemaRegistry.hasSchema(FunctionDef)) {
53
- space.db.graph.schemaRegistry.addSchema([FunctionDef]);
54
- }
55
-
56
- // Sync definitions.
57
- const { objects: existing } = await space.db.query(Filter.schema(FunctionDef)).run();
58
- const { added } = diff(existing, functions, (a, b) => a.uri === b.uri);
59
- // TODO(burdon): Update existing templates.
60
- added.forEach((def) => space.db.add(create(FunctionDef, def)));
61
-
62
- if (added.length > 0) {
63
- await space.db.flush({ indexes: true, updates: true });
64
- }
65
- }
66
-
67
- protected override async _open(): Promise<void> {
68
- log.info('opening...');
69
- const spacesSubscription = this._client.spaces.subscribe(async (spaces) => {
70
- for (const space of spaces) {
71
- if (this._functionBySpaceKey.has(space.key)) {
72
- continue;
73
- }
74
-
75
- const registered: FunctionDef[] = [];
76
- this._functionBySpaceKey.set(space.key, registered);
77
- await space.waitUntilReady();
78
- if (this._ctx.disposed) {
79
- break;
80
- }
81
-
82
- // Subscribe to updates.
83
- this._ctx.onDispose(
84
- space.db.query(Filter.schema(FunctionDef)).subscribe(({ objects }) => {
85
- const { added } = diff(registered, objects, (a, b) => a.uri === b.uri);
86
- // TODO(burdon): Update and remove.
87
- if (added.length > 0) {
88
- registered.push(...added);
89
- this.registered.emit({ space, added });
90
- }
91
- }),
92
- );
93
- }
94
- });
95
-
96
- // TODO(burdon): API: Normalize unsubscribe methods.
97
- this._ctx.onDispose(() => spacesSubscription.unsubscribe());
98
- }
99
-
100
- protected override async _close(_: Context): Promise<void> {
101
- log.info('closing...');
102
- this._functionBySpaceKey.clear();
103
- }
104
- }
@@ -1,5 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './function-registry';
@@ -1,79 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { getRandomPort } from 'get-port-please';
6
- import path from 'node:path';
7
- import { afterAll, beforeAll, describe, expect, test } from 'vitest';
8
-
9
- import { sleep, waitForCondition } from '@dxos/async';
10
- import { type Client } from '@dxos/client';
11
- import { TestBuilder } from '@dxos/client/testing';
12
-
13
- import { DevServer } from './dev-server';
14
- import { FunctionRegistry } from '../function';
15
- import { createFunctionRuntime, testFunctionManifest } from '../testing';
16
- import { initFunctionsPlugin } from '../testing/plugin-init';
17
-
18
- // TODO(wittjosiah): Doesn't work in vitest.
19
- describe.skip('dev server', () => {
20
- let client: Client;
21
- let testBuilder: TestBuilder;
22
-
23
- beforeAll(async () => {
24
- testBuilder = new TestBuilder();
25
- client = await createFunctionRuntime(testBuilder, initFunctionsPlugin);
26
- expect(client.services.services.FunctionRegistryService).to.exist;
27
- });
28
-
29
- afterAll(async () => {
30
- await testBuilder.destroy();
31
- });
32
-
33
- test('function registry open after dev server started', async () => {
34
- const { space, registry, server } = await setupTest();
35
- await registry.register(space, testFunctionManifest.functions);
36
- await server.start();
37
- await registry.open();
38
- await waitForCondition({ condition: () => server.functions.length > 0 });
39
- await expectTestFunctionInvocable(server);
40
- });
41
-
42
- test('function registry open before dev server started', async () => {
43
- const { space, registry, server } = await setupTest();
44
- await registry.register(space, testFunctionManifest.functions);
45
- await registry.open();
46
- await server.start();
47
- await waitForCondition({ condition: () => server.functions.length > 0 });
48
- await expectTestFunctionInvocable(server);
49
- });
50
-
51
- test('unsubscribes from functions after stopped', async () => {
52
- const { space, registry, server } = await setupTest();
53
- await registry.register(space, testFunctionManifest.functions);
54
- await server.start();
55
- await server.stop();
56
- await registry.open();
57
- await sleep(20);
58
- expect(server.functions.length).to.eq(0);
59
- });
60
-
61
- const expectTestFunctionInvocable = async (server: DevServer) => {
62
- const seq = server.stats.seq;
63
- await server.invoke('test', {});
64
- expect(server.stats.seq).to.eq(seq + 1);
65
- };
66
-
67
- const setupTest = async () => {
68
- const registry = new FunctionRegistry(client);
69
- const server = new DevServer(client, registry, {
70
- baseDir: path.join(__dirname, '../testing'),
71
- port: await getRandomPort('127.0.0.1'),
72
- });
73
- const space = await client.spaces.create();
74
- // TODO(burdon): Doesn't shut down cleanly.
75
- // Error: invariant violation [this._client.services.services.FunctionRegistryService]
76
- testBuilder.ctx.onDispose(() => server.stop());
77
- return { registry, server, space };
78
- };
79
- });
@@ -1,240 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import express from 'express';
6
- import { getPort } from 'get-port-please';
7
- import type http from 'http';
8
- import { join } from 'node:path';
9
-
10
- import { asyncTimeout, Event, Trigger } from '@dxos/async';
11
- import { type Client } from '@dxos/client';
12
- import { Context } from '@dxos/context';
13
- import { invariant } from '@dxos/invariant';
14
- import { log } from '@dxos/log';
15
-
16
- import { type FunctionRegistry } from '../function';
17
- import { type FunctionContext, type FunctionEvent, type FunctionHandler, type FunctionResponse } from '../handler';
18
- import { type FunctionDef } from '../types';
19
-
20
- const FN_TIMEOUT = 20_000;
21
-
22
- export type DevServerOptions = {
23
- baseDir: string;
24
- port?: number;
25
- reload?: boolean;
26
- dataDir?: string;
27
- };
28
-
29
- /**
30
- * Functions dev server provides a local HTTP server for loading and invoking functions.
31
- * Functions are executed in the context of an authenticated client.
32
- */
33
- export class DevServer {
34
- private _ctx = createContext();
35
-
36
- // Function handlers indexed by name (URL path).
37
- private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};
38
-
39
- private _server?: http.Server;
40
- private _port?: number;
41
- private _functionServiceRegistration?: string;
42
- private _proxy?: string;
43
- private _seq = 0;
44
-
45
- public readonly update = new Event<number>();
46
-
47
- constructor(
48
- private readonly _client: Client,
49
- private readonly _functionsRegistry: FunctionRegistry,
50
- private readonly _options: DevServerOptions,
51
- ) {}
52
-
53
- get stats() {
54
- return {
55
- seq: this._seq,
56
- };
57
- }
58
-
59
- get endpoint() {
60
- invariant(this._port);
61
- return `http://localhost:${this._port}`;
62
- }
63
-
64
- get proxy() {
65
- return this._proxy;
66
- }
67
-
68
- get functions() {
69
- return Object.values(this._handlers);
70
- }
71
-
72
- async start() {
73
- invariant(!this._server);
74
- log.info('starting...');
75
- this._ctx = createContext();
76
-
77
- // TODO(burdon): Change to hono.
78
- const app = express();
79
- app.use(express.json());
80
-
81
- app.post('/:path', async (req, res) => {
82
- const { path } = req.params;
83
- try {
84
- log.info('calling', { path });
85
- if (this._options.reload) {
86
- const { def } = this._handlers['/' + path];
87
- await this._load(def, true);
88
- }
89
-
90
- // TODO(burdon): Get function context.
91
- res.statusCode = await asyncTimeout(this.invoke('/' + path, req.body), FN_TIMEOUT);
92
- res.end();
93
- } catch (err: any) {
94
- log.catch(err);
95
- res.statusCode = 500;
96
- res.end();
97
- }
98
- });
99
-
100
- this._port = this._options.port ?? (await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] }));
101
- this._server = app.listen(this._port);
102
-
103
- try {
104
- // Register functions.
105
- const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
106
- endpoint: this.endpoint,
107
- });
108
-
109
- log.info('registered', { endpoint });
110
- this._proxy = endpoint;
111
- this._functionServiceRegistration = registrationId;
112
-
113
- // Open after registration, so that it can be updated with the list of function definitions.
114
- await this._handleNewFunctions(this._functionsRegistry.getUniqueByUri());
115
- this._ctx.onDispose(this._functionsRegistry.registered.on(({ added }) => this._handleNewFunctions(added)));
116
- } catch (err: any) {
117
- await this.stop();
118
- throw new Error('FunctionRegistryService not available (check plugin is configured).');
119
- }
120
-
121
- log.info('started', { port: this._port });
122
- }
123
-
124
- async stop() {
125
- if (!this._server) {
126
- return;
127
- }
128
-
129
- log.info('stopping...');
130
- await this._ctx.dispose();
131
-
132
- const trigger = new Trigger();
133
- this._server.close(async () => {
134
- log.info('server stopped');
135
- try {
136
- if (this._functionServiceRegistration) {
137
- invariant(this._client.services.services.FunctionRegistryService);
138
- await this._client.services.services.FunctionRegistryService.unregister({
139
- registrationId: this._functionServiceRegistration,
140
- });
141
-
142
- log.info('unregistered', { registrationId: this._functionServiceRegistration });
143
- this._functionServiceRegistration = undefined;
144
- this._proxy = undefined;
145
- }
146
-
147
- trigger.wake();
148
- } catch (err) {
149
- trigger.throw(err as Error);
150
- }
151
- });
152
-
153
- await trigger.wait();
154
- this._port = undefined;
155
- this._server = undefined;
156
- log.info('stopped');
157
- }
158
-
159
- private async _handleNewFunctions(newFunctions: FunctionDef[]) {
160
- newFunctions.forEach((def) => this._load(def));
161
- await this._safeUpdateRegistration();
162
- log('new functions loaded', { newFunctions });
163
- }
164
-
165
- /**
166
- * Load function.
167
- */
168
- private async _load(def: FunctionDef, force?: boolean | undefined) {
169
- const { uri, route, handler } = def;
170
- const filePath = join(this._options.baseDir, handler);
171
- log.info('loading', { uri, force });
172
-
173
- // Remove from cache.
174
- if (force) {
175
- Object.keys(require.cache)
176
- .filter((key) => key.startsWith(filePath))
177
- .forEach((key) => {
178
- delete require.cache[key];
179
- });
180
- }
181
-
182
- // TODO(burdon): Import types.
183
- // eslint-disable-next-line @typescript-eslint/no-var-requires
184
- const module = require(filePath);
185
- if (typeof module.default !== 'function') {
186
- throw new Error(`Handler must export default function: ${uri}`);
187
- }
188
-
189
- this._handlers[route] = { def, handler: module.default };
190
- }
191
-
192
- private async _safeUpdateRegistration(): Promise<void> {
193
- invariant(this._functionServiceRegistration);
194
- try {
195
- await this._client.services.services.FunctionRegistryService!.updateRegistration({
196
- registrationId: this._functionServiceRegistration,
197
- functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
198
- });
199
- } catch (err) {
200
- log.catch(err);
201
- }
202
- }
203
-
204
- /**
205
- * Invoke function.
206
- */
207
- public async invoke(path: string, data: any): Promise<number> {
208
- const seq = ++this._seq;
209
- const now = Date.now();
210
-
211
- log.info('req', { seq, path });
212
- const statusCode = await this._invoke(path, { data });
213
-
214
- log.info('res', { seq, path, statusCode, duration: Date.now() - now });
215
- this.update.emit(statusCode);
216
- return statusCode;
217
- }
218
-
219
- private async _invoke(path: string, event: FunctionEvent) {
220
- const { handler } = this._handlers[path] ?? {};
221
- invariant(handler, `invalid path: ${path}`);
222
- const context: FunctionContext = {
223
- client: this._client,
224
- dataDir: this._options.dataDir,
225
- } as any;
226
-
227
- let statusCode = 200;
228
- const response: FunctionResponse = {
229
- status: (code: number) => {
230
- statusCode = code;
231
- return response;
232
- },
233
- };
234
-
235
- await handler({ context, event, response });
236
- return statusCode;
237
- }
238
- }
239
-
240
- const createContext = () => new Context({ name: 'DevServer' });
@@ -1,6 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- export * from './dev-server';
6
- export * from './scheduler';
@@ -1,152 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { afterAll, beforeAll, describe, expect, test } from 'vitest';
6
-
7
- import { Trigger } from '@dxos/async';
8
- import { type Client } from '@dxos/client';
9
- import { TestBuilder } from '@dxos/client/testing';
10
- import { create } from '@dxos/live-object';
11
-
12
- import { Scheduler, type SchedulerOptions } from './scheduler';
13
- import { FunctionRegistry } from '../function';
14
- import { createInitializedClients, TestType, triggerWebhook } from '../testing';
15
- import { TriggerRegistry } from '../trigger';
16
- import { TriggerKind, type FunctionManifest } from '../types';
17
-
18
- // TODO(burdon): Test we can add and remove triggers.
19
- // Flaky: https://cloud.nx.app/runs/uqhKOBA6JQ/task/functions%3Atest
20
- describe.skip('scheduler', () => {
21
- let testBuilder: TestBuilder;
22
- let client: Client;
23
-
24
- beforeAll(async () => {
25
- testBuilder = new TestBuilder();
26
- client = (await createInitializedClients(testBuilder, 1))[0];
27
- });
28
-
29
- afterAll(async () => {
30
- await testBuilder.destroy();
31
- });
32
-
33
- const createScheduler = (callback: SchedulerOptions['callback']) => {
34
- const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
35
-
36
- afterAll(async () => {
37
- await scheduler.stop();
38
- });
39
-
40
- return scheduler;
41
- };
42
-
43
- test('timer', async () => {
44
- const manifest: FunctionManifest = {
45
- functions: [
46
- {
47
- uri: 'example.com/function/test',
48
- route: '/test',
49
- handler: 'test',
50
- },
51
- ],
52
- triggers: [
53
- {
54
- function: 'example.com/function/test',
55
- enabled: true,
56
- spec: {
57
- type: TriggerKind.Timer,
58
- cron: '0/1 * * * * *', // Every 1s.
59
- },
60
- },
61
- ],
62
- };
63
-
64
- let count = 0;
65
- const done = new Trigger();
66
- const scheduler = createScheduler(async () => {
67
- if (++count === 3) {
68
- done.wake();
69
- }
70
- });
71
- await scheduler.register(client.spaces.default, manifest);
72
- await scheduler.start();
73
-
74
- await done.wait({ timeout: 5_000 });
75
- expect(count).to.equal(3);
76
- });
77
-
78
- // Flaky.
79
- test.skip('webhook', async () => {
80
- const manifest: FunctionManifest = {
81
- functions: [
82
- {
83
- uri: 'example.com/function/test',
84
- route: '/test',
85
- handler: 'test',
86
- },
87
- ],
88
- triggers: [
89
- {
90
- function: 'example.com/function/test',
91
- enabled: true,
92
- spec: {
93
- type: TriggerKind.Webhook,
94
- method: 'GET',
95
- },
96
- },
97
- ],
98
- };
99
-
100
- const done = new Trigger();
101
- const scheduler = createScheduler(async () => {
102
- done.wake();
103
- });
104
- const space = await client.spaces.create();
105
- await scheduler.register(space, manifest);
106
- await scheduler.start();
107
-
108
- setTimeout(async () => triggerWebhook(space, manifest.functions![0].uri));
109
-
110
- await done.wait();
111
- });
112
-
113
- test('subscription', async () => {
114
- const manifest: FunctionManifest = {
115
- functions: [
116
- {
117
- uri: 'example.com/function/test',
118
- route: '/test',
119
- handler: 'test',
120
- },
121
- ],
122
- triggers: [
123
- {
124
- function: 'example.com/function/test',
125
- enabled: true,
126
- spec: {
127
- type: TriggerKind.Subscription,
128
- filter: { type: TestType.typename },
129
- },
130
- },
131
- ],
132
- };
133
-
134
- let count = 0;
135
- const done = new Trigger();
136
- const scheduler = createScheduler(async () => {
137
- if (++count === 1) {
138
- done.wake();
139
- }
140
- });
141
- await scheduler.register(client.spaces.default, manifest);
142
- await scheduler.start();
143
-
144
- setTimeout(() => {
145
- const space = client.spaces.default;
146
- const object = create(TestType, { title: 'Hello world!' });
147
- space.db.add(object);
148
- }, 100);
149
-
150
- await done.wait();
151
- });
152
- });