@dxos/functions 0.1.53-main.3ecc504 → 0.1.53-main.499755c

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 (34) hide show
  1. package/README.md +29 -53
  2. package/dist/lib/browser/index.mjs +254 -65
  3. package/dist/lib/browser/index.mjs.map +4 -4
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/node/index.cjs +255 -65
  6. package/dist/lib/node/index.cjs.map +4 -4
  7. package/dist/lib/node/meta.json +1 -1
  8. package/dist/types/src/function.d.ts +30 -0
  9. package/dist/types/src/function.d.ts.map +1 -0
  10. package/dist/types/src/function.test.d.ts +2 -0
  11. package/dist/types/src/function.test.d.ts.map +1 -0
  12. package/dist/types/src/index.d.ts +1 -1
  13. package/dist/types/src/index.d.ts.map +1 -1
  14. package/dist/types/src/runtime/dev-server.d.ts +25 -0
  15. package/dist/types/src/runtime/dev-server.d.ts.map +1 -0
  16. package/dist/types/src/runtime/index.d.ts +2 -8
  17. package/dist/types/src/runtime/index.d.ts.map +1 -1
  18. package/dist/types/src/runtime/trigger-manager.d.ts +19 -0
  19. package/dist/types/src/runtime/trigger-manager.d.ts.map +1 -0
  20. package/package.json +9 -5
  21. package/src/function.ts +40 -0
  22. package/src/index.ts +1 -1
  23. package/src/runtime/dev-server.ts +131 -0
  24. package/src/runtime/index.ts +2 -81
  25. package/src/runtime/trigger-manager.ts +144 -0
  26. package/dist/types/src/defintions.d.ts +0 -19
  27. package/dist/types/src/defintions.d.ts.map +0 -1
  28. package/dist/types/src/functions.test.d.ts +0 -2
  29. package/dist/types/src/functions.test.d.ts.map +0 -1
  30. package/dist/types/src/interface.d.ts +0 -13
  31. package/dist/types/src/interface.d.ts.map +0 -1
  32. package/src/defintions.ts +0 -25
  33. package/src/interface.ts +0 -20
  34. /package/src/{functions.test.ts → function.test.ts} +0 -0
package/README.md CHANGED
@@ -2,99 +2,75 @@
2
2
 
3
3
  Functions SDK.
4
4
 
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm i @dxos/functions
9
+ ```
5
10
 
6
11
  ## Writing functions
7
12
 
8
- - Create a manifest file at package root:
13
+ Create a manifest file at the package root:
9
14
 
10
15
  ```yaml
11
- # <my package>/functions.yml
12
-
13
16
  functions:
14
- chess: # function name - must match the function executable filename
15
- description: Play chess with AI.
16
-
17
-
18
- # trigger conditions (not implemented yet)
19
- triggers:
20
- - function: chess
21
- # spaceKey: f1ed03
22
- subscription:
23
- type: dxos.experimental.chess.Game
24
-
25
-
17
+ hello:
18
+ description: Test function.
26
19
  ```
27
20
 
28
- - Write function implementation at `src/functions/<name>.ts`:
21
+ > NOTE: The function name must match the filename (e.g., `src/functions/hello.ts`).
29
22
 
30
- ```ts
31
- // <my package>/src/functions/chess.ts
23
+ Create an example function:
32
24
 
25
+ ```ts
33
26
  import { FunctionContext } from '@dxos/functions';
34
27
 
35
28
  export default (event: any, context: FunctionContext) => {
36
29
  const identity = context.client.halo.identity.get();
37
- return context.status(200).succeed({ event, greeting: `Hello, ${identity?.profile?.displayName}` });
30
+ return context
31
+ .status(200)
32
+ .succeed({
33
+ message: `Hello ${identity?.profile?.displayName}`
34
+ });
38
35
  };
39
-
40
36
  ```
41
37
 
42
- ## Running functions with dev agent
43
-
44
- 1. Configure agent to run functions dev server:
45
-
46
- ```bash
47
- code ~/.config/dx/profile/default.yml # or specify another profile
48
- ```
38
+ ## Running functions with the dev server:
49
39
 
50
- Expose functions port:
40
+ Configure the agent to run functions on a given port:
51
41
 
52
42
  ```yaml
53
-
54
- ...
55
-
43
+ # ~/.config/dx/profile/default.yml
56
44
  runtime:
57
45
  agent:
58
46
  functions:
59
47
  port: 7001
60
-
61
- ...
62
-
63
48
  ```
64
49
 
65
- 2. Load dev functions:
50
+ Start functions in dev mode (from the related package):
66
51
 
67
52
  ```bash
68
- # Run in your functions package:
69
- dx function dev -r ts-node/register
53
+ dx function dev-server -r ts-node/register
70
54
  ```
71
55
 
72
- `-r ts-node/register` configures the runtime support TypesScript natively.
73
-
74
- ### Live reload on change
75
-
76
- - Install nodemon: `npm i -g nodemon`
77
- - Wrap dev runtime in nodemon:
56
+ > NOTE: `-r ts-node/register` configures native TypesScript support.
78
57
 
79
- > NOTE: Nodemon does not support bash aliases, a binary in `$PATH` or a full path to one is required:
58
+ Install `nodemon` to support live reloading:
80
59
 
81
60
  ```bash
82
- nodemon -w src -e ts --exec /Users/dmaretskyi/Projects/protocols/packages/devtools/cli/bin/dev function dev -r ts-node/register
61
+ npm i -g nodemon
62
+ export DXOS_ROOT=$(git rev-parse --show-toplevel)
63
+
64
+ nodemon -w ./src -e ts --exec $DXOS_ROOT/packages/devtools/cli/bin/dev function dev-server -r ts-node/register
83
65
  ```
84
66
 
85
67
  ## Invoking functions
86
68
 
87
-
88
69
  > NOTE: The port (7001) must match the one in config.
89
70
 
90
71
  ```bash
91
- curl --data '{ "foo": "bar" }' -H 'Content-Type: application/json' -i -X POST http://localhost:7001/dev/chess
92
- ```
93
-
94
- ## Installation
95
-
96
- ```bash
97
- pnpm i @dxos/echo-db
72
+ curl -X POST -H 'Content-Type: application/json' -w '\n' \
73
+ http://localhost:7001/dev/hello --data '{ "message": "Hello World!" }'
98
74
  ```
99
75
 
100
76
  ## DXOS Resources
@@ -1,4 +1,4 @@
1
- import "@dxos/node-std/globals"
1
+ import "@dxos/node-std/globals";
2
2
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
3
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
4
  }) : x)(function(x) {
@@ -7,81 +7,270 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  throw new Error('Dynamic require of "' + x + '" is not supported');
8
8
  });
9
9
 
10
- // packages/core/functions/src/runtime/index.ts
10
+ // packages/core/functions/src/runtime/dev-server.ts
11
11
  import express from "express";
12
12
  import { join } from "@dxos/node-std/path";
13
13
  import { getPortPromise } from "portfinder";
14
+ import { Trigger } from "@dxos/async";
14
15
  import { log } from "@dxos/log";
15
- var runFunctions = async (options) => {
16
- const functionHandlers = {};
17
- for (const [functionName, _] of Object.entries(options.manifest.functions)) {
18
- try {
19
- const module = __require(join(options.functionsDirectory, functionName));
20
- const handler = module.default;
21
- if (typeof handler !== "function") {
22
- throw new Error(`Function ${functionName} does not export a default function`);
16
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
17
+ var DEFAULT_PORT = 7e3;
18
+ var DevServer = class {
19
+ // prettier-ignore
20
+ constructor(_client, _options) {
21
+ this._client = _client;
22
+ this._options = _options;
23
+ this._functionHandlers = {};
24
+ }
25
+ get port() {
26
+ return this._port;
27
+ }
28
+ get endpoint() {
29
+ return this._port ? `http://localhost:${this._port}` : void 0;
30
+ }
31
+ get functions() {
32
+ return Object.keys(this._functionHandlers);
33
+ }
34
+ async initialize() {
35
+ for (const [name, _] of Object.entries(this._options.manifest.functions)) {
36
+ try {
37
+ const module = __require(join(this._options.directory, name));
38
+ const handler = module.default;
39
+ if (typeof handler !== "function") {
40
+ throw new Error(`Handler must export default function: ${name}`);
41
+ }
42
+ this._functionHandlers[name] = handler;
43
+ } catch (err) {
44
+ log.error("parsing function (check functions.yml manifest)", err, {
45
+ F: __dxlog_file,
46
+ L: 63,
47
+ S: this,
48
+ C: (f, a) => f(...a)
49
+ });
23
50
  }
24
- functionHandlers[functionName] = handler;
25
- } catch (e) {
26
- console.error(e);
27
51
  }
28
52
  }
29
- const port = await getPortPromise({
30
- startPort: 7e3
31
- });
32
- const app = express();
33
- app.use(express.json());
34
- app.post("/:functionName", async (req, res) => {
35
- const functionName = req.params.functionName;
36
- const replyBuilder = {
37
- status: (code) => {
38
- res.statusCode = code;
39
- return replyBuilder;
40
- },
41
- succeed: (result) => {
42
- res.end(JSON.stringify(result));
43
- return replyBuilder;
53
+ async start() {
54
+ const app = express();
55
+ app.use(express.json());
56
+ app.post("/:functionName", async (req, res) => {
57
+ const functionName = req.params.functionName;
58
+ log("invoke", {
59
+ function: functionName,
60
+ data: req.body
61
+ }, {
62
+ F: __dxlog_file,
63
+ L: 74,
64
+ S: this,
65
+ C: (f, a) => f(...a)
66
+ });
67
+ const builder = {
68
+ status: (code) => {
69
+ res.statusCode = code;
70
+ return builder;
71
+ },
72
+ succeed: (result = {}) => {
73
+ res.end(JSON.stringify(result));
74
+ return builder;
75
+ }
76
+ };
77
+ const context = {
78
+ client: this._client,
79
+ status: builder.status.bind(builder)
80
+ };
81
+ void (async () => {
82
+ try {
83
+ await this._functionHandlers[functionName](req.body, context);
84
+ } catch (err) {
85
+ res.statusCode = 500;
86
+ res.end(err.message);
87
+ }
88
+ })();
89
+ });
90
+ this._port = await getPortPromise({
91
+ startPort: DEFAULT_PORT
92
+ });
93
+ this._server = app.listen(this._port);
94
+ const { registrationId } = await this._client.services.services.FunctionRegistryService.register({
95
+ endpoint: this.endpoint,
96
+ functions: this.functions.map((name) => ({
97
+ name
98
+ }))
99
+ });
100
+ this._registrationId = registrationId;
101
+ }
102
+ async stop() {
103
+ var _a;
104
+ const trigger = new Trigger();
105
+ (_a = this._server) == null ? void 0 : _a.close(async () => {
106
+ if (this._registrationId) {
107
+ await this._client.services.services.FunctionRegistryService.unregister({
108
+ registrationId: this._registrationId
109
+ });
110
+ this._registrationId = void 0;
44
111
  }
45
- };
46
- const context = {
47
- client: options.client,
48
- status: replyBuilder.status.bind(replyBuilder)
49
- };
50
- void (async () => {
51
- try {
52
- await functionHandlers[functionName](req.body, context);
53
- } catch (err) {
54
- res.statusCode = 500;
55
- res.end(err.message);
112
+ trigger.wake();
113
+ });
114
+ await trigger.wait();
115
+ this._server = void 0;
116
+ this._port = void 0;
117
+ }
118
+ };
119
+
120
+ // packages/core/functions/src/runtime/trigger-manager.ts
121
+ import assert from "@dxos/node-std/assert";
122
+ import { DeferredTask } from "@dxos/async";
123
+ import { Context } from "@dxos/context";
124
+ import { createSubscription } from "@dxos/echo-schema";
125
+ import { log as log2 } from "@dxos/log";
126
+ import { ComplexMap } from "@dxos/util";
127
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/trigger-manager.ts";
128
+ var TriggerManager = class {
129
+ constructor(_client, _triggers, _invokeOptions) {
130
+ this._client = _client;
131
+ this._triggers = _triggers;
132
+ this._invokeOptions = _invokeOptions;
133
+ this._mounts = new ComplexMap(({ name, spaceKey }) => `${spaceKey.toHex()}:${name}`);
134
+ }
135
+ async start() {
136
+ this._client.spaces.subscribe(async (spaces) => {
137
+ for (const space of spaces) {
138
+ await space.waitUntilReady();
139
+ for (const trigger of this._triggers) {
140
+ await this.mount(new Context(), trigger, space);
141
+ }
56
142
  }
57
- })();
58
- });
59
- app.listen(port);
60
- const functionNames = Object.keys(functionHandlers);
61
- const { registrationId } = await options.client.services.services.FunctionRegistryService.register({
62
- endpoint: `http://localhost:${port}`,
63
- functions: functionNames.map((name) => ({
64
- name
65
- }))
66
- });
67
- process.on("SIGINT", async () => {
68
- await options.client.services.services.FunctionRegistryService.unregister({
69
- registrationId
70
143
  });
71
- process.exit();
72
- });
73
- log.info("functions runtime started", {
74
- port,
75
- functionNames,
76
- registrationId
77
- }, {
78
- file: "index.ts",
79
- line: 84,
80
- scope: void 0,
81
- callSite: (f, a) => f(...a)
82
- });
144
+ }
145
+ async stop() {
146
+ for (const { name, spaceKey } of this._mounts.keys()) {
147
+ await this.unmount(name, spaceKey);
148
+ }
149
+ }
150
+ async mount(ctx, trigger, space) {
151
+ const key = {
152
+ name: trigger.function,
153
+ spaceKey: space.key
154
+ };
155
+ const exists = this._mounts.get(key);
156
+ if (!exists) {
157
+ this._mounts.set(key, {
158
+ ctx,
159
+ trigger
160
+ });
161
+ if (ctx.disposed) {
162
+ return;
163
+ }
164
+ let count = 0;
165
+ const objectIds = /* @__PURE__ */ new Set();
166
+ const task = new DeferredTask(ctx, async () => {
167
+ const updatedObjects = Array.from(objectIds);
168
+ objectIds.clear();
169
+ await this.invokeFunction(this._invokeOptions, trigger.function, {
170
+ space: space.key,
171
+ objects: updatedObjects
172
+ });
173
+ });
174
+ const selection = createSubscription(({ added, updated }) => {
175
+ for (const object of added) {
176
+ objectIds.add(object.id);
177
+ }
178
+ for (const object of updated) {
179
+ objectIds.add(object.id);
180
+ }
181
+ log2.info("updated", {
182
+ space: space.key,
183
+ objects: objectIds.size,
184
+ added: added.length,
185
+ updated: updated.length,
186
+ count
187
+ }, {
188
+ F: __dxlog_file2,
189
+ L: 86,
190
+ S: this,
191
+ C: (f, a) => f(...a)
192
+ });
193
+ if (count++) {
194
+ task.schedule();
195
+ }
196
+ });
197
+ ctx.onDispose(() => selection.unsubscribe());
198
+ const query = space.db.query({
199
+ ...trigger.subscription.props,
200
+ "@type": trigger.subscription.type
201
+ });
202
+ const unsubscribe = query.subscribe(({ objects }) => {
203
+ selection.update(objects);
204
+ });
205
+ ctx.onDispose(unsubscribe);
206
+ log2.info("mounted", {
207
+ space: space.key,
208
+ trigger
209
+ }, {
210
+ F: __dxlog_file2,
211
+ L: 110,
212
+ S: this,
213
+ C: (f, a) => f(...a)
214
+ });
215
+ }
216
+ }
217
+ async unmount(name, spaceKey) {
218
+ var _a;
219
+ const key = {
220
+ name,
221
+ spaceKey
222
+ };
223
+ const { ctx } = (_a = this._mounts.get(key)) != null ? _a : {};
224
+ if (ctx) {
225
+ this._mounts.delete(key);
226
+ await ctx.dispose();
227
+ }
228
+ }
229
+ async invokeFunction(options, functionName, data) {
230
+ const { endpoint, runtime } = options;
231
+ assert(endpoint, "Missing endpoint");
232
+ assert(runtime, "Missing runtime");
233
+ try {
234
+ log2("invoke", {
235
+ function: functionName
236
+ }, {
237
+ F: __dxlog_file2,
238
+ L: 129,
239
+ S: this,
240
+ C: (f, a) => f(...a)
241
+ });
242
+ const url = `${endpoint}/${runtime}/${functionName}`;
243
+ const res = await fetch(url, {
244
+ method: "POST",
245
+ body: JSON.stringify(data),
246
+ headers: {
247
+ "Content-Type": "application/json"
248
+ }
249
+ });
250
+ log2("result", {
251
+ function: functionName,
252
+ result: await res.json()
253
+ }, {
254
+ F: __dxlog_file2,
255
+ L: 139,
256
+ S: this,
257
+ C: (f, a) => f(...a)
258
+ });
259
+ } catch (err) {
260
+ log2.error("error", {
261
+ function: functionName,
262
+ error: err.message
263
+ }, {
264
+ F: __dxlog_file2,
265
+ L: 141,
266
+ S: this,
267
+ C: (f, a) => f(...a)
268
+ });
269
+ }
270
+ }
83
271
  };
84
272
  export {
85
- runFunctions
273
+ DevServer,
274
+ TriggerManager
86
275
  };
87
276
  //# sourceMappingURL=index.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../../src/runtime/index.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { join } from 'node:path';\nimport { getPortPromise } from 'portfinder';\n\nimport { Client } from '@dxos/client';\nimport { log } from '@dxos/log';\n\nimport { FunctionsManifest } from '../defintions';\nimport { FunctionContext, FunctionHandler, Reply } from '../interface';\n\nexport type FunctionsRuntimeParams = {\n client: Client;\n functionsDirectory: string;\n manifest: FunctionsManifest;\n};\n\nexport const runFunctions = async (options: FunctionsRuntimeParams) => {\n const functionHandlers: Record<string, FunctionHandler> = {};\n\n for (const [functionName, _] of Object.entries(options.manifest.functions)) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(join(options.functionsDirectory, functionName));\n const handler = module.default;\n if (typeof handler !== 'function') {\n throw new Error(`Function ${functionName} does not export a default function`);\n }\n\n functionHandlers[functionName] = handler;\n } catch (e) {\n console.error(e);\n }\n }\n\n const port = await getPortPromise({ startPort: 7000 });\n\n const app = express();\n app.use(express.json());\n\n app.post('/:functionName', async (req, res) => {\n const functionName = req.params.functionName;\n\n const replyBuilder: Reply = {\n status: (code: number) => {\n res.statusCode = code;\n return replyBuilder;\n },\n succeed: (result: any) => {\n res.end(JSON.stringify(result));\n return replyBuilder;\n },\n };\n const context: FunctionContext = {\n client: options.client,\n status: replyBuilder.status.bind(replyBuilder),\n };\n\n void (async () => {\n try {\n await functionHandlers[functionName](req.body, context);\n } catch (err: any) {\n res.statusCode = 500;\n res.end(err.message);\n }\n })();\n });\n app.listen(port);\n\n const functionNames = Object.keys(functionHandlers);\n const { registrationId } = await options.client.services.services.FunctionRegistryService!.register({\n endpoint: `http://localhost:${port}`,\n functions: functionNames.map((name) => ({ name })),\n });\n\n process.on('SIGINT', async () => {\n await options.client.services.services.FunctionRegistryService!.unregister({ registrationId });\n process.exit();\n });\n\n log.info('functions runtime started', { port, functionNames, registrationId });\n};\n"],
5
- "mappings": ";;;;;;;;;;AAIA,OAAOA,aAAa;AACpB,SAASC,YAAY;AACrB,SAASC,sBAAsB;AAG/B,SAASC,WAAW;AAWb,IAAMC,eAAe,OAAOC,YAAoC;AACrE,QAAMC,mBAAoD,CAAC;AAE3D,aAAW,CAACC,cAAcC,CAAAA,KAAMC,OAAOC,QAAQL,QAAQM,SAASC,SAAS,GAAG;AAC1E,QAAI;AAEF,YAAMC,SAASC,UAAQC,KAAKV,QAAQW,oBAAoBT,YAAAA,CAAAA;AACxD,YAAMU,UAAUJ,OAAOK;AACvB,UAAI,OAAOD,YAAY,YAAY;AACjC,cAAM,IAAIE,MAAM,YAAYZ,iDAAiD;MAC/E;AAEAD,uBAAiBC,YAAAA,IAAgBU;IACnC,SAASG,GAAP;AACAC,cAAQC,MAAMF,CAAAA;IAChB;EACF;AAEA,QAAMG,OAAO,MAAMC,eAAe;IAAEC,WAAW;EAAK,CAAA;AAEpD,QAAMC,MAAMC,QAAAA;AACZD,MAAIE,IAAID,QAAQE,KAAI,CAAA;AAEpBH,MAAII,KAAK,kBAAkB,OAAOC,KAAKC,QAAQ;AAC7C,UAAMzB,eAAewB,IAAIE,OAAO1B;AAEhC,UAAM2B,eAAsB;MAC1BC,QAAQ,CAACC,SAAiB;AACxBJ,YAAIK,aAAaD;AACjB,eAAOF;MACT;MACAI,SAAS,CAACC,WAAgB;AACxBP,YAAIQ,IAAIC,KAAKC,UAAUH,MAAAA,CAAAA;AACvB,eAAOL;MACT;IACF;AACA,UAAMS,UAA2B;MAC/BC,QAAQvC,QAAQuC;MAChBT,QAAQD,aAAaC,OAAOU,KAAKX,YAAAA;IACnC;AAEA,UAAM,YAAY;AAChB,UAAI;AACF,cAAM5B,iBAAiBC,YAAAA,EAAcwB,IAAIe,MAAMH,OAAAA;MACjD,SAASI,KAAP;AACAf,YAAIK,aAAa;AACjBL,YAAIQ,IAAIO,IAAIC,OAAO;MACrB;IACF,GAAA;EACF,CAAA;AACAtB,MAAIuB,OAAO1B,IAAAA;AAEX,QAAM2B,gBAAgBzC,OAAO0C,KAAK7C,gBAAAA;AAClC,QAAM,EAAE8C,eAAc,IAAK,MAAM/C,QAAQuC,OAAOS,SAASA,SAASC,wBAAyBC,SAAS;IAClGC,UAAU,oBAAoBjC;IAC9BX,WAAWsC,cAAcO,IAAI,CAACC,UAAU;MAAEA;IAAK,EAAA;EACjD,CAAA;AAEAC,UAAQC,GAAG,UAAU,YAAY;AAC/B,UAAMvD,QAAQuC,OAAOS,SAASA,SAASC,wBAAyBO,WAAW;MAAET;IAAe,CAAA;AAC5FO,YAAQG,KAAI;EACd,CAAA;AAEAC,MAAIC,KAAK,6BAA6B;IAAEzC;IAAM2B;IAAeE;EAAe,GAAA;;;;;;AAC9E;",
6
- "names": ["express", "join", "getPortPromise", "log", "runFunctions", "options", "functionHandlers", "functionName", "_", "Object", "entries", "manifest", "functions", "module", "require", "join", "functionsDirectory", "handler", "default", "Error", "e", "console", "error", "port", "getPortPromise", "startPort", "app", "express", "use", "json", "post", "req", "res", "params", "replyBuilder", "status", "code", "statusCode", "succeed", "result", "end", "JSON", "stringify", "context", "client", "bind", "body", "err", "message", "listen", "functionNames", "keys", "registrationId", "services", "FunctionRegistryService", "register", "endpoint", "map", "name", "process", "on", "unregister", "exit", "log", "info"]
3
+ "sources": ["../../../src/runtime/dev-server.ts", "../../../src/runtime/trigger-manager.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport http from 'http';\nimport { join } from 'node:path';\nimport { getPortPromise } from 'portfinder';\n\nimport { Trigger } from '@dxos/async';\nimport { Client } from '@dxos/client';\nimport { log } from '@dxos/log';\n\nimport { FunctionContext, FunctionHandler, FunctionsManifest, Response } from '../function';\n\nconst DEFAULT_PORT = 7000;\n\nexport type DevServerOptions = {\n directory: string;\n manifest: FunctionsManifest;\n};\n\n/**\n * Functions dev server provides a local HTTP server for testing functions.\n */\nexport class DevServer {\n private readonly _functionHandlers: Record<string, FunctionHandler> = {};\n\n private _server?: http.Server;\n private _port?: number;\n private _registrationId?: string;\n\n // prettier-ignore\n constructor(\n private readonly _client: Client,\n private readonly _options: DevServerOptions\n ) {}\n\n get port() {\n return this._port;\n }\n\n get endpoint() {\n return this._port ? `http://localhost:${this._port}` : undefined;\n }\n\n get functions() {\n return Object.keys(this._functionHandlers);\n }\n\n async initialize() {\n for (const [name, _] of Object.entries(this._options.manifest.functions)) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(join(this._options.directory, name));\n const handler = module.default;\n if (typeof handler !== 'function') {\n throw new Error(`Handler must export default function: ${name}`);\n }\n\n this._functionHandlers[name] = handler;\n } catch (err) {\n log.error('parsing function (check functions.yml manifest)', err);\n }\n }\n }\n\n async start() {\n const app = express();\n app.use(express.json());\n\n app.post('/:functionName', async (req, res) => {\n const functionName = req.params.functionName;\n log('invoke', { function: functionName, data: req.body });\n\n const builder: Response = {\n status: (code: number) => {\n res.statusCode = code;\n return builder;\n },\n succeed: (result = {}) => {\n res.end(JSON.stringify(result));\n return builder;\n },\n };\n\n const context: FunctionContext = {\n client: this._client,\n status: builder.status.bind(builder),\n };\n\n void (async () => {\n try {\n await this._functionHandlers[functionName](req.body, context);\n } catch (err: any) {\n res.statusCode = 500;\n res.end(err.message);\n }\n })();\n });\n\n this._port = await getPortPromise({ startPort: DEFAULT_PORT });\n this._server = app.listen(this._port);\n\n // TODO(burdon): Check plugin is registered.\n // TypeError: Cannot read properties of undefined (reading 'register')\n const { registrationId } = await this._client.services.services.FunctionRegistryService!.register({\n endpoint: this.endpoint!,\n functions: this.functions.map((name) => ({ name })),\n });\n this._registrationId = registrationId;\n }\n\n async stop() {\n const trigger = new Trigger();\n this._server?.close(async () => {\n if (this._registrationId) {\n await this._client.services.services.FunctionRegistryService!.unregister({\n registrationId: this._registrationId,\n });\n this._registrationId = undefined;\n }\n\n trigger.wake();\n });\n\n await trigger.wait();\n this._server = undefined;\n this._port = undefined;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport assert from 'node:assert';\n\nimport { DeferredTask } from '@dxos/async';\nimport { Client, PublicKey } from '@dxos/client';\nimport type { Space } from '@dxos/client/echo';\nimport { Context } from '@dxos/context';\nimport { createSubscription } from '@dxos/echo-schema';\nimport { log } from '@dxos/log';\nimport { ComplexMap } from '@dxos/util';\n\nimport { FunctionTrigger } from '../function';\n\n// TODO(burdon): Rename.\nexport type InvokeOptions = {\n endpoint: string;\n runtime: string;\n};\n\nexport class TriggerManager {\n private readonly _mounts = new ComplexMap<\n { name: string; spaceKey: PublicKey },\n { ctx: Context; trigger: FunctionTrigger }\n >(({ name, spaceKey }) => `${spaceKey.toHex()}:${name}`);\n\n constructor(\n private readonly _client: Client,\n private readonly _triggers: FunctionTrigger[],\n private readonly _invokeOptions: InvokeOptions,\n ) {}\n\n async start() {\n // TODO(burdon): Make runtime configurable (via CLI)?\n this._client.spaces.subscribe(async (spaces) => {\n for (const space of spaces) {\n await space.waitUntilReady();\n for (const trigger of this._triggers) {\n // TODO(burdon): New context? Shared?\n await this.mount(new Context(), trigger, space);\n }\n }\n });\n }\n\n async stop() {\n for (const { name, spaceKey } of this._mounts.keys()) {\n await this.unmount(name, spaceKey);\n }\n }\n\n private async mount(ctx: Context, trigger: FunctionTrigger, space: Space) {\n const key = { name: trigger.function, spaceKey: space.key };\n const exists = this._mounts.get(key);\n if (!exists) {\n this._mounts.set(key, { ctx, trigger });\n if (ctx.disposed) {\n return;\n }\n\n // TODO(burdon): Factor out subscription/result delta.\n\n let count = 0;\n const objectIds = new Set<string>();\n const task = new DeferredTask(ctx, async () => {\n const updatedObjects = Array.from(objectIds);\n objectIds.clear();\n\n await this.invokeFunction(this._invokeOptions, trigger.function, {\n space: space.key,\n objects: updatedObjects,\n });\n });\n\n // TODO(burdon): Removed?\n const selection = createSubscription(({ added, updated }) => {\n for (const object of added) {\n objectIds.add(object.id);\n }\n for (const object of updated) {\n objectIds.add(object.id);\n }\n\n log.info('updated', {\n space: space.key,\n objects: objectIds.size,\n added: added.length,\n updated: updated.length,\n count,\n });\n if (count++) {\n task.schedule();\n }\n });\n\n ctx.onDispose(() => selection.unsubscribe());\n\n const query = space.db.query({ ...trigger.subscription.props, '@type': trigger.subscription.type });\n const unsubscribe = query.subscribe(({ objects }) => {\n selection.update(objects);\n });\n\n // Trigger first update, but don't schedule task.\n // selection.update(query.objects);\n\n ctx.onDispose(unsubscribe);\n\n log.info('mounted', { space: space.key, trigger });\n }\n }\n\n private async unmount(name: string, spaceKey: PublicKey) {\n const key = { name, spaceKey };\n const { ctx } = this._mounts.get(key) ?? {};\n if (ctx) {\n this._mounts.delete(key);\n await ctx.dispose();\n }\n }\n\n private async invokeFunction(options: InvokeOptions, functionName: string, data: any) {\n const { endpoint, runtime } = options;\n assert(endpoint, 'Missing endpoint');\n assert(runtime, 'Missing runtime');\n\n try {\n log('invoke', { function: functionName });\n const url = `${endpoint}/${runtime}/${functionName}`;\n const res = await fetch(url, {\n method: 'POST',\n body: JSON.stringify(data),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n log('result', { function: functionName, result: await res.json() });\n } catch (err: any) {\n log.error('error', { function: functionName, error: err.message });\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;AAIA,OAAOA,aAAa;AAEpB,SAASC,YAAY;AACrB,SAASC,sBAAsB;AAE/B,SAASC,eAAe;AAExB,SAASC,WAAW;;AAIpB,IAAMC,eAAe;AAUd,IAAMC,YAAN,MAAMA;;EAQXC,YACmBC,SACAC,UACjB;mBAFiBD;oBACAC;SATFC,oBAAqD,CAAC;EAUpE;EAEH,IAAIC,OAAO;AACT,WAAO,KAAKC;EACd;EAEA,IAAIC,WAAW;AACb,WAAO,KAAKD,QAAQ,oBAAoB,KAAKA,UAAUE;EACzD;EAEA,IAAIC,YAAY;AACd,WAAOC,OAAOC,KAAK,KAAKP,iBAAiB;EAC3C;EAEA,MAAMQ,aAAa;AACjB,eAAW,CAACC,MAAMC,CAAAA,KAAMJ,OAAOK,QAAQ,KAAKZ,SAASa,SAASP,SAAS,GAAG;AACxE,UAAI;AAEF,cAAMQ,SAASC,UAAQC,KAAK,KAAKhB,SAASiB,WAAWP,IAAAA,CAAAA;AACrD,cAAMQ,UAAUJ,OAAOK;AACvB,YAAI,OAAOD,YAAY,YAAY;AACjC,gBAAM,IAAIE,MAAM,yCAAyCV,MAAM;QACjE;AAEA,aAAKT,kBAAkBS,IAAAA,IAAQQ;MACjC,SAASG,KAAP;AACAC,YAAIC,MAAM,mDAAmDF,KAAAA;;;;;;MAC/D;IACF;EACF;EAEA,MAAMG,QAAQ;AACZ,UAAMC,MAAMC,QAAAA;AACZD,QAAIE,IAAID,QAAQE,KAAI,CAAA;AAEpBH,QAAII,KAAK,kBAAkB,OAAOC,KAAKC,QAAAA;AACrC,YAAMC,eAAeF,IAAIG,OAAOD;AAChCV,UAAI,UAAU;QAAEY,UAAUF;QAAcG,MAAML,IAAIM;MAAK,GAAA;;;;;;AAEvD,YAAMC,UAAoB;QACxBC,QAAQ,CAACC,SAAAA;AACPR,cAAIS,aAAaD;AACjB,iBAAOF;QACT;QACAI,SAAS,CAACC,SAAS,CAAC,MAAC;AACnBX,cAAIY,IAAIC,KAAKC,UAAUH,MAAAA,CAAAA;AACvB,iBAAOL;QACT;MACF;AAEA,YAAMS,UAA2B;QAC/BC,QAAQ,KAAKhD;QACbuC,QAAQD,QAAQC,OAAOU,KAAKX,OAAAA;MAC9B;AAEA,YAAM,YAAA;AACJ,YAAI;AACF,gBAAM,KAAKpC,kBAAkB+B,YAAAA,EAAcF,IAAIM,MAAMU,OAAAA;QACvD,SAASzB,KAAP;AACAU,cAAIS,aAAa;AACjBT,cAAIY,IAAItB,IAAI4B,OAAO;QACrB;MACF,GAAA;IACF,CAAA;AAEA,SAAK9C,QAAQ,MAAM+C,eAAe;MAAEC,WAAWvD;IAAa,CAAA;AAC5D,SAAKwD,UAAU3B,IAAI4B,OAAO,KAAKlD,KAAK;AAIpC,UAAM,EAAEmD,eAAc,IAAK,MAAM,KAAKvD,QAAQwD,SAASA,SAASC,wBAAyBC,SAAS;MAChGrD,UAAU,KAAKA;MACfE,WAAW,KAAKA,UAAUoD,IAAI,CAAChD,UAAU;QAAEA;MAAK,EAAA;IAClD,CAAA;AACA,SAAKiD,kBAAkBL;EACzB;EAEA,MAAMM,OAAO;AAjHf;AAkHI,UAAMC,UAAU,IAAIC,QAAAA;AACpB,eAAKV,YAAL,mBAAcW,MAAM,YAAA;AAClB,UAAI,KAAKJ,iBAAiB;AACxB,cAAM,KAAK5D,QAAQwD,SAASA,SAASC,wBAAyBQ,WAAW;UACvEV,gBAAgB,KAAKK;QACvB,CAAA;AACA,aAAKA,kBAAkBtD;MACzB;AAEAwD,cAAQI,KAAI;IACd;AAEA,UAAMJ,QAAQK,KAAI;AAClB,SAAKd,UAAU/C;AACf,SAAKF,QAAQE;EACf;AACF;;;AC9HA,OAAO8D,YAAY;AAEnB,SAASC,oBAAoB;AAG7B,SAASC,eAAe;AACxB,SAASC,0BAA0B;AACnC,SAASC,OAAAA,YAAW;AACpB,SAASC,kBAAkB;;AAUpB,IAAMC,iBAAN,MAAMA;EAMXC,YACmBC,SACAC,WACAC,gBACjB;mBAHiBF;qBACAC;0BACAC;SARFC,UAAU,IAAIN,WAG7B,CAAC,EAAEO,MAAMC,SAAQ,MAAO,GAAGA,SAASC,MAAK,KAAMF,MAAM;EAMpD;EAEH,MAAMG,QAAQ;AAEZ,SAAKP,QAAQQ,OAAOC,UAAU,OAAOD,WAAAA;AACnC,iBAAWE,SAASF,QAAQ;AAC1B,cAAME,MAAMC,eAAc;AAC1B,mBAAWC,WAAW,KAAKX,WAAW;AAEpC,gBAAM,KAAKY,MAAM,IAAInB,QAAAA,GAAWkB,SAASF,KAAAA;QAC3C;MACF;IACF,CAAA;EACF;EAEA,MAAMI,OAAO;AACX,eAAW,EAAEV,MAAMC,SAAQ,KAAM,KAAKF,QAAQY,KAAI,GAAI;AACpD,YAAM,KAAKC,QAAQZ,MAAMC,QAAAA;IAC3B;EACF;EAEA,MAAcQ,MAAMI,KAAcL,SAA0BF,OAAc;AACxE,UAAMQ,MAAM;MAAEd,MAAMQ,QAAQO;MAAUd,UAAUK,MAAMQ;IAAI;AAC1D,UAAME,SAAS,KAAKjB,QAAQkB,IAAIH,GAAAA;AAChC,QAAI,CAACE,QAAQ;AACX,WAAKjB,QAAQmB,IAAIJ,KAAK;QAAED;QAAKL;MAAQ,CAAA;AACrC,UAAIK,IAAIM,UAAU;AAChB;MACF;AAIA,UAAIC,QAAQ;AACZ,YAAMC,YAAY,oBAAIC,IAAAA;AACtB,YAAMC,OAAO,IAAIlC,aAAawB,KAAK,YAAA;AACjC,cAAMW,iBAAiBC,MAAMC,KAAKL,SAAAA;AAClCA,kBAAUM,MAAK;AAEf,cAAM,KAAKC,eAAe,KAAK9B,gBAAgBU,QAAQO,UAAU;UAC/DT,OAAOA,MAAMQ;UACbe,SAASL;QACX,CAAA;MACF,CAAA;AAGA,YAAMM,YAAYvC,mBAAmB,CAAC,EAAEwC,OAAOC,QAAO,MAAE;AACtD,mBAAWC,UAAUF,OAAO;AAC1BV,oBAAUa,IAAID,OAAOE,EAAE;QACzB;AACA,mBAAWF,UAAUD,SAAS;AAC5BX,oBAAUa,IAAID,OAAOE,EAAE;QACzB;AAEA3C,QAAAA,KAAI4C,KAAK,WAAW;UAClB9B,OAAOA,MAAMQ;UACbe,SAASR,UAAUgB;UACnBN,OAAOA,MAAMO;UACbN,SAASA,QAAQM;UACjBlB;QACF,GAAA;;;;;;AACA,YAAIA,SAAS;AACXG,eAAKgB,SAAQ;QACf;MACF,CAAA;AAEA1B,UAAI2B,UAAU,MAAMV,UAAUW,YAAW,CAAA;AAEzC,YAAMC,QAAQpC,MAAMqC,GAAGD,MAAM;QAAE,GAAGlC,QAAQoC,aAAaC;QAAO,SAASrC,QAAQoC,aAAaE;MAAK,CAAA;AACjG,YAAML,cAAcC,MAAMrC,UAAU,CAAC,EAAEwB,QAAO,MAAE;AAC9CC,kBAAUiB,OAAOlB,OAAAA;MACnB,CAAA;AAKAhB,UAAI2B,UAAUC,WAAAA;AAEdjD,MAAAA,KAAI4C,KAAK,WAAW;QAAE9B,OAAOA,MAAMQ;QAAKN;MAAQ,GAAA;;;;;;IAClD;EACF;EAEA,MAAcI,QAAQZ,MAAcC,UAAqB;AAjH3D;AAkHI,UAAMa,MAAM;MAAEd;MAAMC;IAAS;AAC7B,UAAM,EAAEY,IAAG,KAAK,UAAKd,QAAQkB,IAAIH,GAAAA,MAAjB,YAAyB,CAAC;AAC1C,QAAID,KAAK;AACP,WAAKd,QAAQiD,OAAOlC,GAAAA;AACpB,YAAMD,IAAIoC,QAAO;IACnB;EACF;EAEA,MAAcrB,eAAesB,SAAwBC,cAAsBC,MAAW;AACpF,UAAM,EAAEC,UAAUC,QAAO,IAAKJ;AAC9B9D,WAAOiE,UAAU,kBAAA;AACjBjE,WAAOkE,SAAS,iBAAA;AAEhB,QAAI;AACF9D,MAAAA,KAAI,UAAU;QAAEuB,UAAUoC;MAAa,GAAA;;;;;;AACvC,YAAMI,MAAM,GAAGF,YAAYC,WAAWH;AACtC,YAAMK,MAAM,MAAMC,MAAMF,KAAK;QAC3BG,QAAQ;QACRC,MAAMC,KAAKC,UAAUT,IAAAA;QACrBU,SAAS;UACP,gBAAgB;QAClB;MACF,CAAA;AAEAtE,MAAAA,KAAI,UAAU;QAAEuB,UAAUoC;QAAcY,QAAQ,MAAMP,IAAIQ,KAAI;MAAG,GAAA;;;;;;IACnE,SAASC,KAAP;AACAzE,MAAAA,KAAI0E,MAAM,SAAS;QAAEnD,UAAUoC;QAAce,OAAOD,IAAIE;MAAQ,GAAA;;;;;;IAClE;EACF;AACF;",
6
+ "names": ["express", "join", "getPortPromise", "Trigger", "log", "DEFAULT_PORT", "DevServer", "constructor", "_client", "_options", "_functionHandlers", "port", "_port", "endpoint", "undefined", "functions", "Object", "keys", "initialize", "name", "_", "entries", "manifest", "module", "require", "join", "directory", "handler", "default", "Error", "err", "log", "error", "start", "app", "express", "use", "json", "post", "req", "res", "functionName", "params", "function", "data", "body", "builder", "status", "code", "statusCode", "succeed", "result", "end", "JSON", "stringify", "context", "client", "bind", "message", "getPortPromise", "startPort", "_server", "listen", "registrationId", "services", "FunctionRegistryService", "register", "map", "_registrationId", "stop", "trigger", "Trigger", "close", "unregister", "wake", "wait", "assert", "DeferredTask", "Context", "createSubscription", "log", "ComplexMap", "TriggerManager", "constructor", "_client", "_triggers", "_invokeOptions", "_mounts", "name", "spaceKey", "toHex", "start", "spaces", "subscribe", "space", "waitUntilReady", "trigger", "mount", "stop", "keys", "unmount", "ctx", "key", "function", "exists", "get", "set", "disposed", "count", "objectIds", "Set", "task", "updatedObjects", "Array", "from", "clear", "invokeFunction", "objects", "selection", "added", "updated", "object", "add", "id", "info", "size", "length", "schedule", "onDispose", "unsubscribe", "query", "db", "subscription", "props", "type", "update", "delete", "dispose", "options", "functionName", "data", "endpoint", "runtime", "url", "res", "fetch", "method", "body", "JSON", "stringify", "headers", "result", "json", "err", "error", "message"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/core/functions/src/interface.ts":{"bytes":756,"imports":[]},"packages/core/functions/src/runtime/index.ts":{"bytes":9127,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"portfinder","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}]},"packages/core/functions/src/index.ts":{"bytes":464,"imports":[{"path":"packages/core/functions/src/interface.ts","kind":"import-statement","original":"./interface"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}]}},"outputs":{"packages/core/functions/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":4827},"packages/core/functions/dist/lib/browser/index.mjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"portfinder","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true}],"exports":["runFunctions"],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":2098}},"bytes":2614}}}
1
+ {"inputs":{"packages/core/functions/src/function.ts":{"bytes":1422,"imports":[]},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":13335,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"portfinder","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}]},"packages/core/functions/src/runtime/trigger-manager.ts":{"bytes":16196,"imports":[{"path":"@dxos/node-std/assert","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}]},"packages/core/functions/src/runtime/index.ts":{"bytes":584,"imports":[{"path":"packages/core/functions/src/runtime/dev-server.ts","kind":"import-statement","original":"./dev-server"},{"path":"packages/core/functions/src/runtime/trigger-manager.ts","kind":"import-statement","original":"./trigger-manager"}]},"packages/core/functions/src/index.ts":{"bytes":550,"imports":[{"path":"packages/core/functions/src/function.ts","kind":"import-statement","original":"./function"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}]}},"outputs":{"packages/core/functions/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":14209},"packages/core/functions/dist/lib/browser/index.mjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"portfinder","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/node-std/assert","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["DevServer","TriggerManager"],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/dev-server.ts":{"bytesInOutput":3119},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/trigger-manager.ts":{"bytesInOutput":4112}},"bytes":7827}}}