@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.
@@ -0,0 +1,10 @@
1
+ import {defineConfig, globalIgnores} from "eslint/config";
2
+ import RulesMFDC from '@momsfriendlydevco/eslint-config';
3
+
4
+ export default defineConfig([
5
+ globalIgnores([
6
+ '.*',
7
+ 'node_modules/',
8
+ ]),
9
+ ...RulesMFDC,
10
+ ])
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
- patchAxios: true,
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... Middleware to call in sequence
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
- * @param {CloudflareRequest} req The incoming request
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
- return this.schedule.handler.call(this, event, env, ctx);
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} request Additional request options
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} cb The callback to install for all scheduled events. Called as `(event:CloudflareEvent, env:Object, ctx:CloudflareContext)`
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
@@ -2,7 +2,7 @@ import debug from '#lib/debug';
2
2
 
3
3
  /**
4
4
  * Tiny wrapper around Wrangler to wrap its default Request object in an Express-like structure
5
- * @extends CloudflareRequest
5
+ * @augments {CloudflareRequest}
6
6
  */
7
7
  export default class CowboyRequest {
8
8
  /**
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();
@@ -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] Attach an `options` method against all routes that don't already have one to pass the CORS pre-flight check
6
- * @param {Object} [options.headers] Generic CORS headers to inject
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
- headers: {
14
- 'Access-Control-Allow-Origin': '*',
15
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
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(settings.headers);
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) {
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export default function CowboyMiddlewareDevOnly() {
8
8
  return (req, res, env) => {
9
- const isDev = self.ENVIRONMENT === 'development';
9
+ const isDev = env.ENVIRONMENT === 'development';
10
10
 
11
11
  if (!isDev) return res
12
12
  .status(403)
@@ -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 !settings.isJwt(req, res)) return;
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));
@@ -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.0",
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": "^1.0.7",
44
- "eslint": "^8.51.0"
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
  }