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 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 { asInteger, getOrThrow } from "bootpress/helpers";
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 = asInteger(idInParams);
27
- return getOrThrow(this.users.find(user => user == id), new HttpError(404, "Not Found"));
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 { asInteger, asSchema, getOrThrow } from "bootpress/helpers";
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: number | string) {
45
- console.log("looking for " + id);
44
+ findById(id: string) {
46
45
  return getOrThrow(
47
- this.posts.find(p => p == id),
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 = asSchema(body, {
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(asInteger(idInQuery)) : -1;
58
+ const idx = deleteInQuery === "yes" ? this.posts.indexOf(as(idInQuery, "integer")) : -1;
60
59
  if (idx > -1) {
61
- this.posts.splice(idx, 1);
62
- this.#printDeleted(idInQuery);
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
- #printDeleted(id: number | string) {
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
@@ -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, schema: T): T extends ExtendedTypeKeys ? ExtValOf<T> : TypedSchema<T>;
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 not`;
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 asArrayOf(o, elementType) {
128
+ function asPrimitiveArrayOf(o, elementType) {
121
129
  if (Array.isArray(o)) {
122
130
  for (let i = 0; i < o.length; i++) {
123
- if (elementType === "integer") {
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 == "string") {
140
+ if (typeof type === "string") {
137
141
  if (type.endsWith("[]")) {
138
142
  // array check
139
143
  const elementType = type.replace("[]", "");
140
- return asArrayOf(o, elementType);
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 == "object" && type != null) {
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 PassBody(actualHandler) {
159
- return (...args) => {
160
- if (isRequstHandlerArgs(args)) {
161
- const req = args.at(-3); const res = args.at(-2);
162
- return actualHandler(req.body)(req, res);
163
- } else {
164
- return (req, res) => actualHandler(...args, req.body)(req, res)
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(as(req.body, type))(req, res);
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, as(req.body, type))(req, res);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bootpress",
3
- "version": "6.0.2",
3
+ "version": "7.1.0",
4
4
  "description": "REST service methods for express",
5
5
  "main": "index.js",
6
6
  "repository": {