Initial commit

This commit is contained in:
abrendan
2023-11-30 14:15:19 +00:00
commit e4599df811
5457 changed files with 500139 additions and 0 deletions

29
node_modules/server/src/config/env.js generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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' });
}
};