ivl 0.1.0 → 0.1.2
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 +148 -2
- package/lib/helpers/index.js +1 -0
- package/lib/index.d.ts +3 -13
- package/lib/index.js +1 -1
- package/lib/patterns/index.js +1 -0
- package/package.json +16 -1
package/README.md
CHANGED
|
@@ -1,8 +1,154 @@
|
|
|
1
1
|
# Super lightweight input validation library
|
|
2
|
+
This is a lightweight library for user input validation.
|
|
3
|
+
Main focus is on speed and flexibility of the validation rules
|
|
2
4
|
|
|
5
|
+
Supports asynchronous rule validation by default. Use `getInputErrorsSync` and `getSchemaErrorsSync` for better performance if you don't need to support asynchronous checks on your inputs.
|
|
3
6
|
# Main functionality
|
|
4
|
-
|
|
5
|
-
```
|
|
7
|
+
Write your own custom validators to exactly match your usecase using a simple object:
|
|
8
|
+
```javascript
|
|
9
|
+
const my_rules = {
|
|
10
|
+
"Input must be more than 4": (i) => i > 4,
|
|
11
|
+
"Input must be divisible by 10": (i) => i % 10
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
The input will be checked against your rule set and return all failing rules names as a string array.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Frontend example
|
|
18
|
+
```javascript
|
|
6
19
|
// src/index.ts
|
|
20
|
+
import { getInputErrorsSync, getInputErrors } from 'ivl';
|
|
21
|
+
import type { RULES } from 'ivl';
|
|
22
|
+
import { matchesRegex, stringBetween, type } from 'ivl/helpers';
|
|
23
|
+
|
|
24
|
+
// This is also exportable from 'ivl/patterns'
|
|
25
|
+
const EMAIL_PATTERN = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{1,}$/;
|
|
26
|
+
|
|
27
|
+
const EMAIL_REQUIREMENTS: RULES = {
|
|
28
|
+
"Must be string": type('string'),
|
|
29
|
+
"Must be less then 100 characters": stringBetween(),
|
|
30
|
+
"Not a valid email address": matchesRegex(EMAIL_PATTERN),
|
|
31
|
+
"That email is already in use": async (i) => {
|
|
32
|
+
// Fetch info from whatever backend and make a decision based on that asynchronously
|
|
33
|
+
const email_in_use = await fetch(`https://mybackend/email-exists/${i}`)
|
|
34
|
+
return !email_in_use // We will return true if the email is not already taken
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const PASSWORD_REQUIREMENTS: RULES = {
|
|
39
|
+
"Must be string": type('string'),
|
|
40
|
+
"Must be at least 8 characters": stringBetween(Infinity, 8),
|
|
41
|
+
"Must contain at least one upper case character": matchesRegex(/(?=.*[A-Z])/),
|
|
42
|
+
"Must contain at least one lower case character": matchesRegex(/(?=.*[a-z])/),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// You can of course expand on your existing rules:
|
|
46
|
+
const STRONG_PASSWORD_REQUIREMENTS: RULES = {
|
|
47
|
+
...PASSWORD_REQUIREMENTS,
|
|
48
|
+
"Must contain at least one digit": matchesRegex(/(?=.*\d)/),
|
|
49
|
+
"Must contain at least one symbol": matchesRegex(/[^\w]/),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const email_value = "some-value";
|
|
53
|
+
const pw_value = "Passesweakpw";
|
|
54
|
+
|
|
55
|
+
const email_errors = getInputErrors(email_value, EMAIL_REQUIREMENTS);
|
|
56
|
+
const pw_errors = getInputErrorsSync(pw_value, PASSWORD_REQUIREMENTS);
|
|
57
|
+
const strong_pw_errors = getInputErrorsSync(pw_value, STRONG_PASSWORD_REQUIREMENTS);
|
|
58
|
+
|
|
59
|
+
console.log({email_errors, pw_errors, strong_pw_errors});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Backend example with Hono & Bun.js
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// rules.ts
|
|
67
|
+
const EMAIL_REQUIREMENTS = {
|
|
68
|
+
"Must be string": type('string'),
|
|
69
|
+
"Must be less then 100 characters": stringBetween(),
|
|
70
|
+
"Not a valid email address": matchesRegex(EMAIL_PATTERN),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const PASSWORD_REQUIREMENTS = {
|
|
74
|
+
"Must be string": type('string'),
|
|
75
|
+
"Must be at least 8 characters": stringBetween(Infinity, 8),
|
|
76
|
+
"Must contain at least one upper case character": matchesRegex(/(?=.*[A-Z])/),
|
|
77
|
+
"Must contain at least one lower case character": matchesRegex(/(?=.*[a-z])/),
|
|
78
|
+
"Must contain at least one digit": matchesRegex(/(?=.*\d)/),
|
|
79
|
+
"Must contain at least one symbol": matchesRegex(/[^\w]/),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const LOGIN_SCHEMA = {
|
|
83
|
+
email: EMAIL_REQUIREMENTS,
|
|
84
|
+
password: PASSWORD_REQUIREMENTS
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const REGISTER_SCHEMA = {
|
|
88
|
+
organization_name: {} // Pass in an empty rule set, so the value can be whatever
|
|
89
|
+
email: {
|
|
90
|
+
...EMAIL_REQUIREMENTS,
|
|
91
|
+
"Email already registered": checkValueInDatabase('email','users', false)
|
|
92
|
+
},
|
|
93
|
+
password: PASSWORD_REQUIREMENTS
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const INVITATION_PARAM: SCHEMA = {
|
|
97
|
+
code: {
|
|
98
|
+
"Not a valid invitation": existsInDatabase('code', 'invitation')
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// index.ts
|
|
106
|
+
import { Hono } from 'hono';
|
|
107
|
+
import { jwt } from 'hono/jwt';
|
|
108
|
+
import { validator } from 'hono/validator';
|
|
109
|
+
import { getSchemaErrors } from 'ivl';
|
|
110
|
+
import type { SCHEMA } from 'ivl';
|
|
111
|
+
import { LOGIN_SCHEMA, REGISTER_SCHEMA, INVITATION_PARAM } from './rules.ts'
|
|
112
|
+
|
|
113
|
+
// Wrapper for hono validator middleware
|
|
114
|
+
const validateWrapper = (schema: SCHEMA, error: ClientErrorStatusCode | RedirectStatusCode = 400, strict = true) =>
|
|
115
|
+
async (value: any, c: Context) => {
|
|
116
|
+
const jwt = c.get('jwtPayload'); // Pass jwt to the validator so we could use its value when checking input validation rules
|
|
117
|
+
const errors = await getSchemaErrors(value, schema, strict, jwt);
|
|
118
|
+
if (Object.values(errors).filter((e) => e.length).length) {
|
|
119
|
+
throw new HTTPException(error, {
|
|
120
|
+
message: JSON.stringify(errors),
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
return value
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const app = new Hono();
|
|
127
|
+
|
|
128
|
+
app.use(jwt({
|
|
129
|
+
secret: "some-secret-value",
|
|
130
|
+
cookie: 'token'
|
|
131
|
+
}))
|
|
132
|
+
|
|
133
|
+
app.post('/login',
|
|
134
|
+
validator('json', validateWrapper(LOGIN_SCHEMA)),
|
|
135
|
+
(c) => c.text("Success"));
|
|
136
|
+
|
|
137
|
+
app.all('/logout', (c) => c.text("Success"));
|
|
138
|
+
|
|
139
|
+
app.put('/register',
|
|
140
|
+
validator('json', validateWrapper(REGISTER_SCHEMA)),
|
|
141
|
+
(c) => c.text("Success"));
|
|
142
|
+
|
|
143
|
+
// This first validates the invitation parameter against our database
|
|
144
|
+
// And then validates the body of the request
|
|
145
|
+
app.put('/register/:invitation_code',
|
|
146
|
+
validator('param', validateWrapper(INVITATION_PARAM)),
|
|
147
|
+
validator('json', validateWrapper(
|
|
148
|
+
// Registering via invitation needs all the same values except organization name - inherit parts of rule sets, as opposed to extending the rule set as shown above
|
|
149
|
+
(({ organization_name, ...invite_schema }) => invite_schema)(REGISTRATION_SCHEMA)
|
|
150
|
+
)),
|
|
151
|
+
(c)=> c.text("Success")))
|
|
7
152
|
|
|
153
|
+
export default app;
|
|
8
154
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var p=(e)=>(n)=>typeof n===e,s=(e)=>(n)=>typeof n==="string"&&e.test(n),i=(e=100,n=0)=>(t)=>typeof t==="string"&&t.length>=n&&t.length<e,w=(e=100,n=1)=>(t)=>typeof t==="number"&&t>=n&&t<e,f=(e=[])=>async(n,...t)=>(await Promise.all(e.map((o)=>o.call(void 0,n,...t)))).some((o)=>o),d=(e)=>Object.entries(e).reduce((n,[t,o])=>({...n,[t]:(r,...c)=>typeof r==="undefined"?!0:o(r,...c)}),{}),k=(e,n)=>Object.entries(n).reduce((t,[o,r])=>({...t,[o]:(c,...u)=>r(e(c),...u)}),{});export{p as type,i as stringBetween,k as preprocess,w as numberBetween,s as matchesRegex,d as allowUndefined,f as acceptAny};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v8.1.1
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
};
|
|
7
|
-
export type CHECKABLE_OBJECT = {
|
|
8
|
-
[index: string]: unknown;
|
|
9
|
-
};
|
|
10
|
-
export type SCHEMA = {
|
|
11
|
-
[index: string]: RULES;
|
|
12
|
-
};
|
|
13
|
-
export type CHECKED_SCHEMA = Promise<{
|
|
14
|
-
[index: string]: string[];
|
|
15
|
-
}>;
|
|
3
|
+
export declare const EMAIL_PATTERN: RegExp;
|
|
4
|
+
export declare const TIMESTAMP_PATTERN: RegExp;
|
|
5
|
+
export declare const URL_PATTERN: RegExp;
|
|
16
6
|
|
|
17
7
|
export {};
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var U=async(E,S,...L)=>{const p={};return Object.entries(S).forEach(([n,x])=>{p[n]=Promise.resolve().then(()=>x(E,...L)).then((C)=>p[n]=C).catch(()=>{p[n]=!1})}),await Promise.allSettled(Object.values(p)),Object.entries(p).reduce((n,[x,C])=>C?n:[...n,x],[])},N=async(E,S,L=!1,...p)=>{const n={};if(Object.entries(S).forEach(([x,C])=>{n[x]=U(E[x],C,...p).then((i)=>{n[x]=i}).catch((i)=>{n[x]=i})}),L){const x=Object.keys(E),C=new Set(Object.keys(S));x.filter((R)=>!C.has(R)).forEach((R)=>{n[R]=["Key not allowed"]})}return await Promise.allSettled(Object.values(n)),n},H=(E,S,...L)=>Object.entries(S).reduce((p,[n,x])=>x(E,...L)?p:[...p,n],[]),Y=(E,S,L=!1,...p)=>{const n=Object.entries(S).reduce((x,[C,i])=>({...x,[C]:H(E[C],i,...p)}),{});if(L){const x=Object.keys(E),C=new Set(Object.keys(S));x.filter((R)=>!C.has(R)).forEach((R)=>{n[R]=["Key not allowed"]})}return n};export{H as getValueErrorsSync,U as getValueErrors,Y as getSchemaErrorsSync,N as getSchemaErrors};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var T=/^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{1,}$/,s=/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/,t=/^(https?:\/\/)?([^\s$.?#].[^\s]*)\.[a-z]{2,}(\/[^\s]*)?$/i;export{t as URL_PATTERN,s as TIMESTAMP_PATTERN,T as EMAIL_PATTERN};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ivl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"description": "Lightweight input validation",
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
"name": "Oskar Voorel",
|
|
9
9
|
"email": "oskar@voorel.com"
|
|
10
10
|
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/Oskar-V/validator.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/Oskar-V/validator/issues/new"
|
|
17
|
+
},
|
|
11
18
|
"scripts": {
|
|
12
19
|
"build": "bun run build.mjs",
|
|
13
20
|
"prepublishOnly": "bun run build"
|
|
@@ -25,6 +32,14 @@
|
|
|
25
32
|
".": {
|
|
26
33
|
"types": "./lib/index.d.ts",
|
|
27
34
|
"default": "./lib/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./patterns": {
|
|
37
|
+
"types": "./lib/patterns/index.d.ts",
|
|
38
|
+
"default": "./lib/patterns/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./helpers": {
|
|
41
|
+
"types": "./lib/helpers/index.d.ts",
|
|
42
|
+
"default": "./lib/helpers/index.js"
|
|
28
43
|
}
|
|
29
44
|
},
|
|
30
45
|
"sideEffects": "false",
|