@dxos/functions 0.3.7 → 0.3.8-main.0ab6c73

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.
@@ -1 +1 @@
1
- {"inputs":{"packages/core/functions/src/function.ts":{"bytes":1777,"imports":[],"format":"esm"},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":14040,"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/async","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/trigger-manager.ts":{"bytes":16473,"imports":[{"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/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/index.ts":{"bytes":579,"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"}],"format":"esm"},"packages/core/functions/src/index.ts":{"bytes":545,"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"}],"format":"esm"}},"outputs":{"packages/core/functions/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":14580},"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/async","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true},{"path":"@dxos/async","kind":"require-call","external":true},{"path":"@dxos/context","kind":"require-call","external":true},{"path":"@dxos/echo-schema","kind":"require-call","external":true},{"path":"@dxos/invariant","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true},{"path":"@dxos/util","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":163},"packages/core/functions/src/runtime/dev-server.ts":{"bytesInOutput":3394},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/trigger-manager.ts":{"bytesInOutput":4520}},"bytes":9783}}}
1
+ {"inputs":{"packages/core/functions/src/handler.ts":{"bytes":1313,"imports":[],"format":"esm"},"packages/core/functions/src/manifest.ts":{"bytes":1735,"imports":[],"format":"esm"},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":18553,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/scheduler.ts":{"bytes":16641,"imports":[{"path":"cron","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/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/index.ts":{"bytes":566,"imports":[{"path":"packages/core/functions/src/runtime/dev-server.ts","kind":"import-statement","original":"./dev-server"},{"path":"packages/core/functions/src/runtime/scheduler.ts","kind":"import-statement","original":"./scheduler"}],"format":"esm"},"packages/core/functions/src/index.ts":{"bytes":637,"imports":[{"path":"packages/core/functions/src/handler.ts","kind":"import-statement","original":"./handler"},{"path":"packages/core/functions/src/manifest.ts","kind":"import-statement","original":"./manifest"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}],"format":"esm"}},"outputs":{"packages/core/functions/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16608},"packages/core/functions/dist/lib/node/index.cjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"cron","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/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["DevServer","Scheduler"],"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":4729},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/scheduler.ts":{"bytesInOutput":4189}},"bytes":9466}}}
@@ -0,0 +1,17 @@
1
+ import { type Client } from '@dxos/client';
2
+ export interface Response {
3
+ status(code: number): Response;
4
+ }
5
+ export interface FunctionContext {
6
+ client: Client;
7
+ }
8
+ export type FunctionHandler<T extends {}> = (params: {
9
+ event: T;
10
+ context: FunctionContext;
11
+ response: Response;
12
+ }) => Promise<Response | void>;
13
+ export type FunctionSubscriptionEvent = {
14
+ space: string;
15
+ objects: string[];
16
+ };
17
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;CAChC;AAGD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE;IACnD,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC;CACpB,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AAE/B,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC"}
@@ -1,3 +1,4 @@
1
- export * from './function';
1
+ export * from './handler';
2
+ export * from './manifest';
2
3
  export * from './runtime';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,25 @@
1
+ export type FunctionDef = {
2
+ id: string;
3
+ name: string;
4
+ handler: string;
5
+ description?: string;
6
+ };
7
+ export type TriggerSubscription = {
8
+ type: string;
9
+ spaceKey: string;
10
+ props?: Record<string, any>;
11
+ nested?: string[];
12
+ };
13
+ export type FunctionTrigger = {
14
+ function: string;
15
+ schedule?: string;
16
+ subscription?: TriggerSubscription;
17
+ };
18
+ /**
19
+ * Function manifest file.
20
+ */
21
+ export type FunctionManifest = {
22
+ functions: FunctionDef[];
23
+ triggers: FunctionTrigger[];
24
+ };
25
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/manifest.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IAExB,EAAE,EAAE,MAAM,CAAC;IAEX,IAAI,EAAE,MAAM,CAAC;IAEb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAKF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC"}
@@ -1,8 +1,11 @@
1
1
  import { type Client } from '@dxos/client';
2
- import { type FunctionsManifest } from '../function';
2
+ import { type FunctionHandler } from '../handler';
3
+ import { type FunctionDef, type FunctionManifest } from '../manifest';
3
4
  export type DevServerOptions = {
5
+ port?: number;
4
6
  directory: string;
5
- manifest: FunctionsManifest;
7
+ manifest: FunctionManifest;
8
+ reload?: boolean;
6
9
  };
7
10
  /**
8
11
  * Functions dev server provides a local HTTP server for testing functions.
@@ -14,12 +17,25 @@ export declare class DevServer {
14
17
  private _server?;
15
18
  private _port?;
16
19
  private _registrationId?;
20
+ private _proxy?;
21
+ private _seq;
17
22
  constructor(_client: Client, _options: DevServerOptions);
18
- get port(): number | undefined;
19
- get endpoint(): string | undefined;
20
- get functions(): string[];
23
+ get endpoint(): string;
24
+ get proxy(): string | undefined;
25
+ get functions(): {
26
+ def: FunctionDef;
27
+ handler: FunctionHandler<any>;
28
+ }[];
21
29
  initialize(): Promise<void>;
22
30
  start(): Promise<void>;
23
31
  stop(): Promise<void>;
32
+ /**
33
+ * Load function.
34
+ */
35
+ private _load;
36
+ /**
37
+ * Invoke function handler.
38
+ */
39
+ private _invoke;
24
40
  }
25
41
  //# sourceMappingURL=dev-server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../../src/runtime/dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EAA8C,KAAK,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AAIhH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IASlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAT3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4C;IAEtE,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,eAAe,CAAC,CAAS;gBAId,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB;IAG7C,IAAI,IAAI,uBAEP;IAED,IAAI,QAAQ,uBAEX;IAED,IAAI,SAAS,aAEZ;IAEK,UAAU;IAiBV,KAAK;IAoDL,IAAI;CAiBX"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../../src/runtime/dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C,OAAO,EAAwB,KAAK,eAAe,EAAiB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAYlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAErG,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;gBAIE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB;IAG7C,IAAI,QAAQ,WAGX;IAED,IAAI,KAAK,uBAER;IAED,IAAI,SAAS;;;QAEZ;IAEK,UAAU;IAUV,KAAK;IAwCL,IAAI;IAqBV;;OAEG;YACW,KAAK;IAqBnB;;OAEG;YACW,OAAO;CAwBtB"}
@@ -1,3 +1,3 @@
1
1
  export * from './dev-server';
2
- export * from './trigger-manager';
2
+ export * from './scheduler';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/runtime/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/runtime/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type Client } from '@dxos/client';
2
+ import { type FunctionManifest } from '../manifest';
3
+ type SchedulerOptions = {
4
+ endpoint: string;
5
+ };
6
+ /**
7
+ * Functions scheduler.
8
+ */
9
+ export declare class Scheduler {
10
+ private readonly _client;
11
+ private readonly _manifest;
12
+ private readonly _options;
13
+ private readonly _mounts;
14
+ constructor(_client: Client, _manifest: FunctionManifest, _options: SchedulerOptions);
15
+ start(): Promise<void>;
16
+ stop(): Promise<void>;
17
+ private mount;
18
+ private unmount;
19
+ private execFunction;
20
+ }
21
+ export {};
22
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../../src/runtime/scheduler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,MAAM,EAAkB,MAAM,cAAc,CAAC;AAQ3D,OAAO,EAAoB,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAE5F,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AAEH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAG6B;gBAGlC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,gBAAgB;IAGvC,KAAK;IAWL,IAAI;YAMI,KAAK;YA+DL,OAAO;YASP,YAAY;CAiB3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.3.7",
3
+ "version": "0.3.8-main.0ab6c73",
4
4
  "description": "Functions SDK and runtime.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -19,16 +19,17 @@
19
19
  "src"
20
20
  ],
21
21
  "dependencies": {
22
+ "cron": "^3.1.6",
22
23
  "express": "^4.17.1",
23
- "portfinder": "^1.0.32",
24
- "@dxos/async": "0.3.7",
25
- "@dxos/client": "0.3.7",
26
- "@dxos/context": "0.3.7",
27
- "@dxos/echo-schema": "0.3.7",
28
- "@dxos/invariant": "0.3.7",
29
- "@dxos/log": "0.3.7",
30
- "@dxos/node-std": "0.3.7",
31
- "@dxos/util": "0.3.7"
24
+ "get-port-please": "^3.1.1",
25
+ "@dxos/async": "0.3.8-main.0ab6c73",
26
+ "@dxos/context": "0.3.8-main.0ab6c73",
27
+ "@dxos/echo-schema": "0.3.8-main.0ab6c73",
28
+ "@dxos/client": "0.3.8-main.0ab6c73",
29
+ "@dxos/invariant": "0.3.8-main.0ab6c73",
30
+ "@dxos/node-std": "0.3.8-main.0ab6c73",
31
+ "@dxos/log": "0.3.8-main.0ab6c73",
32
+ "@dxos/util": "0.3.8-main.0ab6c73"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@types/express": "^4.17.17"
package/src/handler.ts ADDED
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type Client } from '@dxos/client';
6
+
7
+ // TODO(burdon): No response?
8
+ export interface Response {
9
+ status(code: number): Response;
10
+ }
11
+
12
+ // TODO(burdon): Limit access to individual space?
13
+ export interface FunctionContext {
14
+ client: Client;
15
+ }
16
+
17
+ // TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
18
+ // https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
19
+ export type FunctionHandler<T extends {}> = (params: {
20
+ event: T;
21
+ context: FunctionContext;
22
+ response: Response;
23
+ }) => Promise<Response | void>;
24
+
25
+ export type FunctionSubscriptionEvent = {
26
+ space: string; // TODO(burdon): Convert to PublicKey.
27
+ objects: string[];
28
+ };
package/src/index.ts CHANGED
@@ -2,5 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './function';
5
+ export * from './handler';
6
+ export * from './manifest';
6
7
  export * from './runtime';
@@ -0,0 +1,40 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ // Lambda-like function definitions.
6
+ // See: https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml/#functions
7
+
8
+ export type FunctionDef = {
9
+ // FQ function name.
10
+ id: string;
11
+ // URL path.
12
+ name: string;
13
+ // File path of handler.
14
+ handler: string;
15
+ description?: string;
16
+ };
17
+
18
+ export type TriggerSubscription = {
19
+ type: string;
20
+ spaceKey: string;
21
+ props?: Record<string, any>;
22
+ nested?: string[];
23
+ };
24
+
25
+ // TODO(burdon): Generalize binding.
26
+ // https://www.npmjs.com/package/aws-lambda
27
+ // https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
28
+ export type FunctionTrigger = {
29
+ function: string;
30
+ schedule?: string;
31
+ subscription?: TriggerSubscription;
32
+ };
33
+
34
+ /**
35
+ * Function manifest file.
36
+ */
37
+ export type FunctionManifest = {
38
+ functions: FunctionDef[];
39
+ triggers: FunctionTrigger[];
40
+ };
@@ -3,32 +3,37 @@
3
3
  //
4
4
 
5
5
  import express from 'express';
6
+ import { getPort } from 'get-port-please';
6
7
  import type http from 'http';
7
8
  import { join } from 'node:path';
8
- import { getPortPromise } from 'portfinder';
9
9
 
10
10
  import { Trigger } from '@dxos/async';
11
11
  import { type Client } from '@dxos/client';
12
+ import { invariant } from '@dxos/invariant';
12
13
  import { log } from '@dxos/log';
13
14
 
14
- import { type FunctionContext, type FunctionHandler, type FunctionsManifest, type Response } from '../function';
15
-
16
- const DEFAULT_PORT = 7000;
15
+ import { type FunctionContext, type FunctionHandler, type Response } from '../handler';
16
+ import { type FunctionDef, type FunctionManifest } from '../manifest';
17
17
 
18
18
  export type DevServerOptions = {
19
+ port?: number;
19
20
  directory: string;
20
- manifest: FunctionsManifest;
21
+ manifest: FunctionManifest;
22
+ reload?: boolean;
21
23
  };
22
24
 
23
25
  /**
24
26
  * Functions dev server provides a local HTTP server for testing functions.
25
27
  */
26
28
  export class DevServer {
27
- private readonly _handlers: Record<string, FunctionHandler<any>> = {};
29
+ // Function handlers indexed by name (URL path).
30
+ private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};
28
31
 
29
32
  private _server?: http.Server;
30
33
  private _port?: number;
31
34
  private _registrationId?: string;
35
+ private _proxy?: string;
36
+ private _seq = 0;
32
37
 
33
38
  // prettier-ignore
34
39
  constructor(
@@ -36,31 +41,25 @@ export class DevServer {
36
41
  private readonly _options: DevServerOptions,
37
42
  ) {}
38
43
 
39
- get port() {
40
- return this._port;
44
+ get endpoint() {
45
+ invariant(this._port);
46
+ return `http://localhost:${this._port}`;
41
47
  }
42
48
 
43
- get endpoint() {
44
- return this._port ? `http://localhost:${this._port}` : undefined;
49
+ get proxy() {
50
+ return this._proxy;
45
51
  }
46
52
 
47
53
  get functions() {
48
- return Object.keys(this._handlers);
54
+ return Object.values(this._handlers);
49
55
  }
50
56
 
51
57
  async initialize() {
52
- for (const { id } of this._options.manifest.functions) {
58
+ for (const def of this._options.manifest.functions) {
53
59
  try {
54
- // eslint-disable-next-line @typescript-eslint/no-var-requires
55
- const module = require(join(this._options.directory, id));
56
- const handler = module.default;
57
- if (typeof handler !== 'function') {
58
- throw new Error(`Handler must export default function: ${id}`);
59
- }
60
-
61
- this._handlers[id] = handler;
60
+ await this._load(def);
62
61
  } catch (err) {
63
- log.error('parsing function (check functions.yml manifest)', err);
62
+ log.error('parsing function (check manifest)', err);
64
63
  }
65
64
  }
66
65
  }
@@ -69,51 +68,39 @@ export class DevServer {
69
68
  const app = express();
70
69
  app.use(express.json());
71
70
 
72
- app.post('/:functionName', async (req, res) => {
73
- const functionName = req.params.functionName;
74
- log('invoke', { function: functionName, data: req.body });
75
-
76
- const builder: Response = {
77
- status: (code: number) => {
78
- res.statusCode = code;
79
- return builder;
80
- },
81
- succeed: (result = {}) => {
82
- res.end(JSON.stringify(result));
83
- return builder;
84
- },
85
- };
86
-
87
- const context: FunctionContext = {
88
- client: this._client,
89
- status: builder.status.bind(builder),
90
- };
91
-
92
- void (async () => {
93
- try {
94
- // TODO(burdon): Typed event handler.
95
- await this._handlers[functionName]({ event: req.body, context });
96
- } catch (err: any) {
97
- res.statusCode = 500;
98
- res.end(err.message);
71
+ app.post('/:name', async (req, res) => {
72
+ const { name } = req.params;
73
+ try {
74
+ if (this._options.reload) {
75
+ const { def } = this._handlers[name];
76
+ await this._load(def, true);
99
77
  }
100
- })();
78
+
79
+ res.statusCode = await this._invoke(name, req.body);
80
+ res.end();
81
+ } catch (err: any) {
82
+ log.error(err);
83
+ res.statusCode = 500;
84
+ res.end();
85
+ }
101
86
  });
102
87
 
103
- this._port = await getPortPromise({ startPort: DEFAULT_PORT });
88
+ this._port = await getPort({ port: 7200, portRange: [7200, 7299] });
104
89
  this._server = app.listen(this._port);
105
90
 
106
- // TODO(burdon): Check plugin is registered.
107
- // TypeError: Cannot read properties of undefined (reading 'register')
108
91
  try {
109
- const { registrationId } = await this._client.services.services.FunctionRegistryService!.register({
110
- endpoint: this.endpoint!,
111
- functions: this.functions.map((name) => ({ name })),
92
+ // Register functions.
93
+ const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
94
+ endpoint: this.endpoint,
95
+ functions: this.functions.map(({ def: { name } }) => ({ name })),
112
96
  });
97
+
98
+ log.info('registered', { registrationId, endpoint });
113
99
  this._registrationId = registrationId;
100
+ this._proxy = endpoint;
114
101
  } catch (err: any) {
115
102
  await this.stop();
116
- throw new Error('FunctionRegistryService not available; check config (agent.plugins.functions).');
103
+ throw new Error('FunctionRegistryService not available (check plugin is configured).');
117
104
  }
118
105
  }
119
106
 
@@ -124,14 +111,69 @@ export class DevServer {
124
111
  await this._client.services.services.FunctionRegistryService!.unregister({
125
112
  registrationId: this._registrationId,
126
113
  });
114
+
115
+ log.info('unregistered', { registrationId: this._registrationId });
127
116
  this._registrationId = undefined;
117
+ this._proxy = undefined;
128
118
  }
129
119
 
130
120
  trigger.wake();
131
121
  });
132
122
 
133
123
  await trigger.wait();
134
- this._server = undefined;
135
124
  this._port = undefined;
125
+ this._server = undefined;
126
+ }
127
+
128
+ /**
129
+ * Load function.
130
+ */
131
+ private async _load(def: FunctionDef, flush = false) {
132
+ const { id, name, handler } = def;
133
+ const path = join(this._options.directory, handler);
134
+ log.info('loading', { id });
135
+
136
+ // Remove from cache.
137
+ if (flush) {
138
+ Object.keys(require.cache)
139
+ .filter((key) => key.startsWith(path))
140
+ .forEach((key) => delete require.cache[key]);
141
+ }
142
+
143
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
144
+ const module = require(path);
145
+ if (typeof module.default !== 'function') {
146
+ throw new Error(`Handler must export default function: ${id}`);
147
+ }
148
+
149
+ this._handlers[name] = { def, handler: module.default };
150
+ }
151
+
152
+ /**
153
+ * Invoke function handler.
154
+ */
155
+ private async _invoke(name: string, event: any) {
156
+ const seq = ++this._seq;
157
+ const now = Date.now();
158
+
159
+ log.info('req', { seq, name });
160
+ const { handler } = this._handlers[name];
161
+
162
+ const context: FunctionContext = {
163
+ client: this._client,
164
+ };
165
+
166
+ let statusCode = 200;
167
+ const response: Response = {
168
+ status: (code: number) => {
169
+ statusCode = code;
170
+ return response;
171
+ },
172
+ };
173
+
174
+ await handler({ context, event, response });
175
+ log.info('res', { seq, name, statusCode, duration: Date.now() - now });
176
+
177
+ return statusCode;
136
178
  }
137
179
  }
@@ -3,4 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './dev-server';
6
- export * from './trigger-manager';
6
+ export * from './scheduler';