@odatnurd/cf-requests 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -78,17 +78,115 @@ export const $post = routeHandler(
78
78
  );
79
79
  ```
80
80
 
81
+
82
+ ## Testing Utilities (Optional)
83
+
84
+ This package includes an optional set of helpers to facilitate testing your own
85
+ projects with the [Aegis](https://www.npmjs.com/package/@axel669/aegis) test
86
+ runner.
87
+
88
+ To use these utilities, you must install the required peer dependencies into
89
+ your own project's `devDependencies` if you have not already done so.
90
+
91
+ ```sh
92
+ pnpm add -D @odatnurd/d1-query @axel669/aegis miniflare fs-jetpack
93
+ ```
94
+
95
+ > ℹ️ If you are actively using
96
+ > [@odatnurd/d1-query](https://www.npmjs.com/package/@odatnurd/d1-query) in your
97
+ > project, that library should be installed as a regular `dependency` and not a
98
+ > `devDependency`
99
+
100
+ The `@odatnurd/cf-requests` module exports the following functions:
101
+
102
+
103
+ ### Helper Functions
104
+
105
+ ```javascript
106
+ export function initializeResponseChecks() {}
107
+ ```
108
+ Registers all [custom checks](#custom-checks) with Aegis. This should be called
109
+ once at the top of your `aegis.config.js` file.
110
+
111
+ ---
112
+
113
+ ```javascript
114
+ export async function schemaTest(dataType, schema, data, validator = undefined) {}
115
+ ```
116
+ Takes a `dataType` and `schema` as would be provided to the `validate` function
117
+ and runs the validation to see what the result is. The function will return
118
+ either:
119
+
120
+ * `Valid Data`: An Object that represents the validated and masked data
121
+ * `Invalid Data`: A `Response` object that carries the error payload
122
+
123
+ Using this, it is possible to validate that a schema works as expected without
124
+ having to use it in the actual request first.
125
+
126
+ > ℹ️ By default, the test will use the `validate` function to perform the data
127
+ > validation. If desired, you can pass an optional `validator` function as the
128
+ > final argument. This must take the same arguments as `validate` does, and
129
+ > follow the same contract. This allows for testing of other schema libraries,
130
+ > such as during migrations to this library.
131
+
132
+
133
+ ### Configuration
134
+
135
+ You can import the helper functions into your `aegis.config.js` file to easily
136
+ set up a test environment, optionally also populating one or more SQL files into
137
+ the database first in order to set up testing.
138
+
139
+ **Example `aegis.config.js`:**
140
+
141
+ ```js
142
+ import { initializeCustomChecks, aegisSetup, aegisTeardown } from '@odatnurd/cf-aegis';
143
+ import { initializeResponseChecks } from '@odatnurd/cf-requests/aegis';
144
+
145
+ initializeCustomChecks();
146
+ initializeResponseChecks()
147
+
148
+ export const config = {
149
+ files: [
150
+ "test/**/*.test.js",
151
+ ],
152
+ hooks: {
153
+ async setup(ctx) {
154
+ await aegisSetup(ctx, 'test/setup.sql', 'DB');
155
+ },
156
+
157
+ async teardown(ctx) {
158
+ await aegisTeardown(ctx);
159
+ },
160
+ },
161
+ failAction: "afterSection",
162
+ }
163
+ ```
164
+
165
+
166
+ ### Custom Checks
167
+
168
+ The `initializeResponseChecks()` function registers several custom checks with Aegis
169
+ to simplify testing database-related logic.
170
+
171
+ * `.isResponse($)`: Checks if a value is a `Response` object.
172
+ * `.isNotResponse($)`: Checks if a value is not a `Response` object.
173
+ * `.isResponseWithStatus($, count)`: Checks if an object is a `Response` with a
174
+ specific `status` code.
175
+
176
+
81
177
  ## Methods
82
178
 
83
179
  ```js
84
180
  export function success(ctx, message, result, status) {}
85
181
  ```
86
182
 
87
- Indicate a successful return in JSON with the given `HTTP` status code:
183
+ Indicate a successful return in JSON with the given `HTTP` status code; the
184
+ status code is used to construct the JSON as well as the response:
88
185
 
89
186
  ```js
90
187
  {
91
188
  "success": true,
189
+ status,
92
190
  message,
93
191
  data: result
94
192
  }
package/aegis/index.js ADDED
@@ -0,0 +1,121 @@
1
+ /******************************************************************************/
2
+
3
+
4
+ import { addCheck } from '@axel669/aegis';
5
+ import { validate } from '../lib/handlers.js';
6
+
7
+
8
+ /******************************************************************************/
9
+
10
+
11
+ /*
12
+ * Initializes some custom Aegis checks that make testing of schema and data
13
+ * requests easier.
14
+ *
15
+ * This is entirely optional.
16
+ */
17
+ export function initializeRequestChecks() {
18
+ // Check that a value is a response object from our middleware
19
+ addCheck.value.isResponse(
20
+ source => source instanceof Response
21
+ );
22
+
23
+ // Check that a value is NOT a response object
24
+ addCheck.value.isNotResponse(
25
+ source => (source instanceof Response) === false
26
+ );
27
+
28
+ addCheck.value.isResponseWithStatus(
29
+ (source, status) => source instanceof Response && source.status == status
30
+ );
31
+ }
32
+
33
+
34
+ /******************************************************************************/
35
+
36
+
37
+ /* A helper function to be able to test the schema validation options in the
38
+ * library. This takes a schema object and data type such as you would pass to
39
+ * the validate() function, along with an input data object, and exercises that
40
+ * the schema works as expected.
41
+ *
42
+ * The result of the call is either a JSON object that represents the validated
43
+ * and masked input data if the schema validated the data, or a Response object
44
+ * that carries the failure of the validation. This would be a response of code
45
+ * 400 with a JSON body that carries the actual validation failure message
46
+ * within it. */
47
+ export async function schemaTest(dataType, schema, data, validator) {
48
+ // If a validator is provided, use it; otherwise use ours. This requires that
49
+ // you provide a call-compatible validator. This is here only to support some
50
+ // migrations of old code that is using a different validator than the one
51
+ // this library currently uses.
52
+ validator = validator ??= validate;
53
+
54
+ // Use the Hono factory to create our middleware, just as a caller would.
55
+ // Create a middleware using the Hono factory method for this, using the
56
+ // schema object and data type provided.
57
+ const middleware = validator(dataType, schema);
58
+
59
+ // As a result of the middleware, we will either capture the validated (and
60
+ // masked) input JSON data, or we will capture an error response. As a part of
61
+ // this we also capture what the eventual status of the call would be if this
62
+ // generates a response, so that we can put it into the response object.
63
+ let validData = null;
64
+ let errorResponse = null;
65
+ let responseStatus = 200;
66
+
67
+ // A fake next to pass to the middleware when we execute it, so that it does
68
+ // not throw an error.
69
+ const next = () => {};
70
+
71
+ // In order to run the test we need to create a fake Hono context object to
72
+ // pass to the middleware; this mimics the smallest possible footprint of
73
+ // Hono context for our purposes.
74
+ const ctx = {
75
+ req: {
76
+ // These methods are used by the validator to pull the parsed data out of
77
+ // the request in order to validate it.
78
+ param: () => data,
79
+ json: async () => data,
80
+ query: () => data,
81
+
82
+ // The validator invokes this to get headers out of the request when the
83
+ // data type is JSON.
84
+ header: (name) => {
85
+ if (name.toLowerCase() === 'content-type' && dataType === 'json') {
86
+ return 'application/json';
87
+ }
88
+ return undefined;
89
+ },
90
+
91
+ // When validation succeeds, it invokes this to store the data back into
92
+ // the context.
93
+ addValidatedData: (target, data) => validData = data
94
+ },
95
+
96
+ // Used to capture a failure; the validator will invoke status to set the
97
+ // required HTTP response and then invoke the json() method to populate the
98
+ // error.
99
+ status: (inStatus) => { responseStatus = inStatus; },
100
+ json: (payload) => {
101
+ errorResponse = new Response(
102
+ JSON.stringify(payload), {
103
+ status: responseStatus,
104
+ statusText: "Bad Request",
105
+ headers: { "Content-Type": "application/json" }
106
+ }
107
+ );
108
+ },
109
+ };
110
+
111
+ // Run the middleware; we either capture a result in the error payload or the
112
+ // validation result.
113
+ await middleware(ctx, next);
114
+
115
+ // Return the error payload if validation failed, otherwise return the
116
+ // validated data from the success path.
117
+ return errorResponse ?? validData;
118
+ };
119
+
120
+
121
+ /******************************************************************************/
package/lib/handlers.js CHANGED
@@ -16,7 +16,7 @@ export const success = (ctx, message, result, status) => {
16
16
  result ??= [];
17
17
 
18
18
  ctx.status(status);
19
- return ctx.json({ success: true, message, data: result });
19
+ return ctx.json({ success: true, status, message, data: result });
20
20
  }
21
21
 
22
22
 
@@ -31,7 +31,7 @@ export const fail = (ctx, message, status, result) => {
31
31
  status ??= 400;
32
32
 
33
33
  ctx.status(status);
34
- return ctx.json({ success: false, message, data: result });
34
+ return ctx.json({ success: false, status, message, data: result });
35
35
  }
36
36
 
37
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odatnurd/cf-requests",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Simple Cloudflare Hono request wrapper",
5
5
  "author": "OdatNurd (https://odatnurd.net)",
6
6
  "homepage": "https://github.com/OdatNurd/cf-requests",
@@ -13,15 +13,18 @@
13
13
  "type": "module",
14
14
  "main": "lib/handlers.js",
15
15
  "exports": {
16
- ".": "./lib/handlers.js"
16
+ ".": "./lib/handlers.js",
17
+ "./aegis": "./aegis/index.js"
17
18
  },
18
19
  "files": [
19
- "lib/handlers.js"
20
+ "lib",
21
+ "aegis"
20
22
  ],
21
23
  "keywords": [
22
24
  "cloudflare",
23
25
  "hono",
24
- "routing"
26
+ "routing",
27
+ "aegis"
25
28
  ],
26
29
  "peerDependencies": {
27
30
  "hono": "^4.7.0"