@dxos/functions 0.1.52 → 0.1.53-main.3ecc504

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.
package/README.md CHANGED
@@ -1,6 +1,95 @@
1
- # @dxos/echo-db
1
+ # @dxos/functions
2
2
 
3
- ECHO database.
3
+ Functions SDK.
4
+
5
+
6
+ ## Writing functions
7
+
8
+ - Create a manifest file at package root:
9
+
10
+ ```yaml
11
+ # <my package>/functions.yml
12
+
13
+ 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
+
26
+ ```
27
+
28
+ - Write function implementation at `src/functions/<name>.ts`:
29
+
30
+ ```ts
31
+ // <my package>/src/functions/chess.ts
32
+
33
+ import { FunctionContext } from '@dxos/functions';
34
+
35
+ export default (event: any, context: FunctionContext) => {
36
+ const identity = context.client.halo.identity.get();
37
+ return context.status(200).succeed({ event, greeting: `Hello, ${identity?.profile?.displayName}` });
38
+ };
39
+
40
+ ```
41
+
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
+ ```
49
+
50
+ Expose functions port:
51
+
52
+ ```yaml
53
+
54
+ ...
55
+
56
+ runtime:
57
+ agent:
58
+ functions:
59
+ port: 7001
60
+
61
+ ...
62
+
63
+ ```
64
+
65
+ 2. Load dev functions:
66
+
67
+ ```bash
68
+ # Run in your functions package:
69
+ dx function dev -r ts-node/register
70
+ ```
71
+
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:
78
+
79
+ > NOTE: Nodemon does not support bash aliases, a binary in `$PATH` or a full path to one is required:
80
+
81
+ ```bash
82
+ nodemon -w src -e ts --exec /Users/dmaretskyi/Projects/protocols/packages/devtools/cli/bin/dev function dev -r ts-node/register
83
+ ```
84
+
85
+ ## Invoking functions
86
+
87
+
88
+ > NOTE: The port (7001) must match the one in config.
89
+
90
+ ```bash
91
+ curl --data '{ "foo": "bar" }' -H 'Content-Type: application/json' -i -X POST http://localhost:7001/dev/chess
92
+ ```
4
93
 
5
94
  ## Installation
6
95
 
@@ -9,28 +9,18 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
 
10
10
  // packages/core/functions/src/runtime/index.ts
11
11
  import express from "express";
12
- import { readdir } from "@dxos/node-std/fs/promises";
13
- import { extname, join } from "@dxos/node-std/path";
12
+ import { join } from "@dxos/node-std/path";
14
13
  import { getPortPromise } from "portfinder";
15
14
  import { log } from "@dxos/log";
16
- var FUNCTION_EXTENSIONS = [
17
- ".js",
18
- ".ts"
19
- ];
20
15
  var runFunctions = async (options) => {
21
- const files = await readdir(options.functionsDirectory);
22
16
  const functionHandlers = {};
23
- for (const file of files) {
24
- if (!FUNCTION_EXTENSIONS.some((ext) => extname(file) === ext)) {
25
- continue;
26
- }
17
+ for (const [functionName, _] of Object.entries(options.manifest.functions)) {
27
18
  try {
28
- const module = __require(join(options.functionsDirectory, file));
19
+ const module = __require(join(options.functionsDirectory, functionName));
29
20
  const handler = module.default;
30
21
  if (typeof handler !== "function") {
31
- throw new Error(`Function ${file} does not export a default function`);
22
+ throw new Error(`Function ${functionName} does not export a default function`);
32
23
  }
33
- const functionName = file.slice(0, -extname(file).length);
34
24
  functionHandlers[functionName] = handler;
35
25
  } catch (e) {
36
26
  console.error(e);
@@ -86,7 +76,7 @@ var runFunctions = async (options) => {
86
76
  registrationId
87
77
  }, {
88
78
  file: "index.ts",
89
- line: 93,
79
+ line: 84,
90
80
  scope: void 0,
91
81
  callSite: (f, a) => f(...a)
92
82
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/runtime/index.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { readdir } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { getPortPromise } from 'portfinder';\n\nimport { Client } from '@dxos/client';\nimport { log } from '@dxos/log';\n\nimport { FunctionContext, FunctionHandler, Reply } from '../interface';\n\nconst FUNCTION_EXTENSIONS = ['.js', '.ts'];\n\nexport type FunctionsRuntimeParams = {\n client: Client;\n functionsDirectory: string;\n};\n\nexport const runFunctions = async (options: FunctionsRuntimeParams) => {\n const files = await readdir(options.functionsDirectory);\n\n const functionHandlers: Record<string, FunctionHandler> = {};\n\n for (const file of files) {\n if (!FUNCTION_EXTENSIONS.some((ext) => extname(file) === ext)) {\n continue;\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(join(options.functionsDirectory, file));\n const handler = module.default;\n if (typeof handler !== 'function') {\n throw new Error(`Function ${file} does not export a default function`);\n }\n\n const functionName = file.slice(0, -extname(file).length);\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,eAAe;AACxB,SAASC,SAASC,YAAY;AAC9B,SAASC,sBAAsB;AAG/B,SAASC,WAAW;AAIpB,IAAMC,sBAAsB;EAAC;EAAO;;AAO7B,IAAMC,eAAe,OAAOC,YAAoC;AACrE,QAAMC,QAAQ,MAAMC,QAAQF,QAAQG,kBAAkB;AAEtD,QAAMC,mBAAoD,CAAC;AAE3D,aAAWC,QAAQJ,OAAO;AACxB,QAAI,CAACH,oBAAoBQ,KAAK,CAACC,QAAQC,QAAQH,IAAAA,MAAUE,GAAAA,GAAM;AAC7D;IACF;AAEA,QAAI;AAEF,YAAME,SAASC,UAAQC,KAAKX,QAAQG,oBAAoBE,IAAAA,CAAAA;AACxD,YAAMO,UAAUH,OAAOI;AACvB,UAAI,OAAOD,YAAY,YAAY;AACjC,cAAM,IAAIE,MAAM,YAAYT,yCAAyC;MACvE;AAEA,YAAMU,eAAeV,KAAKW,MAAM,GAAG,CAACR,QAAQH,IAAAA,EAAMY,MAAM;AAExDb,uBAAiBW,YAAAA,IAAgBH;IACnC,SAASM,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,UAAMf,eAAec,IAAIE,OAAOhB;AAEhC,UAAMiB,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,QAAQ1C,QAAQ0C;MAChBT,QAAQD,aAAaC,OAAOU,KAAKX,YAAAA;IACnC;AAEA,UAAM,YAAY;AAChB,UAAI;AACF,cAAM5B,iBAAiBW,YAAAA,EAAcc,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,gBAAgBC,OAAOC,KAAK9C,gBAAAA;AAClC,QAAM,EAAE+C,eAAc,IAAK,MAAMnD,QAAQ0C,OAAOU,SAASA,SAASC,wBAAyBC,SAAS;IAClGC,UAAU,oBAAoBlC;IAC9BmC,WAAWR,cAAcS,IAAI,CAACC,UAAU;MAAEA;IAAK,EAAA;EACjD,CAAA;AAEAC,UAAQC,GAAG,UAAU,YAAY;AAC/B,UAAM5D,QAAQ0C,OAAOU,SAASA,SAASC,wBAAyBQ,WAAW;MAAEV;IAAe,CAAA;AAC5FQ,YAAQG,KAAI;EACd,CAAA;AAEAC,MAAIC,KAAK,6BAA6B;IAAE3C;IAAM2B;IAAeG;EAAe,GAAA;;;;;;AAC9E;",
6
- "names": ["express", "readdir", "extname", "join", "getPortPromise", "log", "FUNCTION_EXTENSIONS", "runFunctions", "options", "files", "readdir", "functionsDirectory", "functionHandlers", "file", "some", "ext", "extname", "module", "require", "join", "handler", "default", "Error", "functionName", "slice", "length", "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", "Object", "keys", "registrationId", "services", "FunctionRegistryService", "register", "endpoint", "functions", "map", "name", "process", "on", "unregister", "exit", "log", "info"]
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"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/core/functions/src/interface.ts":{"bytes":756,"imports":[]},"packages/core/functions/src/runtime/index.ts":{"bytes":9991,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"@dxos/node-std/fs/promises","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":5283},"packages/core/functions/dist/lib/browser/index.mjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"@dxos/node-std/fs/promises","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":2358}},"bytes":2874}}}
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}}}
@@ -36,28 +36,18 @@ module.exports = __toCommonJS(src_exports);
36
36
 
37
37
  // packages/core/functions/src/runtime/index.ts
38
38
  var import_express = __toESM(require("express"));
39
- var import_promises = require("node:fs/promises");
40
39
  var import_node_path = require("node:path");
41
40
  var import_portfinder = require("portfinder");
42
41
  var import_log = require("@dxos/log");
43
- var FUNCTION_EXTENSIONS = [
44
- ".js",
45
- ".ts"
46
- ];
47
42
  var runFunctions = async (options) => {
48
- const files = await (0, import_promises.readdir)(options.functionsDirectory);
49
43
  const functionHandlers = {};
50
- for (const file of files) {
51
- if (!FUNCTION_EXTENSIONS.some((ext) => (0, import_node_path.extname)(file) === ext)) {
52
- continue;
53
- }
44
+ for (const [functionName, _] of Object.entries(options.manifest.functions)) {
54
45
  try {
55
- const module2 = require((0, import_node_path.join)(options.functionsDirectory, file));
46
+ const module2 = require((0, import_node_path.join)(options.functionsDirectory, functionName));
56
47
  const handler = module2.default;
57
48
  if (typeof handler !== "function") {
58
- throw new Error(`Function ${file} does not export a default function`);
49
+ throw new Error(`Function ${functionName} does not export a default function`);
59
50
  }
60
- const functionName = file.slice(0, -(0, import_node_path.extname)(file).length);
61
51
  functionHandlers[functionName] = handler;
62
52
  } catch (e) {
63
53
  console.error(e);
@@ -113,7 +103,7 @@ var runFunctions = async (options) => {
113
103
  registrationId
114
104
  }, {
115
105
  file: "index.ts",
116
- line: 93,
106
+ line: 84,
117
107
  scope: void 0,
118
108
  callSite: (f, a) => f(...a)
119
109
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts", "../../../src/runtime/index.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nexport * from './interface';\nexport * from './runtime';\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { readdir } from 'node:fs/promises';\nimport { extname, join } from 'node:path';\nimport { getPortPromise } from 'portfinder';\n\nimport { Client } from '@dxos/client';\nimport { log } from '@dxos/log';\n\nimport { FunctionContext, FunctionHandler, Reply } from '../interface';\n\nconst FUNCTION_EXTENSIONS = ['.js', '.ts'];\n\nexport type FunctionsRuntimeParams = {\n client: Client;\n functionsDirectory: string;\n};\n\nexport const runFunctions = async (options: FunctionsRuntimeParams) => {\n const files = await readdir(options.functionsDirectory);\n\n const functionHandlers: Record<string, FunctionHandler> = {};\n\n for (const file of files) {\n if (!FUNCTION_EXTENSIONS.some((ext) => extname(file) === ext)) {\n continue;\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(join(options.functionsDirectory, file));\n const handler = module.default;\n if (typeof handler !== 'function') {\n throw new Error(`Function ${file} does not export a default function`);\n }\n\n const functionName = file.slice(0, -extname(file).length);\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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACIA,qBAAoB;AACpB,sBAAwB;AACxB,uBAA8B;AAC9B,wBAA+B;AAG/B,iBAAoB;AAIpB,IAAMA,sBAAsB;EAAC;EAAO;;AAO7B,IAAMC,eAAe,OAAOC,YAAoC;AACrE,QAAMC,QAAQ,UAAMC,yBAAQF,QAAQG,kBAAkB;AAEtD,QAAMC,mBAAoD,CAAC;AAE3D,aAAWC,QAAQJ,OAAO;AACxB,QAAI,CAACH,oBAAoBQ,KAAK,CAACC,YAAQC,0BAAQH,IAAAA,MAAUE,GAAAA,GAAM;AAC7D;IACF;AAEA,QAAI;AAEF,YAAME,UAASC,YAAQC,uBAAKX,QAAQG,oBAAoBE,IAAAA,CAAAA;AACxD,YAAMO,UAAUH,QAAOI;AACvB,UAAI,OAAOD,YAAY,YAAY;AACjC,cAAM,IAAIE,MAAM,YAAYT,yCAAyC;MACvE;AAEA,YAAMU,eAAeV,KAAKW,MAAM,GAAG,KAACR,0BAAQH,IAAAA,EAAMY,MAAM;AAExDb,uBAAiBW,YAAAA,IAAgBH;IACnC,SAASM,GAAP;AACAC,cAAQC,MAAMF,CAAAA;IAChB;EACF;AAEA,QAAMG,OAAO,UAAMC,kCAAe;IAAEC,WAAW;EAAK,CAAA;AAEpD,QAAMC,UAAMC,eAAAA,SAAAA;AACZD,MAAIE,IAAID,eAAAA,QAAQE,KAAI,CAAA;AAEpBH,MAAII,KAAK,kBAAkB,OAAOC,KAAKC,QAAQ;AAC7C,UAAMf,eAAec,IAAIE,OAAOhB;AAEhC,UAAMiB,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,QAAQ1C,QAAQ0C;MAChBT,QAAQD,aAAaC,OAAOU,KAAKX,YAAAA;IACnC;AAEA,UAAM,YAAY;AAChB,UAAI;AACF,cAAM5B,iBAAiBW,YAAAA,EAAcc,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,gBAAgBC,OAAOC,KAAK9C,gBAAAA;AAClC,QAAM,EAAE+C,eAAc,IAAK,MAAMnD,QAAQ0C,OAAOU,SAASA,SAASC,wBAAyBC,SAAS;IAClGC,UAAU,oBAAoBlC;IAC9BmC,WAAWR,cAAcS,IAAI,CAACC,UAAU;MAAEA;IAAK,EAAA;EACjD,CAAA;AAEAC,UAAQC,GAAG,UAAU,YAAY;AAC/B,UAAM5D,QAAQ0C,OAAOU,SAASA,SAASC,wBAAyBQ,WAAW;MAAEV;IAAe,CAAA;AAC5FQ,YAAQG,KAAI;EACd,CAAA;AAEAC,iBAAIC,KAAK,6BAA6B;IAAE3C;IAAM2B;IAAeG;EAAe,GAAA;;;;;;AAC9E;",
6
- "names": ["FUNCTION_EXTENSIONS", "runFunctions", "options", "files", "readdir", "functionsDirectory", "functionHandlers", "file", "some", "ext", "extname", "module", "require", "join", "handler", "default", "Error", "functionName", "slice", "length", "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", "Object", "keys", "registrationId", "services", "FunctionRegistryService", "register", "endpoint", "functions", "map", "name", "process", "on", "unregister", "exit", "log", "info"]
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nexport * from './interface';\nexport * from './runtime';\n", "//\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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACIA,qBAAoB;AACpB,uBAAqB;AACrB,wBAA+B;AAG/B,iBAAoB;AAWb,IAAMA,eAAe,OAAOC,YAAoC;AACrE,QAAMC,mBAAoD,CAAC;AAE3D,aAAW,CAACC,cAAcC,CAAAA,KAAMC,OAAOC,QAAQL,QAAQM,SAASC,SAAS,GAAG;AAC1E,QAAI;AAEF,YAAMC,UAASC,YAAQC,uBAAKV,QAAQW,oBAAoBT,YAAAA,CAAAA;AACxD,YAAMU,UAAUJ,QAAOK;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,UAAMC,kCAAe;IAAEC,WAAW;EAAK,CAAA;AAEpD,QAAMC,UAAMC,eAAAA,SAAAA;AACZD,MAAIE,IAAID,eAAAA,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,iBAAIC,KAAK,6BAA6B;IAAEzC;IAAM2B;IAAeE;EAAe,GAAA;;;;;;AAC9E;",
6
+ "names": ["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"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/core/functions/src/interface.ts":{"bytes":756,"imports":[]},"packages/core/functions/src/runtime/index.ts":{"bytes":9991,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"node:fs/promises","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"portfinder","kind":"import-statement","external":true},{"path":"@dxos/log","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/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":5363},"packages/core/functions/dist/lib/node/index.cjs":{"imports":[{"path":"express","kind":"require-call","external":true},{"path":"node:fs/promises","kind":"require-call","external":true},{"path":"node:path","kind":"require-call","external":true},{"path":"portfinder","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":129},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":2530}},"bytes":4286}}}
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":"node:path","kind":"import-statement","external":true},{"path":"portfinder","kind":"import-statement","external":true},{"path":"@dxos/log","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/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":4936},"packages/core/functions/dist/lib/node/index.cjs":{"imports":[{"path":"express","kind":"require-call","external":true},{"path":"node:path","kind":"require-call","external":true},{"path":"portfinder","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":129},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":2217}},"bytes":3973}}}
@@ -0,0 +1,19 @@
1
+ export type FunctionsManifest = {
2
+ functions: Record<string, FunctionConfig>;
3
+ };
4
+ export type FunctionConfig = {
5
+ description?: string;
6
+ };
7
+ export type FunctionTriggers = {
8
+ function: string;
9
+ triggers: FunctionTrigger[];
10
+ };
11
+ export type FunctionTrigger = {
12
+ type: string;
13
+ spaceKey: string;
14
+ subscription: {
15
+ props?: Record<string, any>;
16
+ nested?: string[];
17
+ };
18
+ };
19
+ //# sourceMappingURL=defintions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defintions.d.ts","sourceRoot":"","sources":["../../../src/defintions.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACH,CAAC"}
@@ -1,7 +1,9 @@
1
1
  import { Client } from '@dxos/client';
2
+ import { FunctionsManifest } from '../defintions';
2
3
  export type FunctionsRuntimeParams = {
3
4
  client: Client;
4
5
  functionsDirectory: string;
6
+ manifest: FunctionsManifest;
5
7
  };
6
8
  export declare const runFunctions: (options: FunctionsRuntimeParams) => Promise<void>;
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/runtime/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,YAAY,YAAmB,sBAAsB,kBAwEjE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/runtime/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,YAAY,YAAmB,sBAAsB,kBAgEjE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.1.52",
3
+ "version": "0.1.53-main.3ecc504",
4
4
  "description": "Functions SDK.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -21,9 +21,9 @@
21
21
  "dependencies": {
22
22
  "express": "4.17.1",
23
23
  "portfinder": "^1.0.32",
24
- "@dxos/client": "0.1.52",
25
- "@dxos/log": "0.1.52",
26
- "@dxos/node-std": "0.1.52"
24
+ "@dxos/client": "0.1.53-main.3ecc504",
25
+ "@dxos/log": "0.1.53-main.3ecc504",
26
+ "@dxos/node-std": "0.1.53-main.3ecc504"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/express": "^4.17.17"
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export type FunctionsManifest = {
6
+ functions: Record<string, FunctionConfig>;
7
+ };
8
+
9
+ export type FunctionConfig = {
10
+ description?: string;
11
+ };
12
+
13
+ export type FunctionTriggers = {
14
+ function: string;
15
+ triggers: FunctionTrigger[];
16
+ };
17
+
18
+ export type FunctionTrigger = {
19
+ type: string;
20
+ spaceKey: string;
21
+ subscription: {
22
+ props?: Record<string, any>;
23
+ nested?: string[];
24
+ };
25
+ };
@@ -3,42 +3,33 @@
3
3
  //
4
4
 
5
5
  import express from 'express';
6
- import { readdir } from 'node:fs/promises';
7
- import { extname, join } from 'node:path';
6
+ import { join } from 'node:path';
8
7
  import { getPortPromise } from 'portfinder';
9
8
 
10
9
  import { Client } from '@dxos/client';
11
10
  import { log } from '@dxos/log';
12
11
 
12
+ import { FunctionsManifest } from '../defintions';
13
13
  import { FunctionContext, FunctionHandler, Reply } from '../interface';
14
14
 
15
- const FUNCTION_EXTENSIONS = ['.js', '.ts'];
16
-
17
15
  export type FunctionsRuntimeParams = {
18
16
  client: Client;
19
17
  functionsDirectory: string;
18
+ manifest: FunctionsManifest;
20
19
  };
21
20
 
22
21
  export const runFunctions = async (options: FunctionsRuntimeParams) => {
23
- const files = await readdir(options.functionsDirectory);
24
-
25
22
  const functionHandlers: Record<string, FunctionHandler> = {};
26
23
 
27
- for (const file of files) {
28
- if (!FUNCTION_EXTENSIONS.some((ext) => extname(file) === ext)) {
29
- continue;
30
- }
31
-
24
+ for (const [functionName, _] of Object.entries(options.manifest.functions)) {
32
25
  try {
33
26
  // eslint-disable-next-line @typescript-eslint/no-var-requires
34
- const module = require(join(options.functionsDirectory, file));
27
+ const module = require(join(options.functionsDirectory, functionName));
35
28
  const handler = module.default;
36
29
  if (typeof handler !== 'function') {
37
- throw new Error(`Function ${file} does not export a default function`);
30
+ throw new Error(`Function ${functionName} does not export a default function`);
38
31
  }
39
32
 
40
- const functionName = file.slice(0, -extname(file).length);
41
-
42
33
  functionHandlers[functionName] = handler;
43
34
  } catch (e) {
44
35
  console.error(e);