@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.
@@ -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
- packagedFunction:
20
- source: '@foxtware/mineral/api/pokemon/pokemonPokeballThrow.js'
19
+ pokemonPokeballThrow:
21
20
  max_instances: 1
22
21
  wrappers:
23
- - '@foxtware/mineral/server.utils#checkTrainer'
22
+ - checkTrainer
24
23
 
25
24
  groups:
26
25
  example_group:
@@ -1,17 +1,18 @@
1
1
  const fs = require('fs');
2
- const { wrapHostedFunction, parseWrapperRef } = require('../hosting.utils');
2
+ const { wrapHostedFunction } = require('../hosting.utils');
3
3
 
4
- const formatWrapperRefForHostedJs = (wrapperRef) => {
5
- const { modulePath, exportName } = parseWrapperRef(wrapperRef);
6
- return `require('${ modulePath }').${ exportName }`;
7
- };
4
+ const formatResolvedWrapperForHostedJs = ({ modulePath, wrapperName }) => (
5
+ `require('${ modulePath }').${ wrapperName }`
6
+ );
8
7
 
9
- const formatWrappersArg = (wrappers = []) => {
10
- if (!wrappers.length) {
8
+ const formatWrappersArg = (resolvedWrappers = []) => {
9
+ if (!resolvedWrappers.length) {
11
10
  return '';
12
11
  }
13
12
 
14
- const wrapperLines = wrappers.map((wrapperRef) => ` ${ formatWrapperRefForHostedJs(wrapperRef) },`);
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 { entryPoint, wrappers = [], requirePath } = hostedHandler;
25
- const wrappersArg = formatWrappersArg(wrappers);
25
+ const { hostedName, handlerName, resolvedWrappers = [], requirePath } = hostedHandler;
26
+ const wrappersArg = formatWrappersArg(resolvedWrappers);
26
27
 
27
28
  exportLines.push(
28
- ` ${ entryPoint }: wrapHostedFunction(() => require('${ requirePath }'), '${ entryPoint }'${ wrappersArg }),`,
29
+ ` ${ hostedName }: wrapHostedFunction(() => require('${ requirePath }'), '${ handlerName }'${ wrappersArg }),`,
29
30
  );
30
31
  }
31
32
 
@@ -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 parseWrapperRef = (wrapperRef) => {
15
- const hashIndex = wrapperRef.lastIndexOf('#');
15
+ const MINERAL_WRAPPERS_MODULE = '@foxtware/mineral/wrappers.js';
16
+ const WORKSPACE_WRAPPERS_MODULE = './wrappers.js';
16
17
 
17
- if (hashIndex === -1) {
18
- throw new Error(`Invalid wrapper ref (expected path#export): ${ wrapperRef }`);
19
- }
20
-
21
- return {
22
- modulePath: wrapperRef.slice(0, hashIndex),
23
- exportName: wrapperRef.slice(hashIndex + 1),
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
- if (typeof moduleExports[exportName] !== 'function') {
33
- throw new Error(`Wrapper export not found: ${ exportName } in ${ modulePath }`);
26
+ return require('./wrappers.js');
34
27
  }
35
28
  };
36
29
 
37
- const wrapperExportName = (wrapperRef) => (
38
- parseWrapperRef(wrapperRef).exportName
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
- for (const [functionName, functionConfig] of Object.entries(functions)) {
45
- const entryPoint = functionConfig.entry_point || functionConfig.entryPoint || functionName;
46
- const existing = byEntryPoint.get(entryPoint) || {
47
- entryPoint,
48
- wrappers: [],
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
- if (Array.isArray(functionConfig.wrappers)) {
52
- existing.wrappers.push(...functionConfig.wrappers);
53
- existing.wrappers = [...new Set(existing.wrappers)];
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 (functionConfig.source) {
57
- existing.source = functionConfig.source;
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
- return [...byEntryPoint.values()];
57
+ throw new Error(
58
+ `Wrapper "${ wrapperName }" not found in ${ MINERAL_WRAPPERS_MODULE } or ${ WORKSPACE_WRAPPERS_MODULE }`,
59
+ );
64
60
  };
65
61
 
66
- const functionUsesWrapper = (functionConfig = {}, exportName) => (
67
- Array.isArray(functionConfig.wrappers)
68
- && functionConfig.wrappers.some((wrapperRef) => wrapperExportName(wrapperRef) === exportName)
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 { entryPoint, source, wrappers = [] } = hostedEntry;
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
- return {
235
- ...hostedEntry,
236
- requirePath: source,
237
- };
238
- }
226
+ const resolvedWrappers = wrappers.map((wrapperName) => (
227
+ resolveWrapperName(wrapperName, workspaceRequire)
228
+ ));
239
229
 
240
- const handler = handlersByName.get(entryPoint);
230
+ const handler = handlersByName.get(handlerName);
241
231
  if (!handler) {
242
232
  throw new Error(
243
- `entry_point "${ entryPoint }" not found in workspace handlers add api/ handler or source in .hosting.yml`,
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
- requirePath: `./${ handler.filePath.slice(workspace.length + 1) }`,
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
- parseWrapperRef,
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
- wrapperExportName,
258
+ MINERAL_WRAPPERS_MODULE,
259
+ WORKSPACE_WRAPPERS_MODULE,
268
260
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxtware/mineral",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "bin": {
5
5
  "mineral": "bin/mineral.js"
6
6
  },
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
+ };