@dxos/functions 0.3.7 → 0.3.8-main.0ae9c21
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 +2 -2
- package/dist/lib/browser/index.mjs +112 -61
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +127 -75
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/{function.d.ts → handler.d.ts} +8 -23
- package/dist/types/src/handler.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/manifest.d.ts +24 -0
- package/dist/types/src/manifest.d.ts.map +1 -0
- package/dist/types/src/runtime/dev-server.d.ts +11 -5
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/runtime/trigger-manager.d.ts +4 -5
- package/dist/types/src/runtime/trigger-manager.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/handler.ts +29 -0
- package/src/index.ts +2 -1
- package/src/manifest.ts +36 -0
- package/src/runtime/dev-server.ts +50 -32
- package/src/runtime/trigger-manager.ts +9 -10
- package/dist/types/src/function.d.ts.map +0 -1
- package/src/function.ts +0 -51
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/functions",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8-main.0ae9c21",
|
|
4
4
|
"description": "Functions SDK and runtime.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"express": "^4.17.1",
|
|
23
|
-
"
|
|
24
|
-
"@dxos/async": "0.3.
|
|
25
|
-
"@dxos/
|
|
26
|
-
"@dxos/
|
|
27
|
-
"@dxos/echo-schema": "0.3.
|
|
28
|
-
"@dxos/
|
|
29
|
-
"@dxos/
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
23
|
+
"get-port-please": "^3.1.1",
|
|
24
|
+
"@dxos/async": "0.3.8-main.0ae9c21",
|
|
25
|
+
"@dxos/context": "0.3.8-main.0ae9c21",
|
|
26
|
+
"@dxos/invariant": "0.3.8-main.0ae9c21",
|
|
27
|
+
"@dxos/echo-schema": "0.3.8-main.0ae9c21",
|
|
28
|
+
"@dxos/log": "0.3.8-main.0ae9c21",
|
|
29
|
+
"@dxos/node-std": "0.3.8-main.0ae9c21",
|
|
30
|
+
"@dxos/util": "0.3.8-main.0ae9c21",
|
|
31
|
+
"@dxos/client": "0.3.8-main.0ae9c21"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/express": "^4.17.17"
|
package/src/handler.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Client } from '@dxos/client';
|
|
6
|
+
|
|
7
|
+
export interface Response {
|
|
8
|
+
status(code: number): Response;
|
|
9
|
+
succeed(data?: object): Response;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FunctionContext {
|
|
13
|
+
client: Client;
|
|
14
|
+
status(code: number): Response;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function handler.
|
|
19
|
+
*/
|
|
20
|
+
export type FunctionHandler<T extends {}> = (params: {
|
|
21
|
+
context: FunctionContext;
|
|
22
|
+
event: T;
|
|
23
|
+
}) => Promise<Response | void>;
|
|
24
|
+
|
|
25
|
+
// TODO(burdon): Types.
|
|
26
|
+
export type FunctionSubscriptionEvent = {
|
|
27
|
+
space: string;
|
|
28
|
+
objects: string[];
|
|
29
|
+
};
|
package/src/index.ts
CHANGED
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export type FunctionDef = {
|
|
6
|
+
// FQ function name.
|
|
7
|
+
id: string;
|
|
8
|
+
// URL path.
|
|
9
|
+
path: string;
|
|
10
|
+
// Path of handler.
|
|
11
|
+
handler: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type TriggerSubscription = {
|
|
16
|
+
type: string;
|
|
17
|
+
spaceKey: string;
|
|
18
|
+
props?: Record<string, any>;
|
|
19
|
+
nested?: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// TODO(burdon): Generalize binding.
|
|
23
|
+
// https://www.npmjs.com/package/aws-lambda
|
|
24
|
+
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
25
|
+
export type FunctionTrigger = {
|
|
26
|
+
function: string;
|
|
27
|
+
subscription: TriggerSubscription;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function manifest file.
|
|
32
|
+
*/
|
|
33
|
+
export type FunctionManifest = {
|
|
34
|
+
functions: FunctionDef[];
|
|
35
|
+
triggers: FunctionTrigger[];
|
|
36
|
+
};
|
|
@@ -3,32 +3,35 @@
|
|
|
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
|
|
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:
|
|
21
|
+
manifest: FunctionManifest;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Functions dev server provides a local HTTP server for testing functions.
|
|
25
26
|
*/
|
|
27
|
+
// TODO(burdon): Reconcile with agent/functions dev dispatcher.
|
|
26
28
|
export class DevServer {
|
|
27
|
-
private readonly _handlers: Record<string, FunctionHandler<any
|
|
29
|
+
private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};
|
|
28
30
|
|
|
29
31
|
private _server?: http.Server;
|
|
30
32
|
private _port?: number;
|
|
31
33
|
private _registrationId?: string;
|
|
34
|
+
private _proxy?: string;
|
|
32
35
|
|
|
33
36
|
// prettier-ignore
|
|
34
37
|
constructor(
|
|
@@ -36,31 +39,37 @@ export class DevServer {
|
|
|
36
39
|
private readonly _options: DevServerOptions,
|
|
37
40
|
) {}
|
|
38
41
|
|
|
39
|
-
get
|
|
40
|
-
|
|
42
|
+
get endpoint() {
|
|
43
|
+
invariant(this._port);
|
|
44
|
+
return `http://localhost:${this._port}`;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
get
|
|
44
|
-
return this.
|
|
47
|
+
get proxy() {
|
|
48
|
+
return this._proxy;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
get functions() {
|
|
48
|
-
return Object.
|
|
52
|
+
return Object.values(this._handlers);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
async initialize() {
|
|
52
|
-
for (const
|
|
56
|
+
for (const def of this._options.manifest.functions) {
|
|
57
|
+
const { id, path, handler: dir } = def;
|
|
53
58
|
try {
|
|
54
59
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
55
|
-
const module = require(join(this._options.directory,
|
|
60
|
+
const module = require(join(this._options.directory, dir));
|
|
56
61
|
const handler = module.default;
|
|
57
62
|
if (typeof handler !== 'function') {
|
|
58
63
|
throw new Error(`Handler must export default function: ${id}`);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
this._handlers[
|
|
66
|
+
if (this._handlers[path]) {
|
|
67
|
+
log.warn(`Function already registered: ${id}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this._handlers[path] = { def, handler };
|
|
62
71
|
} catch (err) {
|
|
63
|
-
log.error('parsing function (check
|
|
72
|
+
log.error('parsing function (check manifest)', err);
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
}
|
|
@@ -69,30 +78,32 @@ export class DevServer {
|
|
|
69
78
|
const app = express();
|
|
70
79
|
app.use(express.json());
|
|
71
80
|
|
|
72
|
-
app.post('/:
|
|
73
|
-
const
|
|
74
|
-
log('invoke', { function: functionName, data: req.body });
|
|
81
|
+
app.post('/:name', async (req, res) => {
|
|
82
|
+
const { name } = req.params;
|
|
75
83
|
|
|
76
|
-
const
|
|
84
|
+
const response: Response = {
|
|
77
85
|
status: (code: number) => {
|
|
78
86
|
res.statusCode = code;
|
|
79
|
-
return
|
|
87
|
+
return response;
|
|
80
88
|
},
|
|
89
|
+
|
|
81
90
|
succeed: (result = {}) => {
|
|
82
91
|
res.end(JSON.stringify(result));
|
|
83
|
-
return
|
|
92
|
+
return response;
|
|
84
93
|
},
|
|
85
94
|
};
|
|
86
95
|
|
|
87
96
|
const context: FunctionContext = {
|
|
88
97
|
client: this._client,
|
|
89
|
-
status:
|
|
98
|
+
status: response.status.bind(response),
|
|
90
99
|
};
|
|
91
100
|
|
|
92
101
|
void (async () => {
|
|
93
102
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
log(`invoking: ${name}`);
|
|
104
|
+
const { handler } = this._handlers[name];
|
|
105
|
+
const response = await handler({ context, event: req.body });
|
|
106
|
+
log('done', { response });
|
|
96
107
|
} catch (err: any) {
|
|
97
108
|
res.statusCode = 500;
|
|
98
109
|
res.end(err.message);
|
|
@@ -100,20 +111,24 @@ export class DevServer {
|
|
|
100
111
|
})();
|
|
101
112
|
});
|
|
102
113
|
|
|
103
|
-
|
|
114
|
+
// TODO(burdon): Push down port management to agent.
|
|
115
|
+
this._port = await getPort({ port: 7200, portRange: [7200, 7299] });
|
|
104
116
|
this._server = app.listen(this._port);
|
|
105
117
|
|
|
106
|
-
// TODO(burdon):
|
|
107
|
-
// TypeError: Cannot read properties of undefined (reading 'register')
|
|
118
|
+
// TODO(burdon): Test during initialization.
|
|
108
119
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
// Register functions.
|
|
121
|
+
const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({
|
|
122
|
+
endpoint: this.endpoint,
|
|
123
|
+
functions: this.functions.map(({ def: { path } }) => ({ name: path })),
|
|
112
124
|
});
|
|
125
|
+
|
|
126
|
+
log.info('registered', { registrationId, endpoint });
|
|
113
127
|
this._registrationId = registrationId;
|
|
128
|
+
this._proxy = endpoint;
|
|
114
129
|
} catch (err: any) {
|
|
115
130
|
await this.stop();
|
|
116
|
-
throw new Error('FunctionRegistryService not available
|
|
131
|
+
throw new Error('FunctionRegistryService not available (check plugin is configured).');
|
|
117
132
|
}
|
|
118
133
|
}
|
|
119
134
|
|
|
@@ -124,14 +139,17 @@ export class DevServer {
|
|
|
124
139
|
await this._client.services.services.FunctionRegistryService!.unregister({
|
|
125
140
|
registrationId: this._registrationId,
|
|
126
141
|
});
|
|
142
|
+
|
|
143
|
+
log.info('unregistered', { registrationId: this._registrationId });
|
|
127
144
|
this._registrationId = undefined;
|
|
145
|
+
this._proxy = undefined;
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
trigger.wake();
|
|
131
149
|
});
|
|
132
150
|
|
|
133
151
|
await trigger.wait();
|
|
134
|
-
this._server = undefined;
|
|
135
152
|
this._port = undefined;
|
|
153
|
+
this._server = undefined;
|
|
136
154
|
}
|
|
137
155
|
}
|
|
@@ -11,12 +11,11 @@ import { invariant } from '@dxos/invariant';
|
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
12
|
import { ComplexMap } from '@dxos/util';
|
|
13
13
|
|
|
14
|
-
import { type FunctionTrigger } from '../
|
|
14
|
+
import { type FunctionManifest, type FunctionTrigger } from '../manifest';
|
|
15
15
|
|
|
16
16
|
// TODO(burdon): Rename.
|
|
17
17
|
export type InvokeOptions = {
|
|
18
18
|
endpoint: string;
|
|
19
|
-
runtime: string;
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
export class TriggerManager {
|
|
@@ -29,7 +28,7 @@ export class TriggerManager {
|
|
|
29
28
|
|
|
30
29
|
constructor(
|
|
31
30
|
private readonly _client: Client,
|
|
32
|
-
private readonly
|
|
31
|
+
private readonly _manifest: FunctionManifest,
|
|
33
32
|
private readonly _invokeOptions: InvokeOptions,
|
|
34
33
|
) {}
|
|
35
34
|
|
|
@@ -38,7 +37,7 @@ export class TriggerManager {
|
|
|
38
37
|
this._client.spaces.subscribe(async (spaces) => {
|
|
39
38
|
for (const space of spaces) {
|
|
40
39
|
await space.waitUntilReady();
|
|
41
|
-
for (const trigger of this.
|
|
40
|
+
for (const trigger of this._manifest.triggers ?? []) {
|
|
42
41
|
// TODO(burdon): New context? Shared?
|
|
43
42
|
await this.mount(new Context(), space, trigger);
|
|
44
43
|
}
|
|
@@ -54,6 +53,8 @@ export class TriggerManager {
|
|
|
54
53
|
|
|
55
54
|
private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {
|
|
56
55
|
const key = { name: trigger.function, spaceKey: space.key };
|
|
56
|
+
const config = this._manifest.functions.find((config) => config.id === trigger.function);
|
|
57
|
+
invariant(config, `Function not found: ${trigger.function}`);
|
|
57
58
|
const exists = this._mounts.get(key);
|
|
58
59
|
if (!exists) {
|
|
59
60
|
this._mounts.set(key, { ctx, trigger });
|
|
@@ -62,11 +63,10 @@ export class TriggerManager {
|
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
// TODO(burdon): Trigger binding.
|
|
66
66
|
// TODO(burdon): Why DeferredTask? How to pass objectIds to function?
|
|
67
67
|
const objectIds = new Set<string>();
|
|
68
68
|
const task = new DeferredTask(ctx, async () => {
|
|
69
|
-
await this.
|
|
69
|
+
await this.execFunction(this._invokeOptions, config.path, {
|
|
70
70
|
space: space.key,
|
|
71
71
|
objects: Array.from(objectIds),
|
|
72
72
|
});
|
|
@@ -115,14 +115,13 @@ export class TriggerManager {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
private async
|
|
119
|
-
const { endpoint
|
|
118
|
+
private async execFunction(options: InvokeOptions, functionName: string, data: any) {
|
|
119
|
+
const { endpoint } = options;
|
|
120
120
|
invariant(endpoint, 'Missing endpoint');
|
|
121
|
-
invariant(runtime, 'Missing runtime');
|
|
122
121
|
|
|
123
122
|
try {
|
|
124
123
|
log('invoke', { function: functionName });
|
|
125
|
-
const url = `${endpoint}/${
|
|
124
|
+
const url = `${endpoint}/${functionName}`;
|
|
126
125
|
const res = await fetch(url, {
|
|
127
126
|
method: 'POST',
|
|
128
127
|
body: JSON.stringify(data),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"function.d.ts","sourceRoot":"","sources":["../../../src/function.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;CAChC;AAMD,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE;IACnD,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;CAC1B,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,mBAAmB,CAAC;CACnC,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"}
|
package/src/function.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Client } from '@dxos/client';
|
|
6
|
-
|
|
7
|
-
export interface Response {
|
|
8
|
-
status(code: number): Response;
|
|
9
|
-
succeed(data?: object): Response;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface FunctionContext {
|
|
13
|
-
client: Client;
|
|
14
|
-
status(code: number): Response;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// https://www.npmjs.com/package/aws-lambda
|
|
18
|
-
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
19
|
-
|
|
20
|
-
// TODO(burdon): Types.
|
|
21
|
-
export type FunctionSubscriptionEvent = {
|
|
22
|
-
space: string;
|
|
23
|
-
objects: string[];
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type FunctionHandler<T extends {}> = (params: {
|
|
27
|
-
event: T;
|
|
28
|
-
context: FunctionContext;
|
|
29
|
-
}) => Promise<Response | void>;
|
|
30
|
-
|
|
31
|
-
export type FunctionsManifest = {
|
|
32
|
-
functions: FunctionConfig[];
|
|
33
|
-
triggers: FunctionTrigger[];
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type FunctionConfig = {
|
|
37
|
-
id: string;
|
|
38
|
-
description?: string;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export type FunctionTrigger = {
|
|
42
|
-
function: string;
|
|
43
|
-
subscription: TriggerSubscription;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export type TriggerSubscription = {
|
|
47
|
-
type: string;
|
|
48
|
-
spaceKey: string;
|
|
49
|
-
props?: Record<string, any>;
|
|
50
|
-
nested?: string[];
|
|
51
|
-
};
|