bootpress 6.0.2 → 7.1.0
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 +36 -13
- package/helpers/index.d.ts +3 -7
- package/helpers/index.js +82 -23
- package/index.d.ts +2 -0
- package/index.js +48 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Recommended tool: [create-bootpress-app](https://www.npmjs.com/package/create-bo
|
|
|
12
12
|
```ts
|
|
13
13
|
import express from "express";
|
|
14
14
|
import { HttpError, PassParams, RestService } from "bootpress";
|
|
15
|
-
import {
|
|
15
|
+
import { as, getOrThrow } from "bootpress/helpers";
|
|
16
16
|
|
|
17
17
|
const app = express();
|
|
18
18
|
app.use(express.json());
|
|
@@ -23,8 +23,8 @@ const UserServiceImpl = {
|
|
|
23
23
|
return this.users;
|
|
24
24
|
},
|
|
25
25
|
findUserById(idInParams: string) {
|
|
26
|
-
const id =
|
|
27
|
-
return getOrThrow(this.users.find(user => user
|
|
26
|
+
const id = as(idInParams, "integer");
|
|
27
|
+
return getOrThrow(this.users.find(user => user === id), new HttpError(404, "Not Found"));
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -37,33 +37,34 @@ app.get("/users/:id", PassParams("id")(UserService.findUserById));
|
|
|
37
37
|
#### Advanced usage:
|
|
38
38
|
```ts
|
|
39
39
|
import { HttpError, HttpResponse, PassBody, PassParams, PassQueries, RestService } from "bootpress";
|
|
40
|
-
import {
|
|
40
|
+
import { as, asStrict, getOrThrow } from "bootpress/helpers";
|
|
41
41
|
|
|
42
42
|
class PostServiceImpl {
|
|
43
43
|
posts = [1, 2, 3, 4, 5];
|
|
44
|
-
findById(id:
|
|
45
|
-
console.log("looking for " + id);
|
|
44
|
+
findById(id: string) {
|
|
46
45
|
return getOrThrow(
|
|
47
|
-
this.posts.find(p => p
|
|
46
|
+
this.posts.find(p => p === as(id, "integer")),
|
|
48
47
|
new HttpError(404, "Post is not found")
|
|
49
48
|
);
|
|
50
49
|
}
|
|
51
50
|
add(body: any) {
|
|
52
|
-
let casted =
|
|
51
|
+
let casted = asStrict(body, {
|
|
53
52
|
"id": "number"
|
|
54
53
|
});
|
|
55
54
|
this.posts.push(casted.id);
|
|
56
55
|
return new HttpResponse(201, casted.id);
|
|
57
56
|
}
|
|
58
57
|
delete(deleteInQuery: string, idInQuery: string) {
|
|
59
|
-
const idx = deleteInQuery === "yes" ? this.posts.indexOf(
|
|
58
|
+
const idx = deleteInQuery === "yes" ? this.posts.indexOf(as(idInQuery, "integer")) : -1;
|
|
60
59
|
if (idx > -1) {
|
|
61
|
-
this
|
|
62
|
-
this
|
|
60
|
+
this.#logDeleted(idInQuery);
|
|
61
|
+
return this.posts.splice(idx, 1);
|
|
62
|
+
}else {
|
|
63
|
+
throw new HttpError(404, "Post is not found")
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
// use private methods to
|
|
66
|
-
#
|
|
67
|
+
#logDeleted(id: number | string) {
|
|
67
68
|
console.warn(`post ${id} is deleted`)
|
|
68
69
|
}
|
|
69
70
|
findAll() {
|
|
@@ -126,4 +127,26 @@ class LogServiceImpl {
|
|
|
126
127
|
const LogService = new LogServiceImpl();
|
|
127
128
|
|
|
128
129
|
app.get("/logs", LogService.findAll() as RequestHandler)
|
|
129
|
-
```
|
|
130
|
+
```
|
|
131
|
+
## v7.1.0 Release Notes:
|
|
132
|
+
- getOrThrow: Throws specified error when value is an empty array too.
|
|
133
|
+
## v7.0.0 Release Notes:
|
|
134
|
+
|
|
135
|
+
### Deprecated helper methods:
|
|
136
|
+
- asSchema
|
|
137
|
+
- asString
|
|
138
|
+
- asBoolean
|
|
139
|
+
- asInteger
|
|
140
|
+
- asNumber
|
|
141
|
+
|
|
142
|
+
Please use "as" or "asStrict" instead of these functions. For example:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
//const x: string = asString(o); // deprecated
|
|
146
|
+
const x: string = as(o, "string");
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Added / Changed helper methods:
|
|
150
|
+
- asStrict : Asserts types strictly
|
|
151
|
+
- PassBodyAs(schema): Body must be as same as schema
|
|
152
|
+
- ParseBodyAs(schema): Body have to be parsable to schema
|
package/helpers/index.d.ts
CHANGED
|
@@ -31,12 +31,7 @@ type TypedSchema<T> = {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function getOrThrow<T, E extends HttpError>(data: T, error: E): T;
|
|
34
|
-
export function getOrElse<T, E>(data: T, defaultValue: E): T | E;
|
|
35
|
-
export function asBoolean(o: any): boolean;
|
|
36
|
-
export function asNumber(o: any): number;
|
|
37
|
-
export function asInteger(o: any): number;
|
|
38
|
-
export function asString(o: any): string;
|
|
39
|
-
export function asSchema<T extends JsSchema>(o: any, jsSchema: T): TypedSchema<T>;
|
|
34
|
+
export function getOrElse<T, E>(data: T, defaultValue: E): E extends NonNullable<infer T> ? E : T | E;
|
|
40
35
|
export function schema<T extends JsSchema>(schema: T): T;
|
|
41
36
|
|
|
42
37
|
type ExtendedTypeMap = {
|
|
@@ -60,4 +55,5 @@ type ExtendedTypeMap = {
|
|
|
60
55
|
|
|
61
56
|
type ExtendedTypeKeys = keyof ExtendedTypeMap;
|
|
62
57
|
type ExtValOf<T extends ExtendedTypeKeys> = ExtendedTypeMap[T]
|
|
63
|
-
export function as<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o:any,
|
|
58
|
+
export function as<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o:any, type: T, errorVariableName?: string): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
|
|
59
|
+
export function asStrict<T extends (ExtendedTypeKeys | JsSchema | ArraySchema)>(o:any, type: T, errorVariableName?: string): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
|
package/helpers/index.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
const { HttpError } = require("../types");
|
|
2
2
|
|
|
3
|
+
const allowedPrimitives = ["string", "number", "boolean", "integer"];
|
|
4
|
+
|
|
3
5
|
function getOrThrow(data, error) {
|
|
4
|
-
if (data === null || data === undefined) {
|
|
6
|
+
if (data === null || data === undefined || isEmptyArray(data)) {
|
|
5
7
|
throw error;
|
|
6
8
|
} else {
|
|
7
9
|
return data;
|
|
8
10
|
}
|
|
9
11
|
}
|
|
10
12
|
|
|
13
|
+
function isEmptyArray(x){
|
|
14
|
+
return Array.isArray(x) && x.length < 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
function getOrElse(data, defaultValue) {
|
|
12
18
|
if (data === null || data === undefined) {
|
|
13
19
|
return defaultValue;
|
|
@@ -68,7 +74,7 @@ function asInteger(o, errorMessage = undefined, errorStatus = 400) {
|
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
function asString(o, errorMessage = undefined, errorStatus = 400) {
|
|
71
|
-
errorMessage = errorMessage ?? `Value ${o} should have been a string but it's
|
|
77
|
+
errorMessage = errorMessage ?? `Value ${o} should have been a string but it's ${typeof o}`;
|
|
72
78
|
const validTypes = ["string", "number"];
|
|
73
79
|
if (validTypes.includes(typeof o)) {
|
|
74
80
|
return '' + o;
|
|
@@ -77,7 +83,10 @@ function asString(o, errorMessage = undefined, errorStatus = 400) {
|
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
function asSchema(o, schema) {
|
|
86
|
+
function asSchema(o, schema, strict = false) {
|
|
87
|
+
if(!(schema instanceof Object) || Array.isArray(schema)){
|
|
88
|
+
throw new HttpError(500, `Schema is not valid ${JSON.stringify(schema)}`);
|
|
89
|
+
}
|
|
81
90
|
const schemaKeyValues = Object.entries(schema);
|
|
82
91
|
let result = {};
|
|
83
92
|
for (let i = 0; i < schemaKeyValues.length; i++) {
|
|
@@ -90,20 +99,19 @@ function asSchema(o, schema) {
|
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
const expectedType = schemaKeyValues[i][1];
|
|
93
|
-
// const errorMessage = o[key] == null ? `Value of ${key} should have been a ${expectedType} but it's null` : `Value of ${key} should have been a ${expectedType} but it's a ${typeof o[key]}`;
|
|
94
102
|
|
|
95
103
|
if (typeof expectedType === "object") {
|
|
96
104
|
if (Array.isArray(expectedType)) {
|
|
97
105
|
result[key] = [];
|
|
98
106
|
for (let j = 0; j < o[key].length; j++) {
|
|
99
|
-
result[key][j] = asSchema(o[key][j], expectedType[0])
|
|
107
|
+
result[key][j] = asSchema(o[key][j], expectedType[0], strict)
|
|
100
108
|
}
|
|
101
109
|
} else {
|
|
102
|
-
result[key] = asSchema(o[key], expectedType);
|
|
110
|
+
result[key] = asSchema(o[key], expectedType, strict);
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
else if (typeof expectedType === "string") {
|
|
106
|
-
result[key] = as(o[key], expectedType, key);
|
|
114
|
+
result[key] = strict ? asStrict(o[key], expectedType, key) : as(o[key], expectedType, key);
|
|
107
115
|
}
|
|
108
116
|
else {
|
|
109
117
|
throw new HttpError(500, `Type of a schema key should be a primitive type or another schema`);
|
|
@@ -117,14 +125,10 @@ function schema(schema) {
|
|
|
117
125
|
return schema;
|
|
118
126
|
}
|
|
119
127
|
|
|
120
|
-
function
|
|
128
|
+
function asPrimitiveArrayOf(o, elementType) {
|
|
121
129
|
if (Array.isArray(o)) {
|
|
122
130
|
for (let i = 0; i < o.length; i++) {
|
|
123
|
-
|
|
124
|
-
asInteger(o[i]);
|
|
125
|
-
} else if (typeof o[i] != elementType) {
|
|
126
|
-
throw new HttpError(400, `Each element in array should have been a ${elementType} but ${o[i]} is present with type ${typeof o[i]}`);
|
|
127
|
-
}
|
|
131
|
+
o[i] = checkPrimitive(o[i], elementType, "Array elemet");
|
|
128
132
|
}
|
|
129
133
|
return o;
|
|
130
134
|
} else {
|
|
@@ -133,17 +137,17 @@ function asArrayOf(o, elementType) {
|
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
function as(o, type, namedErrorVariable = o) {
|
|
136
|
-
if (typeof type
|
|
140
|
+
if (typeof type === "string") {
|
|
137
141
|
if (type.endsWith("[]")) {
|
|
138
142
|
// array check
|
|
139
143
|
const elementType = type.replace("[]", "");
|
|
140
|
-
return
|
|
144
|
+
return asPrimitiveArrayOf(o, elementType);
|
|
141
145
|
} else { // non array types:
|
|
142
146
|
if (type.endsWith("?") && o == null) {
|
|
143
147
|
return null;
|
|
144
148
|
} else if (type.endsWith("?") && o != null) {
|
|
145
149
|
const actualType = type.replace("?", "");
|
|
146
|
-
return as(o, actualType);
|
|
150
|
+
return as(o, actualType, namedErrorVariable);
|
|
147
151
|
}
|
|
148
152
|
// primitive check
|
|
149
153
|
switch (type) {
|
|
@@ -159,7 +163,7 @@ function as(o, type, namedErrorVariable = o) {
|
|
|
159
163
|
throw new HttpError(500, `Unsupported type ${type}`);
|
|
160
164
|
}
|
|
161
165
|
}
|
|
162
|
-
} else if (typeof type
|
|
166
|
+
} else if (typeof type === "object" && type != null) {
|
|
163
167
|
if (Array.isArray(type)) {
|
|
164
168
|
if (type.length > 1) {
|
|
165
169
|
throw new HttpError(500, `You can define only one schema for types ArrayOf<Schema>`);
|
|
@@ -185,14 +189,69 @@ function as(o, type, namedErrorVariable = o) {
|
|
|
185
189
|
}
|
|
186
190
|
}
|
|
187
191
|
|
|
192
|
+
function asStrict(o, type, namedErrorVariable = o) {
|
|
193
|
+
if (typeof type === "string") {
|
|
194
|
+
if (type.endsWith("[]")) {
|
|
195
|
+
// array check
|
|
196
|
+
const elementType = type.replace("[]", "");
|
|
197
|
+
return asPrimitiveArrayOf(o, elementType);
|
|
198
|
+
} else { // non array types:
|
|
199
|
+
if (type.endsWith("?") && o == null) {
|
|
200
|
+
return null;
|
|
201
|
+
} else if (type.endsWith("?") && o != null) {
|
|
202
|
+
const actualType = type.replace("?", "");
|
|
203
|
+
return asStrict(o, actualType, namedErrorVariable);
|
|
204
|
+
}
|
|
205
|
+
// primitive check
|
|
206
|
+
if (!allowedPrimitives.includes(type)) {
|
|
207
|
+
throw new HttpError(500, `Unsupported type ${type}`);
|
|
208
|
+
}
|
|
209
|
+
return checkPrimitive(o, type, namedErrorVariable);
|
|
210
|
+
}
|
|
211
|
+
} else if (typeof type === "object" && type != null) {
|
|
212
|
+
if (Array.isArray(type)) {
|
|
213
|
+
if (type.length > 1) {
|
|
214
|
+
throw new HttpError(500, `You can define only one schema for types ArrayOf<Schema>`);
|
|
215
|
+
} else if (type.length < 1) {
|
|
216
|
+
throw new HttpError(500, `You must define a schema for types ArrayOf<Schema>`);
|
|
217
|
+
}
|
|
218
|
+
// array schema validation
|
|
219
|
+
if (!Array.isArray(o)) {
|
|
220
|
+
throw new HttpError(400, `Provided value should have been an array but it's ${typeof o}`)
|
|
221
|
+
}
|
|
222
|
+
const providedSchema = type[0];
|
|
223
|
+
let result = [];
|
|
224
|
+
for (let i = 0; i < o.length; i++) {
|
|
225
|
+
result.push(asSchema(o[i], providedSchema, true));
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
} else {
|
|
229
|
+
// schema validation
|
|
230
|
+
return asSchema(o, type, true);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
throw new HttpError(500, `Unsupported type check ${type}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function checkPrimitive(o, type, varName = o) {
|
|
238
|
+
const error = new HttpError(400, `${varName} should have been a ${type}. But it's ${typeof o}`);
|
|
239
|
+
if (type === "integer") {
|
|
240
|
+
if (!Number.isInteger(o)) {
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
return o;
|
|
244
|
+
}
|
|
245
|
+
if (typeof o !== type) {
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
return o;
|
|
249
|
+
}
|
|
250
|
+
|
|
188
251
|
module.exports = {
|
|
189
252
|
getOrThrow,
|
|
190
253
|
getOrElse,
|
|
191
|
-
asBoolean,
|
|
192
|
-
asNumber,
|
|
193
|
-
asInteger,
|
|
194
|
-
asString,
|
|
195
|
-
asSchema,
|
|
196
254
|
schema,
|
|
197
|
-
as
|
|
255
|
+
as,
|
|
256
|
+
asStrict
|
|
198
257
|
}
|
package/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ declare function RestMethod<T>(callback: () => T): RequestHandler;
|
|
|
16
16
|
declare function Restify(target: any, key: string, desc: PropertyDescriptor): PropertyDescriptor;
|
|
17
17
|
|
|
18
18
|
declare function PassBody(serviceFunction: RequestHandler | RequsetHandlerWithArgs): RequestHandler
|
|
19
|
+
declare function ParseBodyAs(type: ValidTypeKeys | JsSchema | ArraySchema ): (serviceFunction: RequestHandler | RequsetHandlerWithArgs) => RequestHandler
|
|
19
20
|
declare function PassBodyAs(type: ValidTypeKeys | JsSchema | ArraySchema ): (serviceFunction: RequestHandler | RequsetHandlerWithArgs) => RequestHandler
|
|
20
21
|
declare function PassAllParams(serviceFunction: RequestHandler | RequsetHandlerWithArgs): RequestHandler
|
|
21
22
|
declare function PassAllQueries(serviceFunction: RequestHandler | RequsetHandlerWithArgs): RequestHandler
|
|
@@ -38,6 +39,7 @@ export {
|
|
|
38
39
|
PassAllCookies,
|
|
39
40
|
PassBody,
|
|
40
41
|
PassBodyAs,
|
|
42
|
+
ParseBodyAs,
|
|
41
43
|
PassRequest,
|
|
42
44
|
PassResponse
|
|
43
45
|
}
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { as } = require("./helpers");
|
|
1
|
+
const { as, asStrict } = require("./helpers");
|
|
2
2
|
const { HttpError } = require("./types");
|
|
3
3
|
|
|
4
4
|
const protectedProperties = [
|
|
@@ -155,13 +155,26 @@ function PassAllQueries(actualHandler) {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
function
|
|
159
|
-
return (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
function ParseBodyAs(type) {
|
|
159
|
+
return (actualHandler) => {
|
|
160
|
+
return (...args) => {
|
|
161
|
+
if (isRequstHandlerArgs(args)) {
|
|
162
|
+
const req = args.at(-3); const res = args.at(-2);
|
|
163
|
+
try {
|
|
164
|
+
return actualHandler(as(req.body, type))(req, res);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
reply(res, e.status || 500, e.message || e);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
} else {
|
|
170
|
+
return (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
return actualHandler(...args, as(req.body, type))(req, res);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
reply(res, e.status || 500, e.message || e)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
165
178
|
}
|
|
166
179
|
}
|
|
167
180
|
}
|
|
@@ -172,16 +185,16 @@ function PassBodyAs(type) {
|
|
|
172
185
|
if (isRequstHandlerArgs(args)) {
|
|
173
186
|
const req = args.at(-3); const res = args.at(-2);
|
|
174
187
|
try {
|
|
175
|
-
return actualHandler(
|
|
188
|
+
return actualHandler(asStrict(req.body, type))(req, res);
|
|
176
189
|
} catch (e) {
|
|
177
190
|
reply(res, e.status || 500, e.message || e);
|
|
178
191
|
}
|
|
179
192
|
|
|
180
193
|
} else {
|
|
181
194
|
return (req, res) => {
|
|
182
|
-
try{
|
|
183
|
-
return actualHandler(...args,
|
|
184
|
-
}catch(e){
|
|
195
|
+
try {
|
|
196
|
+
return actualHandler(...args, asStrict(req.body, type))(req, res);
|
|
197
|
+
} catch (e) {
|
|
185
198
|
reply(res, e.status || 500, e.message || e)
|
|
186
199
|
}
|
|
187
200
|
}
|
|
@@ -190,6 +203,28 @@ function PassBodyAs(type) {
|
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
205
|
|
|
206
|
+
function PassBody(actualHandler) {
|
|
207
|
+
return (...args) => {
|
|
208
|
+
if (isRequstHandlerArgs(args)) {
|
|
209
|
+
const req = args.at(-3); const res = args.at(-2);
|
|
210
|
+
try {
|
|
211
|
+
return actualHandler(req.body)(req, res);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
reply(res, e.status || 500, e.message || e);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
} else {
|
|
217
|
+
return (req, res) => {
|
|
218
|
+
try {
|
|
219
|
+
return actualHandler(...args, req.body)(req, res);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
reply(res, e.status || 500, e.message || e)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
193
228
|
function PassRequest(actualHandler) {
|
|
194
229
|
return (...args) => {
|
|
195
230
|
if (isRequstHandlerArgs(args)) {
|
|
@@ -249,6 +284,7 @@ module.exports = {
|
|
|
249
284
|
PassCookies,
|
|
250
285
|
PassBody,
|
|
251
286
|
PassBodyAs,
|
|
287
|
+
ParseBodyAs,
|
|
252
288
|
PassRequest,
|
|
253
289
|
PassResponse
|
|
254
290
|
}
|