@foxtware/mineral 0.1.4 → 0.1.5
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/.hosting.yml.sample +4 -5
- package/_deploy_scripts/generateHosted.js +12 -11
- package/handlerPaths.js +23 -0
- package/hosting.utils.js +55 -63
- package/package.json +1 -1
- package/server.js +3 -7
- package/server.utils.js +0 -34
- package/wrappers.js +37 -0
package/.hosting.yml.sample
CHANGED
|
@@ -12,15 +12,14 @@ functions:
|
|
|
12
12
|
max_instances: 1
|
|
13
13
|
timeout: 300s
|
|
14
14
|
wrappers:
|
|
15
|
-
- '@foxtware/mineral/server.utils#requireHostedApiKey'
|
|
16
|
-
- '@foxtware/mineral/server.utils#allowCrossOriginCallsAndHandleOptions'
|
|
17
15
|
# entry_point: otherExportName
|
|
16
|
+
- requireHostedApiKey
|
|
17
|
+
- allowCrossOriginCallsAndHandleOptions
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
source: '@foxtware/mineral/api/pokemon/pokemonPokeballThrow.js'
|
|
19
|
+
pokemonPokeballThrow:
|
|
21
20
|
max_instances: 1
|
|
22
21
|
wrappers:
|
|
23
|
-
-
|
|
22
|
+
- checkTrainer
|
|
24
23
|
|
|
25
24
|
groups:
|
|
26
25
|
example_group:
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const { wrapHostedFunction
|
|
2
|
+
const { wrapHostedFunction } = require('../hosting.utils');
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
4
|
+
const formatResolvedWrapperForHostedJs = ({ modulePath, wrapperName }) => (
|
|
5
|
+
`require('${ modulePath }').${ wrapperName }`
|
|
6
|
+
);
|
|
8
7
|
|
|
9
|
-
const formatWrappersArg = (
|
|
10
|
-
if (!
|
|
8
|
+
const formatWrappersArg = (resolvedWrappers = []) => {
|
|
9
|
+
if (!resolvedWrappers.length) {
|
|
11
10
|
return '';
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const wrapperLines =
|
|
13
|
+
const wrapperLines = resolvedWrappers.map((resolvedWrapper) => (
|
|
14
|
+
` ${ formatResolvedWrapperForHostedJs(resolvedWrapper) },`
|
|
15
|
+
));
|
|
15
16
|
return `, [\n${ wrapperLines.join('\n') }\n ]`;
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -21,11 +22,11 @@ const generateHostedJs = ({
|
|
|
21
22
|
const exportLines = [];
|
|
22
23
|
|
|
23
24
|
for (const hostedHandler of hostedHandlers) {
|
|
24
|
-
const {
|
|
25
|
-
const wrappersArg = formatWrappersArg(
|
|
25
|
+
const { hostedName, handlerName, resolvedWrappers = [], requirePath } = hostedHandler;
|
|
26
|
+
const wrappersArg = formatWrappersArg(resolvedWrappers);
|
|
26
27
|
|
|
27
28
|
exportLines.push(
|
|
28
|
-
` ${
|
|
29
|
+
` ${ hostedName }: wrapHostedFunction(() => require('${ requirePath }'), '${ handlerName }'${ wrappersArg }),`,
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
32
|
|
package/handlerPaths.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const MINERAL_ROOT = __dirname;
|
|
2
|
+
const MINERAL_API_DIR = `${ MINERAL_ROOT }/api`;
|
|
3
|
+
|
|
4
|
+
const getRequirePathForHandler = (handler, workspace) => {
|
|
5
|
+
const normalizedWorkspace = workspace.replace(/\/$/, '');
|
|
6
|
+
const { filePath } = handler;
|
|
7
|
+
|
|
8
|
+
if (filePath.startsWith(`${ normalizedWorkspace }/`)) {
|
|
9
|
+
return `./${ filePath.slice(normalizedWorkspace.length + 1) }`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (filePath.startsWith(`${ MINERAL_API_DIR }/`)) {
|
|
13
|
+
const relativePath = filePath.slice(MINERAL_API_DIR.length + 1);
|
|
14
|
+
return `@foxtware/mineral/api/${ relativePath }`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
throw new Error(`Handler "${ handler.routeName }" is not in workspace or mineral api dirs`);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
MINERAL_API_DIR,
|
|
22
|
+
getRequirePathForHandler,
|
|
23
|
+
};
|
package/hosting.utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const { createRequire } = require('module');
|
|
3
3
|
const yaml = require('yaml');
|
|
4
|
+
const { getRequirePathForHandler } = require('./handlerPaths');
|
|
4
5
|
const {
|
|
5
6
|
respondJson,
|
|
6
7
|
errorToReadable,
|
|
@@ -11,61 +12,63 @@ const {
|
|
|
11
12
|
statusCodeFromResult,
|
|
12
13
|
} = require('./server.utils');
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const MINERAL_WRAPPERS_MODULE = '@foxtware/mineral/wrappers.js';
|
|
16
|
+
const WORKSPACE_WRAPPERS_MODULE = './wrappers.js';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const validateWrapperRef = (wrapperRef, workspaceRequire) => {
|
|
28
|
-
const { modulePath, exportName } = parseWrapperRef(wrapperRef);
|
|
29
|
-
workspaceRequire.resolve(modulePath);
|
|
30
|
-
const moduleExports = workspaceRequire(modulePath);
|
|
18
|
+
const getMineralWrappers = (workspaceRequire) => {
|
|
19
|
+
try {
|
|
20
|
+
return workspaceRequire(MINERAL_WRAPPERS_MODULE);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
throw new Error(`Wrapper export not found: ${ exportName } in ${ modulePath }`);
|
|
26
|
+
return require('./wrappers.js');
|
|
34
27
|
}
|
|
35
28
|
};
|
|
36
29
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const getHostedEntries = (functions = {}) => {
|
|
42
|
-
const byEntryPoint = new Map();
|
|
30
|
+
const resolveWrapperName = (wrapperName, workspaceRequire) => {
|
|
31
|
+
if (typeof wrapperName !== 'string' || !wrapperName.trim()) {
|
|
32
|
+
throw new Error(`Invalid wrapper name: ${ wrapperName }`);
|
|
33
|
+
}
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
const mineralWrappers = getMineralWrappers(workspaceRequire);
|
|
36
|
+
if (typeof mineralWrappers[wrapperName] === 'function') {
|
|
37
|
+
return {
|
|
38
|
+
wrapperName,
|
|
39
|
+
modulePath: MINERAL_WRAPPERS_MODULE,
|
|
49
40
|
};
|
|
41
|
+
}
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
try {
|
|
44
|
+
const workspaceWrappers = workspaceRequire(WORKSPACE_WRAPPERS_MODULE);
|
|
45
|
+
if (typeof workspaceWrappers[wrapperName] === 'function') {
|
|
46
|
+
return {
|
|
47
|
+
wrapperName,
|
|
48
|
+
modulePath: WORKSPACE_WRAPPERS_MODULE,
|
|
49
|
+
};
|
|
54
50
|
}
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
53
|
+
throw error;
|
|
58
54
|
}
|
|
59
|
-
|
|
60
|
-
byEntryPoint.set(entryPoint, existing);
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Wrapper "${ wrapperName }" not found in ${ MINERAL_WRAPPERS_MODULE } or ${ WORKSPACE_WRAPPERS_MODULE }`,
|
|
59
|
+
);
|
|
64
60
|
};
|
|
65
61
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
const getHostedEntries = (functions = {}) => (
|
|
63
|
+
Object.entries(functions).map(([hostedName, functionConfig = {}]) => ({
|
|
64
|
+
hostedName,
|
|
65
|
+
handlerName: functionConfig.entry_point || functionConfig.entryPoint || hostedName,
|
|
66
|
+
wrappers: Array.isArray(functionConfig.wrappers) ? functionConfig.wrappers : [],
|
|
67
|
+
}))
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const functionUsesWrapper = (functionConfig = {}, wrapperName) => (
|
|
71
|
+
Array.isArray(functionConfig.wrappers) && functionConfig.wrappers.includes(wrapperName)
|
|
69
72
|
);
|
|
70
73
|
|
|
71
74
|
const getFuncApiConfig = ({
|
|
@@ -218,35 +221,23 @@ const resolveHostedHandlersForDeploy = ({
|
|
|
218
221
|
const hostedEntries = getHostedEntries(functions);
|
|
219
222
|
|
|
220
223
|
return hostedEntries.map((hostedEntry) => {
|
|
221
|
-
const {
|
|
222
|
-
|
|
223
|
-
for (const wrapperRef of wrappers) {
|
|
224
|
-
validateWrapperRef(wrapperRef, workspaceRequire);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (source) {
|
|
228
|
-
workspaceRequire.resolve(source);
|
|
229
|
-
const moduleExports = workspaceRequire(source);
|
|
230
|
-
if (typeof moduleExports[entryPoint] !== 'function') {
|
|
231
|
-
throw new Error(`Hosted export not found: ${ entryPoint } in ${ source }`);
|
|
232
|
-
}
|
|
224
|
+
const { handlerName, wrappers = [] } = hostedEntry;
|
|
233
225
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
};
|
|
238
|
-
}
|
|
226
|
+
const resolvedWrappers = wrappers.map((wrapperName) => (
|
|
227
|
+
resolveWrapperName(wrapperName, workspaceRequire)
|
|
228
|
+
));
|
|
239
229
|
|
|
240
|
-
const handler = handlersByName.get(
|
|
230
|
+
const handler = handlersByName.get(handlerName);
|
|
241
231
|
if (!handler) {
|
|
242
232
|
throw new Error(
|
|
243
|
-
`
|
|
233
|
+
`Function "${ handlerName }" not found — add a handler in workspace api dirs or mineral api`,
|
|
244
234
|
);
|
|
245
235
|
}
|
|
246
236
|
|
|
247
237
|
return {
|
|
248
238
|
...hostedEntry,
|
|
249
|
-
|
|
239
|
+
resolvedWrappers,
|
|
240
|
+
requirePath: getRequirePathForHandler(handler, workspace),
|
|
250
241
|
};
|
|
251
242
|
});
|
|
252
243
|
};
|
|
@@ -254,7 +245,7 @@ const resolveHostedHandlersForDeploy = ({
|
|
|
254
245
|
// TODO: support credsPayload in google_cloud_info instead of full workspace .creds.yml
|
|
255
246
|
|
|
256
247
|
module.exports = {
|
|
257
|
-
|
|
248
|
+
resolveWrapperName,
|
|
258
249
|
getFuncApiConfig,
|
|
259
250
|
wrapHostedFunction,
|
|
260
251
|
readHostingYml,
|
|
@@ -264,5 +255,6 @@ module.exports = {
|
|
|
264
255
|
getHostedEntries,
|
|
265
256
|
resolveHostedHandlersForDeploy,
|
|
266
257
|
functionUsesWrapper,
|
|
267
|
-
|
|
258
|
+
MINERAL_WRAPPERS_MODULE,
|
|
259
|
+
WORKSPACE_WRAPPERS_MODULE,
|
|
268
260
|
};
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -4,8 +4,7 @@ const { respondJson, errorToReadable, getRequestBody, argsFromBody, funcApi, sta
|
|
|
4
4
|
const { getWorkspace, setWorkspace, loadWorkspaceEnv, toAbsolutePath } = require('./api/workspace');
|
|
5
5
|
const { getApiDirs, readCliFlag } = require('./cli');
|
|
6
6
|
const { getFuncApiConfig } = require('./hosting.utils');
|
|
7
|
-
|
|
8
|
-
const MINERAL_API_DIR = `${ __dirname }/api`;
|
|
7
|
+
const { MINERAL_API_DIR, getRequirePathForHandler } = require('./handlerPaths');
|
|
9
8
|
|
|
10
9
|
const getConfig = (options = {}) => ({
|
|
11
10
|
port: Number(options.port ?? process.env.PORT ?? 8000),
|
|
@@ -69,7 +68,6 @@ const listJsFiles = (directory) => {
|
|
|
69
68
|
const directoriesToScan = ({
|
|
70
69
|
workspace,
|
|
71
70
|
api_dirs,
|
|
72
|
-
host_mode = false,
|
|
73
71
|
}) => {
|
|
74
72
|
const extraDirs = api_dirs.map((dir) => (
|
|
75
73
|
dir.startsWith('/')
|
|
@@ -77,10 +75,6 @@ const directoriesToScan = ({
|
|
|
77
75
|
: `${ workspace }/${ dir }`
|
|
78
76
|
));
|
|
79
77
|
|
|
80
|
-
if (host_mode && api_dirs.length) {
|
|
81
|
-
return extraDirs;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
78
|
return [MINERAL_API_DIR, ...extraDirs];
|
|
85
79
|
};
|
|
86
80
|
|
|
@@ -225,6 +219,8 @@ const startServer = (options = {}) => {
|
|
|
225
219
|
module.exports = {
|
|
226
220
|
startServer,
|
|
227
221
|
loadHandlers,
|
|
222
|
+
getRequirePathForHandler,
|
|
223
|
+
MINERAL_API_DIR,
|
|
228
224
|
get server() {
|
|
229
225
|
if (!server) {
|
|
230
226
|
startServer();
|
package/server.utils.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const { logDeep } = require('./api/utils');
|
|
2
2
|
const { StringDecoder } = require('string_decoder');
|
|
3
|
-
const { HOSTED } = require('./api/constants');
|
|
4
3
|
|
|
5
4
|
const respondJson = (res, statusCode, payload) => {
|
|
6
5
|
logDeep(payload);
|
|
@@ -132,37 +131,6 @@ const wrapFunction = (func, wrappers = []) => async (req, res, ...rest) => {
|
|
|
132
131
|
return func(req, res, ...rest);
|
|
133
132
|
};
|
|
134
133
|
|
|
135
|
-
const requireHostedApiKey = async (req) => {
|
|
136
|
-
if (!HOSTED) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (req.headers['x-api-key'] !== process.env.HOSTED_API_KEY) {
|
|
141
|
-
return {
|
|
142
|
-
ok: false,
|
|
143
|
-
error: {
|
|
144
|
-
code: 'UNAUTHORIZED',
|
|
145
|
-
message: 'Unauthorized',
|
|
146
|
-
statusCode: 401,
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const allowCrossOriginCallsAndHandleOptions = async (req, res) => {
|
|
153
|
-
const { origin } = req.headers;
|
|
154
|
-
|
|
155
|
-
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
|
156
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
157
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-api-key, x-wf-token, x-wf-value');
|
|
158
|
-
|
|
159
|
-
if (req.method === 'OPTIONS') {
|
|
160
|
-
res.writeHead(204);
|
|
161
|
-
res.end();
|
|
162
|
-
return { handled: true };
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
134
|
const statusCodeFromResult = (result) => {
|
|
167
135
|
if (result?.ok === false) {
|
|
168
136
|
return result?.error?.statusCode ?? 400;
|
|
@@ -280,8 +248,6 @@ module.exports = {
|
|
|
280
248
|
getRequestBody,
|
|
281
249
|
argsFromBody,
|
|
282
250
|
wrapFunction,
|
|
283
|
-
requireHostedApiKey,
|
|
284
|
-
allowCrossOriginCallsAndHandleOptions,
|
|
285
251
|
statusCodeFromResult,
|
|
286
252
|
funcApi,
|
|
287
253
|
};
|
package/wrappers.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const { HOSTED } = require('./api/constants');
|
|
2
|
+
|
|
3
|
+
const requireHostedApiKey = async (req) => {
|
|
4
|
+
if (!HOSTED) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (req.headers['x-api-key'] !== process.env.HOSTED_API_KEY) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
error: {
|
|
12
|
+
code: 'UNAUTHORIZED',
|
|
13
|
+
message: 'Unauthorized',
|
|
14
|
+
statusCode: 401,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const allowCrossOriginCallsAndHandleOptions = async (req, res) => {
|
|
21
|
+
const { origin } = req.headers;
|
|
22
|
+
|
|
23
|
+
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
|
24
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
25
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, x-api-key, x-wf-token, x-wf-value');
|
|
26
|
+
|
|
27
|
+
if (req.method === 'OPTIONS') {
|
|
28
|
+
res.writeHead(204);
|
|
29
|
+
res.end();
|
|
30
|
+
return { handled: true };
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
requireHostedApiKey,
|
|
36
|
+
allowCrossOriginCallsAndHandleOptions,
|
|
37
|
+
};
|