mirror of
https://github.com/abrendan/MicDropMessages.git
synced 2025-08-25 14:02:03 +02:00
Initial commit
This commit is contained in:
29
node_modules/server/src/config/env.js
generated
vendored
Normal file
29
node_modules/server/src/config/env.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Load them from the environment file if any
|
||||
require('dotenv').config({ silent: true });
|
||||
|
||||
// Check if a variable is numeric even if string
|
||||
const is = {
|
||||
numeric: num => !isNaN(num),
|
||||
boolean: bool => /^(true|false)$/i.test(bool),
|
||||
json: str => /^[{[]/.test(str) && /[}\]]$/.test(str)
|
||||
};
|
||||
|
||||
const type = str => {
|
||||
if (!str) return;
|
||||
if (typeof str !== 'string') return str;
|
||||
if (is.numeric(str)) return +str;
|
||||
if (is.boolean(str)) return /true/i.test(str);
|
||||
try {
|
||||
if (is.json(str)) return JSON.parse(str);
|
||||
} catch (err) {
|
||||
return str;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
const env = {};
|
||||
for (let key in process.env) {
|
||||
env[key] = type(process.env[key]);
|
||||
}
|
||||
|
||||
module.exports = env;
|
63
node_modules/server/src/config/errors.js
generated
vendored
Normal file
63
node_modules/server/src/config/errors.js
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
const error = require('../../error')('/server/options', {
|
||||
url: ({ id }) => `https://serverjs.io/documentation/errors/#${id}`
|
||||
});
|
||||
|
||||
error.notobject = `
|
||||
Your options must be an object as required by the options definition.
|
||||
If you are a developer and want to accept a single option, make sure
|
||||
to use the '__root' property.
|
||||
`;
|
||||
|
||||
error.noarg = ({ name }) => `
|
||||
The option '${name}' cannot be passed through the arguments of server. This
|
||||
might be because it's sensitive and it has to be set in the environment.
|
||||
Please read the documentation for '${name}' and make sure to set it correctly.
|
||||
`;
|
||||
|
||||
error.noenv = ({ name }) => `
|
||||
The option '${name}' cannot be passed through the environment of server.
|
||||
Please read the documentation for '${name}' and make sure to set it correctly.
|
||||
`;
|
||||
|
||||
error.cannotextend = ({ type, name }) => `
|
||||
The option "${name}" must be an object but it received "${type}".
|
||||
Please check your options to make sure you are passing an object.
|
||||
${type === 'undefined' ? `
|
||||
If you are the creator of the plugin and you are receiving 'undefined', you
|
||||
could allow for the default behaviour to be an empty object 'default: {}'
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
error.required = ({ name }) => `
|
||||
The option '${name}' is required but it was not set neither as an argument nor
|
||||
in the environment. Please make sure to set it.
|
||||
`;
|
||||
|
||||
error.type = ({ name, expected, received, value }) => `
|
||||
The option '${name}' should be a '[${typeof expected}]' but you passed a '${received}':
|
||||
${JSON.stringify(value)}
|
||||
`;
|
||||
|
||||
error.enum = ({ name, value, possible }) => `
|
||||
The option '${name}' has a value of '${value}' but it should have one of these values:
|
||||
${JSON.stringify(possible)}
|
||||
`;
|
||||
|
||||
error.validate = ({ name, value }) => `
|
||||
Failed to validate the option '${name}' with the value '${value}'. Please
|
||||
consult this option documentation for more information.
|
||||
`;
|
||||
|
||||
error.secretexample = `
|
||||
It looks like you are trying to use 'your-random-string-here' as the secret,
|
||||
just as in the documentation. Please don't do this! Create a strong secret
|
||||
and store it in your '.env'.
|
||||
`;
|
||||
|
||||
error.secretgenerated = `
|
||||
Please change the secret in your environment configuration.
|
||||
The default one is not recommended and should be changed.
|
||||
More info in https://serverjs.io/errors#defaultSecret
|
||||
`;
|
||||
|
||||
module.exports = error;
|
18
node_modules/server/src/config/index.js
generated
vendored
Normal file
18
node_modules/server/src/config/index.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
const parse = require('./parse');
|
||||
const schema = require('./schema');
|
||||
const env = require('./env');
|
||||
|
||||
// Accept the user options (first argument) and then a list with all the plugins
|
||||
// This will allow us to use the plugin's schemas as well
|
||||
module.exports = async (user = {}, plugins = []) => {
|
||||
|
||||
// First and most important is the core and the user-defined options
|
||||
const options = await parse(schema, user, env);
|
||||
|
||||
// Then load plugin options namespaced with the name in parallel
|
||||
await Promise.all(plugins.map(async ({ name, options: def = {}} = {}) => {
|
||||
options[name] = await parse(def, user[name], env, options);
|
||||
}));
|
||||
|
||||
return options;
|
||||
};
|
78
node_modules/server/src/config/init.test.js
generated
vendored
Normal file
78
node_modules/server/src/config/init.test.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
const server = require('server');
|
||||
const schema = require('./schema');
|
||||
const port = require('server/test/port');
|
||||
|
||||
describe('Options', () => {
|
||||
it('default settings are correct', async () => {
|
||||
expect(schema.port.default).toBe(3000);
|
||||
expect(schema.engine.default).toBe('pug');
|
||||
expect(schema.public.default).toBe('public');
|
||||
expect(schema.secret.default).toMatch(/^secret-/);
|
||||
});
|
||||
|
||||
it('accepts a single port Number', async () => {
|
||||
const options = port();
|
||||
const ctx = await server(options);
|
||||
ctx.close();
|
||||
expect(ctx.options.port).toBe(options);
|
||||
});
|
||||
|
||||
it('accepts a simple Object with a port prop', async () => {
|
||||
const options = { port: port() };
|
||||
const ctx = await server(options);
|
||||
ctx.close();
|
||||
expect(ctx.options.port).toBe(options.port);
|
||||
});
|
||||
|
||||
// it('can listen only one time to the same port', async () => {
|
||||
// const onePort = port();
|
||||
// const ctx = await server(onePort);
|
||||
// let err = await server(onePort).catch(err => err);
|
||||
// await ctx.close();
|
||||
// expect(err.code).toBe('EADDRINUSE');
|
||||
// });
|
||||
|
||||
it('sets the engine properly `engine`', async () => {
|
||||
const ctx = await server({ engine: 'whatever', port: port() });
|
||||
ctx.close();
|
||||
expect(ctx.app.get('view engine')).toBe('whatever');
|
||||
});
|
||||
|
||||
// TODO: this goes in express plugin
|
||||
// it('sets the engine properly `view engine`', async () => {
|
||||
// const ctx = await server({ 'view engine': 'whatever', port: port() });
|
||||
// ctx.close();
|
||||
// expect(ctx.app.get('view engine')).toBe('whatever');
|
||||
// });
|
||||
|
||||
it('has independent instances', async () => {
|
||||
const portA = port();
|
||||
const portB = port();
|
||||
const serv1 = await server(portA);
|
||||
const serv2 = await server(portB);
|
||||
await serv1.close();
|
||||
await serv2.close();
|
||||
|
||||
expect(serv2.options.port).toBe(portB);
|
||||
const portC = port();
|
||||
serv2.options.port = portC;
|
||||
expect(serv1.options.port).toBe(portA);
|
||||
expect(serv2.options.port).toBe(portC);
|
||||
|
||||
serv2.a = 'abc';
|
||||
expect(typeof serv1.a).toBe('undefined');
|
||||
expect(serv2.a).toBe('abc');
|
||||
});
|
||||
|
||||
// // NOT PART OF THE STABLE API
|
||||
// it('logs init string', async () => {
|
||||
// const logs = [];
|
||||
// const index = server.plugins.push({
|
||||
// name: 'log', launch: ctx => { ctx.log = msg => logs.push(msg) }
|
||||
// });
|
||||
// const ctx = await server({ port: port(), verbose: true });
|
||||
// ctx.close();
|
||||
// delete server.plugins[index];
|
||||
// expect(logs.filter(one => /started on/.test(one)).length).toBe(1);
|
||||
// });
|
||||
});
|
40
node_modules/server/src/config/integration.test.js
generated
vendored
Normal file
40
node_modules/server/src/config/integration.test.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Test runner:
|
||||
const run = require('server/test/run');
|
||||
const path = require('path');
|
||||
|
||||
const test = 'test';
|
||||
|
||||
describe('Basic router types', () => {
|
||||
// TODO: fix this
|
||||
it('has independent options', async () => {
|
||||
const res = await Promise.all([
|
||||
run({ public: 'right' }, ctx => new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
ctx.res.send(ctx.options.public);
|
||||
resolve();
|
||||
}, 1000);
|
||||
})).get('/'),
|
||||
run({ public: 'wrong' }, ctx => ctx.options.public).get('/')
|
||||
]);
|
||||
|
||||
expect(res[0].body).toMatch(/right/);
|
||||
expect(res[1].body).toMatch(/wrong/);
|
||||
});
|
||||
|
||||
it('accepts several definitions of public correctly', async () => {
|
||||
const full = path.join(process.cwd(), 'test');
|
||||
const publish = ctx => ctx.options.public;
|
||||
|
||||
expect((await run({
|
||||
public: test
|
||||
}, publish).get('/')).body).toBe(full);
|
||||
|
||||
expect((await run({
|
||||
public: './' + test
|
||||
}, publish).get('/')).body).toBe(full);
|
||||
|
||||
expect((await run({
|
||||
public: __dirname + '/../../' + test
|
||||
}, publish).get('/')).body).toBe(full);
|
||||
});
|
||||
});
|
165
node_modules/server/src/config/parse.js
generated
vendored
Normal file
165
node_modules/server/src/config/parse.js
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
// parse.js
|
||||
// Reads an schema and retrieves the proper options from it
|
||||
|
||||
// Errors specifics to this submodule
|
||||
const OptionsError = require('./errors');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
// The main function.
|
||||
// - arg: user options from the argument in the main server() function
|
||||
// - env: the environment variables as read from env.js
|
||||
// - parent: if it's a submodule, the global configuration
|
||||
const parse = module.exports = async (schema, arg = {}, env= {}, parent = {}) => {
|
||||
|
||||
// Fully parsed options will be stored here
|
||||
const options = {};
|
||||
|
||||
// For plugins, accept "false" as an option to nuke a full plugin
|
||||
if (arg === false && parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Accepts a single option instead of an object and it will be mapped to its
|
||||
// root value. Example: server(2000) === server({ port: 2000 })
|
||||
if (typeof arg !== 'object') {
|
||||
if (!schema.__root) {
|
||||
throw new OptionsError('notobject');
|
||||
}
|
||||
arg = { [schema.__root]: arg };
|
||||
}
|
||||
|
||||
// Loop each of the defined options
|
||||
for (let name in schema) {
|
||||
|
||||
// RETRIEVAL
|
||||
// Make the definition local so it's easier to handle
|
||||
const def = schema[name];
|
||||
let value;
|
||||
|
||||
// Skip the control variables such as '__root'
|
||||
if (/^__/.test(name)) continue;
|
||||
|
||||
// Make sure we are dealing with a valid schema definition for this option
|
||||
if (typeof def !== 'object') {
|
||||
throw new Error('Invalid option definition: ' + JSON.stringify(def));
|
||||
}
|
||||
|
||||
// The user defined a function to find the actual value manually
|
||||
if (def.find) {
|
||||
value = await def.find({ arg, env, def, parent, schema });
|
||||
} else {
|
||||
|
||||
// Use the user-passed option unles explictly told not to
|
||||
if (def.arg !== false) {
|
||||
def.arg = def.arg === true ? name : def.arg || name;
|
||||
} else if (arg[name] && process.env.NODE_ENV === 'test') {
|
||||
throw new OptionsError('noarg', { name });
|
||||
}
|
||||
|
||||
// Use the environment variable unless explicitly told not to
|
||||
if (def.env !== false) {
|
||||
def.env = (def.env === true ? name : def.env || name).toUpperCase();
|
||||
} else if (env[name.toUpperCase()] && process.env.NODE_ENV === 'test') {
|
||||
if (!/^win/.test(process.platform) || name !== 'public') {
|
||||
throw new OptionsError('noenv', { name });
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to use the name if we are inheriting with true
|
||||
if (def.env !== false) {
|
||||
def.inherit = (def.inherit === true ? name : def.inherit || name);
|
||||
}
|
||||
|
||||
// List of possibilities, from HIGHER preference to LOWER preference
|
||||
// Removes the empty one and gets the first one as it has HIGHER preference
|
||||
const possible = [
|
||||
env[def.env],
|
||||
arg[def.arg],
|
||||
parent[def.inherit],
|
||||
def.default
|
||||
].filter(value => typeof value !== 'undefined');
|
||||
if (possible.length) {
|
||||
value = possible[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the base object or user object with new values if these are not set
|
||||
if (def.extend && (typeof value === 'undefined' || typeof value === 'object')) {
|
||||
if (typeof value === 'undefined') {
|
||||
value = {};
|
||||
}
|
||||
value = Object.assign({}, def.default, value);
|
||||
}
|
||||
|
||||
// Normalize the "public" folder or file
|
||||
if ((def.file || def.folder) && typeof value === 'string') {
|
||||
if (!path.isAbsolute(value)) {
|
||||
value = path.join(process.cwd(), value);
|
||||
}
|
||||
value = path.normalize(value);
|
||||
if (def.folder && value[value.length - 1] !== path.sep) {
|
||||
value = value + path.sep;
|
||||
}
|
||||
}
|
||||
|
||||
// A final hook for the schema to call up on the value
|
||||
if (def.clean) {
|
||||
value = await def.clean(value, { arg, env, parent, def, schema });
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// VALIDATION
|
||||
// Validate that it is set
|
||||
if (def.required) {
|
||||
if (typeof value === 'undefined') {
|
||||
throw new OptionsError('required', { name });
|
||||
}
|
||||
// TODO: check that the file and folder exist
|
||||
}
|
||||
|
||||
if (def.enum) {
|
||||
if (!def.enum.includes(value)) {
|
||||
throw new OptionsError('enum', { name, value, possible: def.enum });
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the type (only if there's a value)
|
||||
if (def.type && value) {
|
||||
|
||||
// Parse valid types into a simple array of strings: ['string', 'number']
|
||||
def.type = (def.type instanceof Array ? def.type : [def.type])
|
||||
// pulls up the name for primitives such as String, Number, etc
|
||||
.map(one => (one.name ? one.name : one).toLowerCase());
|
||||
|
||||
// Make sure it is one of the valid types
|
||||
if (!def.type.includes(typeof value)) {
|
||||
throw new OptionsError('type', {
|
||||
name, expected: def.type, value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (def.validate) {
|
||||
let ret = def.validate(value, def, options);
|
||||
if (ret instanceof Error) throw ret;
|
||||
if (!ret) throw new OptionsError('validate', { name, value });
|
||||
}
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
options[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// If the property 'options' exists handle it recursively
|
||||
for (let name in schema) {
|
||||
const def = schema[name];
|
||||
if (def.options) {
|
||||
options[name] = await parse(def.options, arg[name], env, options);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
57
node_modules/server/src/config/schema.js
generated
vendored
Normal file
57
node_modules/server/src/config/schema.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
const buffer = require('crypto').randomBytes(60);
|
||||
const token = buffer.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
__root: 'port',
|
||||
port: {
|
||||
default: 3000,
|
||||
type: Number
|
||||
},
|
||||
public: {
|
||||
default: 'public',
|
||||
type: [String, Boolean],
|
||||
file: true,
|
||||
clean: (value, option) => {
|
||||
if (/^win/.test(process.platform) && value === 'C:\\Users\\Public') {
|
||||
value = option.arg.public || option.def.default;
|
||||
|
||||
if (!value) return;
|
||||
const fullpath = path.isAbsolute(value) ? value : path.join(process.cwd(), value);
|
||||
return path.normalize(fullpath);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
},
|
||||
env: {
|
||||
default: 'development',
|
||||
enum: ['production', 'test', 'development'],
|
||||
arg: false,
|
||||
env: 'NODE_ENV'
|
||||
},
|
||||
engine: {
|
||||
default: 'pug',
|
||||
type: [String, Object]
|
||||
},
|
||||
views: {
|
||||
default: 'views',
|
||||
type: String,
|
||||
folder: true
|
||||
},
|
||||
secret: {
|
||||
default: 'secret-' + token,
|
||||
type: String,
|
||||
arg: false
|
||||
// TODO: integrate this
|
||||
// if (options.secret === 'your-random-string-here') {
|
||||
// throw new ServerError('/server/options/secret/example');
|
||||
// }
|
||||
//
|
||||
// if (/^secret-/.test(options.secret) && options.verbose) {
|
||||
// console.log(new ServerError('/server/options/secret/generated'));
|
||||
// }
|
||||
},
|
||||
'x-powered-by': {
|
||||
default: false
|
||||
}
|
||||
};
|
169
node_modules/server/src/config/unit.test.js
generated
vendored
Normal file
169
node_modules/server/src/config/unit.test.js
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
const schema = require('./schema');
|
||||
const parse = require('./parse');
|
||||
const env = require('./env');
|
||||
|
||||
const config = require('./index');
|
||||
|
||||
describe('options', () => {
|
||||
it('is a function', async () => {
|
||||
expect(config).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('can be called empty', async () => {
|
||||
expect(config).not.toThrow();
|
||||
expect(config()).toEqual(jasmine.any(Object));
|
||||
});
|
||||
|
||||
it('returns the correct defaults', async () => {
|
||||
const opts = await config();
|
||||
expect(opts.port).toBe(3000);
|
||||
expect(opts.engine).toBe('pug');
|
||||
});
|
||||
|
||||
it('can set port with a single param', async () => {
|
||||
const opts = await config(2000);
|
||||
expect(opts.port).toBe(2000);
|
||||
});
|
||||
|
||||
it('can set port as an object', async () => {
|
||||
const opts = await config({ port: 2000 });
|
||||
expect(opts.port).toBe(2000);
|
||||
});
|
||||
|
||||
it('no overwritting if no environment set', async () => {
|
||||
const opts = await config({ democ: 10 });
|
||||
expect(opts.democ).toBe(undefined);
|
||||
});
|
||||
|
||||
it.skip('throws when secret is passed manually', async () => {
|
||||
const opts = config({ secret: 'your-random-string-here' });
|
||||
await expect(opts).rejects.toHaveProperty('code', '/server/options/noarg');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('options/parse', () => {
|
||||
it('uses the defaults', async () => {
|
||||
expect(await parse(schema)).toMatchObject({ port: 3000 });
|
||||
expect(await parse(schema, {})).toMatchObject({ port: 3000 });
|
||||
});
|
||||
|
||||
it('uses the __root', async () => {
|
||||
expect(await parse(schema, 2000)).toMatchObject({ port: 2000 });
|
||||
});
|
||||
|
||||
it('can use a plain object', async () => {
|
||||
expect(await parse(schema, { port: 2000 })).toMatchObject({ port: 2000 });
|
||||
});
|
||||
|
||||
it('can use the argument', async () => {
|
||||
const opts = await parse({ public: { arg: true } }, { public: 'abc' });
|
||||
expect(opts.public).toBe('abc');
|
||||
});
|
||||
|
||||
it('can use the ENV', async () => {
|
||||
expect(await parse(schema, { port: 2000 }, { PORT: 1000 })).toMatchObject({ port: 1000 });
|
||||
expect(await parse({ port: { env: true } }, { port: 2000 }, { PORT: 1000 })).toMatchObject({ port: 1000 });
|
||||
});
|
||||
|
||||
it('just works with false env and no env', async () => {
|
||||
const opts = await parse({ public: { env: false }}, { public: 'abc' });
|
||||
expect(opts.public).toBe('abc');
|
||||
});
|
||||
|
||||
it('accepts required with value', async () => {
|
||||
const opts = await parse({ public: { required: true } }, { public: 'abc' });
|
||||
expect(opts.public).toBe('abc');
|
||||
});
|
||||
|
||||
it('environment wins params', async () => {
|
||||
const opts = await parse(schema, { public: 'aaa' }, { PUBLIC: 'abc' });
|
||||
expect(opts.public).toMatch(/[/\\]abc/);
|
||||
});
|
||||
|
||||
it('can handle several types', async () => {
|
||||
expect((await parse(schema, { public: false })).public).toBe(false);
|
||||
expect((await parse(schema, { public: 'abc' })).public).toMatch(/[/\\]abc/);
|
||||
});
|
||||
|
||||
it('rejects on incorrect types', async () => {
|
||||
const pub = parse(schema, { public: 25 });
|
||||
await expect(pub).rejects.toHaveProperty('code', '/server/options/type');
|
||||
|
||||
const port = parse(schema, { port: '25' });
|
||||
await expect(port).rejects.toHaveProperty('code', '/server/options/type');
|
||||
});
|
||||
|
||||
it('can handle NODE_ENV', async () => {
|
||||
expect(await parse(schema, {}, { NODE_ENV: 'development' })).toMatchObject({ env: 'development' });
|
||||
expect(await parse(schema, {}, { NODE_ENV: 'test' })).toMatchObject({ env: 'test' });
|
||||
expect(await parse(schema, {}, { NODE_ENV: 'production' })).toMatchObject({ env: 'production' });
|
||||
});
|
||||
|
||||
it('throws with wrong value', async () => {
|
||||
const env = parse(schema, {}, { NODE_ENV: 'abc' });
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/enum');
|
||||
});
|
||||
|
||||
it('no `__root` should be given no root', async () => {
|
||||
const env = parse({}, 'hello');
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/notobject');
|
||||
});
|
||||
|
||||
it('no `arg` should be given no arg', async () => {
|
||||
const arg = parse(schema, { env: 'abc' });
|
||||
await expect(arg).rejects.toHaveProperty('code', '/server/options/noarg');
|
||||
});
|
||||
|
||||
it.skip('no `env` should be given no env', async () => {
|
||||
const env = parse({ public: { env: false }}, {}, { PUBLIC: 'hello' });
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/noenv');
|
||||
});
|
||||
|
||||
it('throws with no value for required', async () => {
|
||||
const env = parse({ public: { required: true } });
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/required');
|
||||
});
|
||||
|
||||
it('does a validation', async () => {
|
||||
const validate = () => {
|
||||
let err = new Error('Hello world');
|
||||
err.code = '/server/options/fakeerror';
|
||||
return err;
|
||||
};
|
||||
const env = parse({ public: { validate } }, {}, { PUBLIC: 'hello' });
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/fakeerror');
|
||||
});
|
||||
|
||||
it('expects the validation to return truthy', async () => {
|
||||
const opts = await parse({ public: { validate: () => true } }, { public: 'hello' });
|
||||
expect(opts.public).toBe('hello');
|
||||
});
|
||||
|
||||
it('expects the validation not to return false', async () => {
|
||||
const env = parse({ public: { validate: () => false } });
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/validate');
|
||||
});
|
||||
|
||||
it('works as expected in windows', async () => {
|
||||
const env = parse({ public: { validate: () => false } });
|
||||
await expect(env).rejects.toHaveProperty('code', '/server/options/validate');
|
||||
});
|
||||
|
||||
it('can be recursive with subproperties', async () => {
|
||||
const github = await parse({ github: { options: { id: { default: 5 } } } });
|
||||
expect(github).toEqual({ github: { id: 5 } });
|
||||
|
||||
const github2 = await parse({ github: { options: { id: { default: 5 } } } }, { github: { id: 10 }});
|
||||
expect(github2).toEqual({ github: { id: 10 } });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Parses the .env correctly', () => {
|
||||
it('works with pseudo-json', () => {
|
||||
expect(env.TEST44).toBe('{"a"}');
|
||||
});
|
||||
});
|
17
node_modules/server/src/index.test.js
generated
vendored
Normal file
17
node_modules/server/src/index.test.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Test runner:
|
||||
const run = require('server/test/run');
|
||||
|
||||
describe('Default modules', () => {
|
||||
it('can log the context', async () => {
|
||||
const res = await run(ctx => {
|
||||
|
||||
try {
|
||||
require('util').inspect(ctx);
|
||||
} catch (err) {
|
||||
return err.message;
|
||||
}
|
||||
return 'Good!';
|
||||
}).get('/');
|
||||
expect(res.body).toBe('Good!');
|
||||
});
|
||||
});
|
50
node_modules/server/src/join/index.js
generated
vendored
Normal file
50
node_modules/server/src/join/index.js
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
const load = require('loadware');
|
||||
const assert = require('assert');
|
||||
const reply = require('../../reply');
|
||||
|
||||
// Recursively resolve possible function returns
|
||||
const processReturn = async (ctx, ret) => {
|
||||
if (!ret) return;
|
||||
|
||||
// Use the returned reply instance
|
||||
if (ret.constructor.name === 'Reply') {
|
||||
return await ret.exec(ctx);
|
||||
}
|
||||
|
||||
// TODO: make a check for only accepting the right types of return values
|
||||
|
||||
// Create a whole new reply thing
|
||||
const fn = typeof ret === 'number' ? 'status' : 'send';
|
||||
return await reply[fn](ret).exec(ctx);
|
||||
};
|
||||
|
||||
// Pass an array of modern middleware and return a single modern middleware
|
||||
module.exports = (...middles) => {
|
||||
|
||||
// Flattify all of the middleware
|
||||
const middle = load(middles);
|
||||
|
||||
// Go through each of them
|
||||
return async ctx => {
|
||||
for (const mid of middle) {
|
||||
try {
|
||||
// DO NOT MERGE; the else is relevant only for ctx.error
|
||||
if (ctx.error) {
|
||||
// See if this middleware can fix it
|
||||
if (mid.error) {
|
||||
assert(mid.error instanceof Function, 'Error handler should be a function');
|
||||
let ret = await mid.error(ctx);
|
||||
await processReturn(ctx, ret);
|
||||
}
|
||||
}
|
||||
// No error, call next middleware. Skips middleware if there's an error
|
||||
else {
|
||||
let ret = await mid(ctx);
|
||||
await processReturn(ctx, ret);
|
||||
}
|
||||
} catch (err) {
|
||||
ctx.error = err;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
68
node_modules/server/src/join/integration.test.js
generated
vendored
Normal file
68
node_modules/server/src/join/integration.test.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// Test runner:
|
||||
const run = require('server/test/run');
|
||||
|
||||
const server = require('server');
|
||||
const { get } = server.router;
|
||||
const nocsrf = { security: false };
|
||||
|
||||
describe('join', () => {
|
||||
|
||||
it('loads as a function', async () => {
|
||||
const res = await run(() => 'Hello 世界').get('/');
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
|
||||
it('loads as an array', async () => {
|
||||
const res = await run([() => 'Hello 世界']).get('/');
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
|
||||
it('has a valid context', async () => {
|
||||
const res = await run(ctx => {
|
||||
return `${!!ctx.req}:${!!ctx.res}:${!!ctx.options}`;
|
||||
}).get('/');
|
||||
expect(res.body).toBe('true:true:true');
|
||||
});
|
||||
|
||||
it('loads as a relative file', async () => {
|
||||
const res = await run('./test/a.js').get('/');
|
||||
expect(res.body).toBe('世界');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
describe('Full trip request', () => {
|
||||
it('can perform a simple get', async () => {
|
||||
const res = await run(() => 'Hello 世界').get('/');
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
|
||||
it('uses the first reply', async () => {
|
||||
const res = await run([() => 'Hello 世界', () => 'Hello mundo']).get('/');
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
|
||||
it('loads as an array', async () => {
|
||||
const res = await run([() => 'Hello 世界']).get('/');
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
|
||||
it('can perform a simple post', async () => {
|
||||
const replyBody = ctx => `Hello ${ctx.data.a}`;
|
||||
const res = await run(nocsrf, replyBody).post('/', { body: { a: '世界' } });
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
|
||||
it('can set headers', async () => {
|
||||
const middle = get('/', ctx => {
|
||||
ctx.res.header('Expires', 12345);
|
||||
return 'Hello 世界';
|
||||
});
|
||||
const res = await run(middle).get('/');
|
||||
expect(res.request.method).toBe('GET');
|
||||
expect(res.headers.expires).toBe('12345');
|
||||
expect(res.body).toBe('Hello 世界');
|
||||
});
|
||||
});
|
39
node_modules/server/src/join/unit.test.js
generated
vendored
Normal file
39
node_modules/server/src/join/unit.test.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// const join = require('./index.js');
|
||||
|
||||
describe('Performance', () => {
|
||||
it('dummy', () => {});
|
||||
|
||||
// it('takes time to join promises', () => {
|
||||
// const cb = ctx => ctx.count++;
|
||||
//
|
||||
// const times = [10, 100, 1000, 10000, 100000];
|
||||
// const promises = times.map(k => ctx => {
|
||||
// const proms = [];
|
||||
// for (let i = 0; i < k; i++) {
|
||||
// proms.push(cb);
|
||||
// }
|
||||
// console.time('chained-' + k);
|
||||
// return join(proms)({ count: 0 }).then(ctx => {
|
||||
// console.timeEnd('chained-' + k);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// return join(promises)({});
|
||||
// });
|
||||
});
|
||||
|
||||
|
||||
// Similar:
|
||||
// Native:
|
||||
// chained-10: 0.272ms
|
||||
// chained-100: 1.052ms
|
||||
// chained-1000: 13.040ms
|
||||
// chained-10000: 81.560ms
|
||||
// chained-100000: 882.968ms
|
||||
|
||||
// Bluebird (slower):
|
||||
// chained-10: 1.711ms
|
||||
// chained-100: 7.160ms
|
||||
// chained-1000: 13.631ms
|
||||
// chained-10000: 78.505ms
|
||||
// chained-100000: 749.576ms
|
62
node_modules/server/src/modern/errors.js
generated
vendored
Normal file
62
node_modules/server/src/modern/errors.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
const error = require('../../error')('/server/modern/', {
|
||||
url: ({ id }) => `https://serverjs.io/documentation/errors/#${id}`
|
||||
});
|
||||
|
||||
error.missingmiddleware = `
|
||||
modern() expects a middleware to be passed but nothing was passed.
|
||||
`;
|
||||
|
||||
// error.MissingMiddleware = () => `
|
||||
// modern() expects a middleware to be passed but nothing was passed.
|
||||
//`;
|
||||
|
||||
|
||||
error.invalidmiddleware = ({ type }) => `
|
||||
modern() expects the argument to be a middleware function.
|
||||
"${type}" was passed instead
|
||||
`;
|
||||
|
||||
// error.InvalidMiddleware = ({ type }) => `
|
||||
// modern() expects the argument to be a middleware function.
|
||||
// "${type}" was passed instead
|
||||
// `;
|
||||
|
||||
|
||||
error.errormiddleware = `
|
||||
modern() cannot create a modern middleware that handles errors.
|
||||
If you can handle an error in your middleware do it there.
|
||||
Otherwise, use ".catch()" for truly fatal errors as "server().catch()".
|
||||
`;
|
||||
|
||||
// error.ErrorMiddleware = () => `
|
||||
// modern() cannot create a modern middleware that handles errors.
|
||||
// If you can handle an error in your middleware do it there.
|
||||
// Otherwise, use ".catch()" for truly fatal errors as "server().catch()".
|
||||
// `;
|
||||
|
||||
|
||||
error.missingcontext = `
|
||||
There is no context being passed to the middleware.
|
||||
`;
|
||||
|
||||
// error.MissingContext = () => `
|
||||
// There is no context being passed to the middleware.
|
||||
// `;
|
||||
|
||||
|
||||
error.malformedcontext = ({ item }) => `
|
||||
The argument passed as context is malformed.
|
||||
Expecting it to be an object containing "${item}".
|
||||
This is most likely an error from "server.modern".
|
||||
Please report it: https://github.com/franciscop/server/issues
|
||||
`;
|
||||
|
||||
// error.MalformedContext = ({ item }) => `
|
||||
// The argument passed as context is malformed.
|
||||
// Expecting it to be an object containing "${item}".
|
||||
// This is most likely an error from "server.modern".
|
||||
// Please report it: https://github.com/franciscop/server/issues
|
||||
// `;
|
||||
|
||||
|
||||
module.exports = error;
|
22
node_modules/server/src/modern/index.js
generated
vendored
Normal file
22
node_modules/server/src/modern/index.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Modern - Create a modern middleware from the old-style one
|
||||
|
||||
// Cleanly validate data
|
||||
const validate = require('./validate');
|
||||
|
||||
// Pass it an old middleware and return a new one 'ctx => promise'
|
||||
module.exports = middle => {
|
||||
|
||||
// Validate it early so no requests need to be made
|
||||
validate.middleware(middle);
|
||||
|
||||
// Create and return the modern middleware function
|
||||
return ctx => new Promise((resolve, reject) => {
|
||||
validate.context(ctx);
|
||||
|
||||
// It can handle both success or errors. Pass the right ctx
|
||||
const next = err => err ? reject(err) : resolve();
|
||||
|
||||
// Call the old middleware
|
||||
middle(ctx.req, ctx.res, next);
|
||||
});
|
||||
};
|
175
node_modules/server/src/modern/modern.test.js
generated
vendored
Normal file
175
node_modules/server/src/modern/modern.test.js
generated
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
const join = require('../join');
|
||||
const modern = require('./index');
|
||||
const middle = (req, res, next) => next();
|
||||
const ctx = { req: {}, res: {} };
|
||||
|
||||
describe('initializes', () => {
|
||||
it('returns a function', () => {
|
||||
expect(typeof modern(middle)).toBe('function');
|
||||
});
|
||||
|
||||
it('the returned modern middleware has 1 arg', () => {
|
||||
expect(modern(middle).length).toBe(1);
|
||||
});
|
||||
|
||||
it('requires an argument', () => {
|
||||
expect(() => modern()).toThrow();
|
||||
});
|
||||
|
||||
it('a non-function argument throws', () => {
|
||||
expect(() => modern(true)).toThrow();
|
||||
expect(() => modern(5)).toThrow();
|
||||
expect(() => modern('')).toThrow();
|
||||
expect(() => modern([])).toThrow();
|
||||
expect(() => modern({})).toThrow();
|
||||
|
||||
expect(() => modern(() => {})).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('call the middleware', () => {
|
||||
it('returns a promise when called', () => {
|
||||
expect(modern(middle)(ctx) instanceof Promise).toBe(true);
|
||||
});
|
||||
|
||||
it('requires the context to be called', async () => {
|
||||
expect(modern(middle)()).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it('rejected with empty context', async () => {
|
||||
expect(modern(middle)({})).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it('rejected without res', async () => {
|
||||
expect(modern(middle)({ req: {} })).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it('rejected without req', async () => {
|
||||
expect(modern(middle)({ res: {} })).rejects.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
describe('Middleware handles the promise', () => {
|
||||
it('resolves when next is called empty', async () => {
|
||||
await modern((req, res, next) => next())(ctx);
|
||||
});
|
||||
|
||||
it('cannot handle error middleware', async () => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
expect(() => modern((err, req, res, next) => {})).toThrow();
|
||||
});
|
||||
|
||||
it('keeps the context', async () => {
|
||||
const ctx = { req: 1, res: 2 };
|
||||
await modern((req, res, next) => next())(ctx);
|
||||
expect(ctx.req).toBe(1);
|
||||
expect(ctx.res).toBe(2);
|
||||
});
|
||||
|
||||
it('can modify the context', async () => {
|
||||
const middle = (req, res, next) => {
|
||||
req.user = 'myname';
|
||||
res.send = 'sending';
|
||||
next();
|
||||
};
|
||||
const ctx = { req: {}, res: {} };
|
||||
await modern(middle)(ctx);
|
||||
expect(ctx.req.user).toBe('myname');
|
||||
expect(ctx.res.send).toBe('sending');
|
||||
});
|
||||
|
||||
it('has chainable context', async () => {
|
||||
const ctx = { req: { user: 'a' }, res: { send: 'b' } };
|
||||
const middle = (req, res, next) => {
|
||||
req.user += 1;
|
||||
res.send += 2;
|
||||
next();
|
||||
};
|
||||
await modern(middle)(ctx).then(() => modern(middle)(ctx));
|
||||
expect(ctx.req.user).toBe('a11');
|
||||
expect(ctx.res.send).toBe('b22');
|
||||
});
|
||||
|
||||
it('factory can receive options', async () => {
|
||||
|
||||
// The full context
|
||||
const ctx = {
|
||||
req: { user: 'a' },
|
||||
res: { send: 'b' },
|
||||
options: { extra: 1}
|
||||
};
|
||||
|
||||
// A middleware factory
|
||||
const factory = opts => {
|
||||
return (req, res, next) => {
|
||||
req.user += opts.extra;
|
||||
res.send += opts.extra;
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Plain ol' middleware
|
||||
const factored = factory({ extra: 1 });
|
||||
|
||||
// We need to pass it and then re-call it
|
||||
const middles = [
|
||||
|
||||
// Native sync, this could be extracted to '({ req, res, options })'
|
||||
ctx => {
|
||||
ctx.req.user += ctx.options.extra;
|
||||
ctx.res.send += ctx.options.extra;
|
||||
},
|
||||
|
||||
// Native async
|
||||
ctx => new Promise((resolve) => {
|
||||
ctx.req.user += ctx.options.extra;
|
||||
ctx.res.send += ctx.options.extra;
|
||||
resolve();
|
||||
}),
|
||||
|
||||
// Hardcoded case:
|
||||
modern((req, res, next) => {
|
||||
req.user += 1;
|
||||
res.send += 1;
|
||||
next();
|
||||
}),
|
||||
|
||||
// Using some info from the context:
|
||||
ctx => modern((req, res, next) => {
|
||||
req.user += ctx.options.extra;
|
||||
res.send += ctx.options.extra;
|
||||
next();
|
||||
})(ctx),
|
||||
|
||||
// The definition might come from a factory
|
||||
ctx => modern(factory({ extra: ctx.options.extra }))(ctx),
|
||||
|
||||
// The same as above but already defined
|
||||
ctx => modern(factored)(ctx)
|
||||
];
|
||||
|
||||
await join(middles)(ctx);
|
||||
expect(ctx.req.user).toBe('a111111');
|
||||
expect(ctx.res.send).toBe('b111111');
|
||||
});
|
||||
|
||||
it('rejects when next is called with an error', async () => {
|
||||
const wrong = (req, res, next) => next(new Error('Custom error'));
|
||||
expect(modern(wrong)(ctx)).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it('does not resolve nor reject if next is not called', async () => {
|
||||
modern(() => {})(ctx).then(() => {
|
||||
expect('It was resolved').toBe(false);
|
||||
}).catch(() => {
|
||||
expect('It was rejected').toBe(false);
|
||||
});
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 1000);
|
||||
});
|
||||
});
|
||||
});
|
25
node_modules/server/src/modern/validate.js
generated
vendored
Normal file
25
node_modules/server/src/modern/validate.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
const ModernError = require('./errors');
|
||||
|
||||
exports.middleware = middle => {
|
||||
if (!middle) {
|
||||
throw new ModernError('missingmiddleware');
|
||||
}
|
||||
if (!(middle instanceof Function)) {
|
||||
throw new ModernError('invalidmiddleware', { type: typeof middle });
|
||||
}
|
||||
if (middle.length === 4) {
|
||||
throw new ModernError('errormiddleware');
|
||||
}
|
||||
};
|
||||
|
||||
exports.context = ctx => {
|
||||
if (!ctx) {
|
||||
throw new ModernError('missingcontext');
|
||||
}
|
||||
if (!ctx.req) {
|
||||
throw new ModernError('malformedcontext', { item: 'res' });
|
||||
}
|
||||
if (!ctx.res) {
|
||||
throw new ModernError('malformedcontext', { item: 'res' });
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user