@momsfriendlydevco/cowboy 1.3.0 → 1.4.1
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/eslint.config.js +10 -0
- package/lib/cowboy.js +56 -12
- package/lib/debug.js +6 -0
- package/lib/request.js +1 -1
- package/lib/response.js +2 -1
- package/lib/testkit.js +7 -1
- package/middleware/cors.js +13 -9
- package/middleware/devOnly.js +1 -1
- package/middleware/parseJwt.js +3 -1
- package/middleware/validate.js +1 -1
- package/middleware/validateBody.js +6 -0
- package/middleware/validateHeaders.js +6 -0
- package/middleware/validateParams.js +6 -0
- package/middleware/validateQuery.js +6 -0
- package/package.json +4 -15
package/eslint.config.js
ADDED
package/lib/cowboy.js
CHANGED
|
@@ -25,10 +25,11 @@ export class Cowboy {
|
|
|
25
25
|
* General settings for this Cowboy instance
|
|
26
26
|
*
|
|
27
27
|
* @type {Object}
|
|
28
|
+
* @property {Boolean|'VAR'} scheduler Add a generic `/__scheduled` endpoint to execute any queued scheduler. If value is `'VAR'` this will only apply if the env `COWBOY_SCHEDULER` is set and truthy
|
|
28
29
|
* @property {Function} pathTidy Additional tidyup for server request paths, useful if the API does not live at the server root. Defaults to removing a "/api/:worker/" prefix
|
|
29
30
|
*/
|
|
30
31
|
settings = {
|
|
31
|
-
|
|
32
|
+
scheduler: 'VAR',
|
|
32
33
|
pathTidy(path) {
|
|
33
34
|
return path
|
|
34
35
|
.replace(/^\/api\/\w+/, '/')
|
|
@@ -39,7 +40,7 @@ export class Cowboy {
|
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* List of middleware which will be called on all matching routes
|
|
42
|
-
* @type Array<CowboyMiddleware>
|
|
43
|
+
* @type {Array<CowboyMiddleware>}
|
|
43
44
|
*/
|
|
44
45
|
earlyMiddleware = [];
|
|
45
46
|
|
|
@@ -62,9 +63,9 @@ export class Cowboy {
|
|
|
62
63
|
* Queue up a middleware path
|
|
63
64
|
* All given middleware is called in sequence, if middleware
|
|
64
65
|
*
|
|
65
|
-
* @param {String|Array<String>} methods A method matcher or array of available methods
|
|
66
|
+
* @param {String|Array<String>} methods A method matcher or array of available methods in upper-case
|
|
66
67
|
* @param {String|RegExp|Array<String|RegExp>} paths A prefix path to match
|
|
67
|
-
* @param {CowboyMiddleware} middleware
|
|
68
|
+
* @param {CowboyMiddleware...} middleware Middleware to call in sequence
|
|
68
69
|
*
|
|
69
70
|
* @returns {Cowboy} This chainable Cowboy router instance
|
|
70
71
|
*/
|
|
@@ -107,7 +108,8 @@ export class Cowboy {
|
|
|
107
108
|
|
|
108
109
|
/**
|
|
109
110
|
* Action an incoming route by resolving + walking down its middleware chain
|
|
110
|
-
*
|
|
111
|
+
*
|
|
112
|
+
* @param {CloudflareRequest} cfReq The incoming request
|
|
111
113
|
* @param {Object} [env] Optional environment passed from Cloudflare
|
|
112
114
|
* @returns {Promise<CowboyResponse>} A promise which will eventually resolve when all middleware completes
|
|
113
115
|
*/
|
|
@@ -117,6 +119,46 @@ export class Cowboy {
|
|
|
117
119
|
if (env.COWBOY_DEBUG)
|
|
118
120
|
debug.enabled = true;
|
|
119
121
|
|
|
122
|
+
if ( // Setup scheduler endpoint if this is the first fetch() hit were we can access the `env` state
|
|
123
|
+
!this._schedulerSetup
|
|
124
|
+
&& (
|
|
125
|
+
this.settings.scheduler === true
|
|
126
|
+
|| (
|
|
127
|
+
this.settings.scheduler == 'VAR'
|
|
128
|
+
&& env.COWBOY_SCHEDULER
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
) {
|
|
132
|
+
console.log('Scheduler setup against /__scheduled');
|
|
133
|
+
this._schedulerSetup = true;
|
|
134
|
+
|
|
135
|
+
// Find first GET operation
|
|
136
|
+
let routeIndex = this.routes.findIndex(r => r.methods.some(m => m == 'GET'));
|
|
137
|
+
if (routeIndex < 0) routeIndex = this.routes.length; // If no GETS, assume end position
|
|
138
|
+
|
|
139
|
+
// Splice into position before first GET
|
|
140
|
+
let matcher = compileRoutePaths('/__scheduled');
|
|
141
|
+
this.routes.splice(routeIndex, 0, {
|
|
142
|
+
methods: ['GET'],
|
|
143
|
+
paths: matcher.paths,
|
|
144
|
+
matcher,
|
|
145
|
+
middleware: [
|
|
146
|
+
async (req, res, env) => {
|
|
147
|
+
debug('Executing schedule handler');
|
|
148
|
+
if (!this.schedule.handler) return res.status(404).send('No scheduler installed');
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
let result = await this.schedule.handler.call(this, {}, env, {});
|
|
152
|
+
res.send(result);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
res.status(400).send(`Scheduler threw error: ${e.toString()}`);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
120
162
|
// Create basic [req]uest / [res]ponse objects
|
|
121
163
|
let req = new CowboyRequest(cfReq, {
|
|
122
164
|
router: this,
|
|
@@ -173,10 +215,13 @@ export class Cowboy {
|
|
|
173
215
|
* @param {CloudflareEvent} event The Cloudflare event context passed
|
|
174
216
|
* @param {Object} env Environment variables
|
|
175
217
|
* @param {CloudflareContext} ctx The Cloudflare context to respond to
|
|
218
|
+
*
|
|
219
|
+
* @returns {Cowboy} This chainable Cowboy router instance
|
|
176
220
|
*/
|
|
177
221
|
scheduled(event, env, ctx) {
|
|
178
222
|
if (!this.schedule.handler) throw new Error('Attemped to access Cowboy.scheduled without first calling .schedule() to set something up!');
|
|
179
|
-
|
|
223
|
+
this.schedule.handler.call(this, event, env, ctx);
|
|
224
|
+
return this;
|
|
180
225
|
}
|
|
181
226
|
|
|
182
227
|
|
|
@@ -185,7 +230,7 @@ export class Cowboy {
|
|
|
185
230
|
* This function exists as an easier way to remap body contents without
|
|
186
231
|
*
|
|
187
232
|
* @param {String} url The URL path to call
|
|
188
|
-
* @param {Object}
|
|
233
|
+
* @param {Object} options Additional request options
|
|
189
234
|
* @param {Object} env The environment object to use
|
|
190
235
|
*
|
|
191
236
|
* @returns {CloudflareResponse} The returned CloudflareResponse object
|
|
@@ -287,7 +332,7 @@ export class Cowboy {
|
|
|
287
332
|
/**
|
|
288
333
|
* Handle cron job scheduling
|
|
289
334
|
*
|
|
290
|
-
* @param {Function}
|
|
335
|
+
* @param {Function} handler The callback to install for all scheduled events. Called as `(event:CloudflareEvent, env:Object, ctx:CloudflareContext)`
|
|
291
336
|
* @returns {Cowboy} This chainable Cowboy router instance
|
|
292
337
|
*/
|
|
293
338
|
schedule(handler) {
|
|
@@ -306,10 +351,6 @@ export class Cowboy {
|
|
|
306
351
|
if (this.doneInit) return this; // Already completed init
|
|
307
352
|
debug('INIT!');
|
|
308
353
|
|
|
309
|
-
if (this.settings.patchAxios) {
|
|
310
|
-
// TODO: Patch Axios somehow
|
|
311
|
-
// axios.defaults.adapter = axiosFetchAdapter;
|
|
312
|
-
}
|
|
313
354
|
return this;
|
|
314
355
|
}
|
|
315
356
|
}
|
|
@@ -317,6 +358,9 @@ export class Cowboy {
|
|
|
317
358
|
|
|
318
359
|
/**
|
|
319
360
|
* Wrap an incoming Wrangler request
|
|
361
|
+
*
|
|
362
|
+
* @param {Object} [options] Additional initalization options to use in the Constructor
|
|
363
|
+
*
|
|
320
364
|
* @returns {Object} A Wrangler compatible object
|
|
321
365
|
*/
|
|
322
366
|
export default function cowboy(options) {
|
package/lib/debug.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrapper around the debug instance
|
|
3
|
+
* This function will only output when cowboy is in debug mode
|
|
4
|
+
*
|
|
5
|
+
* @param {*...} msg Message component to show
|
|
6
|
+
*/
|
|
1
7
|
export default function CowboyDebug(...msg) {
|
|
2
8
|
if (!CowboyDebug.enabled) return;
|
|
3
9
|
console.log('COWBOY-DEBUG', ...msg.map(m =>
|
package/lib/request.js
CHANGED
package/lib/response.js
CHANGED
|
@@ -52,6 +52,7 @@ export default class CowboyResponse {
|
|
|
52
52
|
send(data, end = true) {
|
|
53
53
|
if (this.code === null) this.code = 200; // Assume OK if not told otherwise
|
|
54
54
|
|
|
55
|
+
// eslint-disable-next-line unicorn/prefer-ternary
|
|
55
56
|
if (
|
|
56
57
|
typeof data == 'string'
|
|
57
58
|
|| data instanceof FormData
|
|
@@ -125,7 +126,7 @@ export default class CowboyResponse {
|
|
|
125
126
|
console.log('Response', JSON.stringify({
|
|
126
127
|
...cfOptions,
|
|
127
128
|
body:
|
|
128
|
-
typeof this.body == 'string' && this.body.length > 30 ? this.body.substr(0, 50) + '…'
|
|
129
|
+
typeof this.body == 'string' && this.body.length > 30 ? this.body.substr(0, 50) + '…' // eslint-disable-line unicorn/prefer-string-slice
|
|
129
130
|
: this.body,
|
|
130
131
|
}, null, '\t'));
|
|
131
132
|
return new this.CloudflareResponse(this.body, cfOptions);
|
package/lib/testkit.js
CHANGED
|
@@ -19,6 +19,7 @@ export let worker;
|
|
|
19
19
|
* @param {Axios} [options.axios] Axios instance to mutate with the base URL, if specified
|
|
20
20
|
* @param {Boolean} [options.server=true] Initialize a local server - disable this if you're running your own
|
|
21
21
|
* @param {Boolean} [options.debug=false] Force debug as if `DEBUG=cowboy` was set
|
|
22
|
+
* @param {Boolean} [options.scheduler=false] Bind any scedhuler callback to `/__scheduled`
|
|
22
23
|
* @param {Function} [options.logOutput] Function to wrap STDOUT output. Called as `(line:String)`
|
|
23
24
|
* @param {Function} [options.logOutputErr] Function to wrap STDERR output. Called as `(line:String)`
|
|
24
25
|
* @param {String} [options.host='127.0.0.1'] Host to run Wrangler on
|
|
@@ -32,6 +33,7 @@ export function start(options) {
|
|
|
32
33
|
axios: null,
|
|
33
34
|
server: true,
|
|
34
35
|
debug: false,
|
|
36
|
+
scheduler: false,
|
|
35
37
|
logOutput: output => console.log('WRANGLER>', output),
|
|
36
38
|
logOutputErr: output => console.log('WRANGLER!', output),
|
|
37
39
|
host: '127.0.0.1',
|
|
@@ -70,6 +72,9 @@ export function start(options) {
|
|
|
70
72
|
...(debug.enabled ? [
|
|
71
73
|
'--var=COWBOY_DEBUG:1'
|
|
72
74
|
]: []),
|
|
75
|
+
...(settings.scheduler ? [
|
|
76
|
+
'--var=COWBOY_SCHEDULER:1'
|
|
77
|
+
]: []),
|
|
73
78
|
]);
|
|
74
79
|
|
|
75
80
|
worker.stdout.on('data', data => {
|
|
@@ -109,7 +114,6 @@ export function start(options) {
|
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
116
|
* Stop background wrangler instances
|
|
112
|
-
* @returns {Promise} A promise which resolves when the operation has completed
|
|
113
117
|
*/
|
|
114
118
|
export function stop() {
|
|
115
119
|
if (!worker) return; // Worker not active anyway
|
|
@@ -125,11 +129,13 @@ export function stop() {
|
|
|
125
129
|
*/
|
|
126
130
|
export function cowboyMocha(options) {
|
|
127
131
|
|
|
132
|
+
// eslint-disable-next-line no-undef
|
|
128
133
|
before('start cowboy/testkit', function() {
|
|
129
134
|
this.timeout(30 * 1000);
|
|
130
135
|
return start(options);
|
|
131
136
|
});
|
|
132
137
|
|
|
138
|
+
// eslint-disable-next-line no-undef
|
|
133
139
|
after('stop cowboy/testkit', function() {
|
|
134
140
|
this.timeout(5 * 1000);
|
|
135
141
|
return stop();
|
package/middleware/cors.js
CHANGED
|
@@ -2,26 +2,30 @@
|
|
|
2
2
|
* Register a generic middleware to handle CORS requests
|
|
3
3
|
*
|
|
4
4
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
5
|
-
* @param {Boolean} [options.attachOptions=true]
|
|
6
|
-
* @param {
|
|
5
|
+
* @param {Boolean} [options.attachOptions=true] Automatically attach an `OPTIONS` method against all routes that don't already have one to pass the CORS pre-flight check
|
|
6
|
+
* @param {String} [options.origin='*'] Origin URL to allow
|
|
7
|
+
* @param {String} [options.headers='*'] Headers to allow
|
|
8
|
+
* @param {Array<String>} [options.methods=['GET','POST','OPTIONS']] Allowable HTTP methods to add CORS to
|
|
7
9
|
*
|
|
8
10
|
* @returns {CowboyMiddleware}
|
|
9
11
|
*/
|
|
10
12
|
export default function CowboyMiddlewareCORS(options) {
|
|
11
13
|
let settings = {
|
|
12
14
|
attachOptions: true,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'Access-Control-Allow-Headers': '*',
|
|
17
|
-
'Content-Type': 'application/json;charset=UTF-8',
|
|
18
|
-
},
|
|
15
|
+
origin: '*',
|
|
16
|
+
headers: '*',
|
|
17
|
+
methods: ['GET', 'POST', 'OPTIONS'],
|
|
19
18
|
...options,
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
return (req, res) => {
|
|
23
22
|
// Always inject CORS headers
|
|
24
|
-
res.set(
|
|
23
|
+
res.set({
|
|
24
|
+
'Access-Control-Allow-Origin': settings.origin,
|
|
25
|
+
'Access-Control-Allow-Methods': settings.methods.join(', '),
|
|
26
|
+
'Access-Control-Allow-Headers': settings.headers,
|
|
27
|
+
'Content-Type': 'application/json;charset=UTF-8',
|
|
28
|
+
});
|
|
25
29
|
|
|
26
30
|
// Inject various OPTIONS endpoints for CORS pre-flight
|
|
27
31
|
if (settings.attachOptions && !req.router.loadedCors) {
|
package/middleware/devOnly.js
CHANGED
package/middleware/parseJwt.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
5
5
|
* @param {Function} [options.isJwt] Async function, called as `(req, res)` to determine if the input is a JWT payload, defaults to checking the content-type header
|
|
6
|
+
*
|
|
7
|
+
* @returns {CowboyMiddleware}
|
|
6
8
|
*/
|
|
7
9
|
export default function CowboyMiddlewareParseJwt(options) {
|
|
8
10
|
let settings = {
|
|
@@ -13,7 +15,7 @@ export default function CowboyMiddlewareParseJwt(options) {
|
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
return async (req, res) => {
|
|
16
|
-
if (await
|
|
18
|
+
if (!(await settings.isJwt(req, res))) return;
|
|
17
19
|
|
|
18
20
|
const base64 = req.text.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
19
21
|
req.body = JSON.parse(atob(base64));
|
package/middleware/validate.js
CHANGED
|
@@ -4,7 +4,7 @@ import joyful from '@momsfriendlydevco/joyful';
|
|
|
4
4
|
* Run a Joi / Joyful validation function against a specific subkey within `req
|
|
5
5
|
*
|
|
6
6
|
* @param {String} subkey The subkey to run against
|
|
7
|
-
* @param {Function|Object} Callback to use with Joyful to validate with
|
|
7
|
+
* @param {Function|Object} validator Callback to use with Joyful to validate with
|
|
8
8
|
* @returns {Void} Either a successful middleware cycle (if validation succeeds) or a call to `res.status(400)` if failed
|
|
9
9
|
*/
|
|
10
10
|
export default function CowboyMiddlewareValidate(subkey, validator) {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import CowboyMiddlewareValidate from '#middleware/validate';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Shorthand middleware to apply validation to the request body (`req.body`) object
|
|
5
|
+
*
|
|
6
|
+
* @param {Function|Object} validator Callback to use with Joyful to validate with
|
|
7
|
+
* @returns {CowboyMiddleware}
|
|
8
|
+
*/
|
|
3
9
|
export default function CowboyMiddlewareValidateBody(validator) {
|
|
4
10
|
return CowboyMiddlewareValidate('body', validator);
|
|
5
11
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import CowboyMiddlewareValidate from '#middleware/validate';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Shorthand middleware to apply validation to the headers (`req.headers`) object
|
|
5
|
+
*
|
|
6
|
+
* @param {Function|Object} validator Callback to use with Joyful to validate with
|
|
7
|
+
* @returns {CowboyMiddleware}
|
|
8
|
+
*/
|
|
3
9
|
export default function CowboyMiddlewareValidateHeaders(validator) {
|
|
4
10
|
return CowboyMiddlewareValidate('headers', validator);
|
|
5
11
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import CowboyMiddlewareValidate from '#middleware/validate';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Shorthand middleware to apply validation to the parameters (`req.params`) object
|
|
5
|
+
*
|
|
6
|
+
* @param {Function|Object} validator Callback to use with Joyful to validate with
|
|
7
|
+
* @returns {CowboyMiddleware}
|
|
8
|
+
*/
|
|
3
9
|
export default function CowboyMiddlewareValidateParams(validator) {
|
|
4
10
|
return CowboyMiddlewareValidate('params', validator);
|
|
5
11
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import CowboyMiddlewareValidate from '#middleware/validate';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Shorthand middleware to apply validation to the query (`req.query`) object
|
|
5
|
+
*
|
|
6
|
+
* @param {Function|Object} validator Callback to use with Joyful to validate with
|
|
7
|
+
* @returns {CowboyMiddleware}
|
|
8
|
+
*/
|
|
3
9
|
export default function CowboyMiddlewareValidateQuery(validator) {
|
|
4
10
|
return CowboyMiddlewareValidate('query', validator);
|
|
5
11
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momsfriendlydevco/cowboy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Wrapper around Cloudflare Wrangler to provide a more Express-like experience",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"lint": "eslint
|
|
6
|
+
"lint": "eslint"
|
|
7
7
|
},
|
|
8
8
|
"keywords": [
|
|
9
9
|
"wrangler"
|
|
@@ -40,18 +40,7 @@
|
|
|
40
40
|
"toml": "^3.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@momsfriendlydevco/eslint-config": "^
|
|
44
|
-
"eslint": "^
|
|
45
|
-
},
|
|
46
|
-
"eslintConfig": {
|
|
47
|
-
"extends": "@momsfriendlydevco",
|
|
48
|
-
"env": {
|
|
49
|
-
"es6": true,
|
|
50
|
-
"node": true
|
|
51
|
-
},
|
|
52
|
-
"parserOptions": {
|
|
53
|
-
"ecmaVersion": 13,
|
|
54
|
-
"sourceType": "module"
|
|
55
|
-
}
|
|
43
|
+
"@momsfriendlydevco/eslint-config": "^2.3.1",
|
|
44
|
+
"eslint": "^9.33.0"
|
|
56
45
|
}
|
|
57
46
|
}
|