@marslanmustafa/input-shield 0.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/LICENSE +21 -0
- package/NODEMAILER.md +144 -0
- package/README.md +239 -0
- package/dist/builder-C10YQjbV.d.cts +123 -0
- package/dist/builder-C10YQjbV.d.ts +123 -0
- package/dist/chunk-67CBN3U4.cjs +520 -0
- package/dist/chunk-67CBN3U4.cjs.map +1 -0
- package/dist/chunk-ADGSP522.mjs +506 -0
- package/dist/chunk-ADGSP522.mjs.map +1 -0
- package/dist/chunk-KVXEPETW.cjs +28 -0
- package/dist/chunk-KVXEPETW.cjs.map +1 -0
- package/dist/chunk-WACGX73I.mjs +22 -0
- package/dist/chunk-WACGX73I.mjs.map +1 -0
- package/dist/email.cjs +48 -0
- package/dist/email.cjs.map +1 -0
- package/dist/email.d.cts +71 -0
- package/dist/email.d.ts +71 -0
- package/dist/email.mjs +45 -0
- package/dist/email.mjs.map +1 -0
- package/dist/index.cjs +81 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +217 -0
- package/dist/index.d.ts +217 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -0
- package/dist/zod.cjs +59 -0
- package/dist/zod.cjs.map +1 -0
- package/dist/zod.d.cts +47 -0
- package/dist/zod.d.ts +47 -0
- package/dist/zod.mjs +53 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +105 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { V as ValidationResult, G as GibberishSensitivity } from './builder-C10YQjbV.js';
|
|
2
|
+
export { F as FailReason, I as InputShieldValidator, a as ValidationOptions, c as createValidator } from './builder-C10YQjbV.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* presets.ts
|
|
6
|
+
*
|
|
7
|
+
* Ready-to-use validators for the most common field types.
|
|
8
|
+
* Zero config — import and call.
|
|
9
|
+
*
|
|
10
|
+
* All presets are pre-configured InputShieldValidator instances.
|
|
11
|
+
* You can also use them as a starting point and extend with .custom():
|
|
12
|
+
*
|
|
13
|
+
* import { usernameValidator } from 'input-shield/presets';
|
|
14
|
+
* // They're factories, so each call gives a fresh instance:
|
|
15
|
+
* const myValidator = usernameValidator().custom(t => t === 'admin', 'custom', 'reserved name');
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Username / display name.
|
|
20
|
+
* 3–30 chars, no profanity, strict gibberish detection, no spam.
|
|
21
|
+
* Repeated words allowed (e.g. "John John" as a nickname).
|
|
22
|
+
*/
|
|
23
|
+
declare function validateUsername(text: string): ValidationResult;
|
|
24
|
+
/**
|
|
25
|
+
* Short text / name fields (product name, company name, form title).
|
|
26
|
+
* 2–100 chars, no profanity, normal gibberish, no spam.
|
|
27
|
+
*/
|
|
28
|
+
declare function validateShortText(text: string, fieldName?: string): ValidationResult;
|
|
29
|
+
/**
|
|
30
|
+
* Bio / description / about me.
|
|
31
|
+
* 10–300 chars, no profanity, no spam, loose gibberish (allows natural language).
|
|
32
|
+
* Repeated words NOT flagged (natural in prose).
|
|
33
|
+
*/
|
|
34
|
+
declare function validateBio(text: string): ValidationResult;
|
|
35
|
+
/**
|
|
36
|
+
* Long-form text (comment, review, feedback).
|
|
37
|
+
* 5–2000 chars, no profanity, no spam. No gibberish check
|
|
38
|
+
* (long text can contain intentional fragments, code, etc.)
|
|
39
|
+
*/
|
|
40
|
+
declare function validateLongText(text: string, fieldName?: string): ValidationResult;
|
|
41
|
+
/**
|
|
42
|
+
* Search query input.
|
|
43
|
+
* 1–200 chars, no spam URLs (but allows short/fragmentary text).
|
|
44
|
+
* Does NOT flag gibberish (code snippets, SKUs, part numbers are valid queries).
|
|
45
|
+
*/
|
|
46
|
+
declare function validateSearchQuery(text: string): ValidationResult;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* normalize.ts
|
|
50
|
+
*
|
|
51
|
+
* THREE-STAGE normalization pipeline.
|
|
52
|
+
* This is THE most important file in the package.
|
|
53
|
+
*
|
|
54
|
+
* Stage 1 — Unicode NFKC:
|
|
55
|
+
* Collapses compatibility variants before anything else.
|
|
56
|
+
* "A" (fullwidth) → "A", "fi" (ligature) → "fi", "𝐅" (math bold) → "F"
|
|
57
|
+
* This also handles bidirectional control characters and zero-width spaces.
|
|
58
|
+
*
|
|
59
|
+
* Stage 2 — Homoglyph map:
|
|
60
|
+
* Catches Cyrillic/Greek/Armenian lookalikes that survive NFKC.
|
|
61
|
+
* "о" (U+043E Cyrillic) → "o", "а" (U+0430 Cyrillic) → "a"
|
|
62
|
+
* This is the CVE-2025-27611 class of bypass — NFKC alone doesn't fix it.
|
|
63
|
+
*
|
|
64
|
+
* Stage 3 — Leet-speak map:
|
|
65
|
+
* Classic ASCII substitutions: "3" → "e", "@" → "a", "$" → "s", etc.
|
|
66
|
+
* Runs LAST so homoglyphs don't interfere with leet detection.
|
|
67
|
+
*
|
|
68
|
+
* Result: "P.0.r.n" → "porn", "ſhit" → "shit", "аss" (Cyrillic а) → "ass"
|
|
69
|
+
*
|
|
70
|
+
* IMPORTANT: This output is ONLY for pattern matching — never display it.
|
|
71
|
+
* Always return error messages that reference the original input.
|
|
72
|
+
*/
|
|
73
|
+
/**
|
|
74
|
+
* Produce a "skeleton" string for pattern matching ONLY.
|
|
75
|
+
*
|
|
76
|
+
* Pipeline:
|
|
77
|
+
* raw → NFKC → stripSeparators → homoglyph → lowercase → leet → strip non-alpha → collapse spaces
|
|
78
|
+
*
|
|
79
|
+
* @param t - Raw input string
|
|
80
|
+
* @returns Normalized string safe for regex pattern matching
|
|
81
|
+
*/
|
|
82
|
+
declare function toSkeleton(t: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Lightweight normalization for structural checks (length, symbol ratio).
|
|
85
|
+
* Does NOT apply leet/homoglyph maps — just trims and normalizes whitespace.
|
|
86
|
+
* Preserves symbols so hasExcessiveSymbols() works correctly.
|
|
87
|
+
*/
|
|
88
|
+
declare function toStructural(t: string): string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* profanity.ts
|
|
92
|
+
*
|
|
93
|
+
* Profanity detection. All patterns run against the skeleton (toSkeleton()),
|
|
94
|
+
* meaning they automatically handle:
|
|
95
|
+
* - Leet-speak: "f4ck", "@ss", "sh!t"
|
|
96
|
+
* - Homoglyphs: "fuсk" (Cyrillic с), "аss" (Cyrillic а)
|
|
97
|
+
* - Separator dots: "f.u.c.k", "s-h-i-t"
|
|
98
|
+
* - Fullwidth: "fuck"
|
|
99
|
+
* - Repeated chars: "fuuuuck", "shhhhit"
|
|
100
|
+
*
|
|
101
|
+
* Pattern design:
|
|
102
|
+
* - Use \b word boundaries so "classic" doesn't match "ass"
|
|
103
|
+
* - Use + quantifiers to catch character stretching ("fuuuuck")
|
|
104
|
+
* - Cover plurals and -er/-ing forms (shits, bitch, bitching)
|
|
105
|
+
*/
|
|
106
|
+
/**
|
|
107
|
+
* Returns true if the skeleton of `text` matches any profanity pattern.
|
|
108
|
+
* Always check skeleton — never raw — so bypasses don't work.
|
|
109
|
+
*/
|
|
110
|
+
declare function containsProfanity(text: string): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Returns the specific pattern that matched, or null.
|
|
113
|
+
* Useful for debug logging (never expose to users directly).
|
|
114
|
+
*/
|
|
115
|
+
declare function getMatchedProfanityPattern(text: string): RegExp | null;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* spam.ts
|
|
119
|
+
*
|
|
120
|
+
* Spam detection.
|
|
121
|
+
*
|
|
122
|
+
* CRITICAL DESIGN DECISION:
|
|
123
|
+
* URL and domain patterns MUST run on the raw (or NFKC-only) text.
|
|
124
|
+
* If you normalize first (stripping dots, slashes, colons), URLs become
|
|
125
|
+
* undetectable. "https://spam.com" → after skeleton → "httpsspamcom" which
|
|
126
|
+
* no URL regex can match.
|
|
127
|
+
*
|
|
128
|
+
* So: keyword spam runs on skeleton (catches leet evasion),
|
|
129
|
+
* URL/domain spam runs on NFKC-normalized raw text only.
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* Returns true if text contains spam keywords (checked on skeleton)
|
|
133
|
+
* or URLs/domains (checked on NFKC-only normalized text).
|
|
134
|
+
*/
|
|
135
|
+
declare function containsSpam(text: string): boolean;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* gibberish.ts
|
|
139
|
+
*
|
|
140
|
+
* Gibberish / keyboard-mash detection with a configurable sensitivity scale.
|
|
141
|
+
*
|
|
142
|
+
* Why sensitivity levels?
|
|
143
|
+
* "Strict" mode is needed for display names & usernames.
|
|
144
|
+
* "Loose" mode prevents false positives on:
|
|
145
|
+
* - Polish names: "Krzysztof", "Szczepański"
|
|
146
|
+
* - Technical strings: "kubectl", "nginx", "src"
|
|
147
|
+
* - Abbreviations: "HVAC", "VLSI"
|
|
148
|
+
* - Short legitimate words like "nth", "gym", "lynx"
|
|
149
|
+
*
|
|
150
|
+
* Heuristics used (layered by sensitivity):
|
|
151
|
+
* LOOSE: 7+ consonants in a row (obvious keyboard mash only)
|
|
152
|
+
* NORMAL: 6+ consonants in a row OR vowel ratio < 10% on words ≥ 8 chars
|
|
153
|
+
* STRICT: 5+ consonants in a row OR vowel ratio < 15% on words ≥ 6 chars
|
|
154
|
+
* OR no vowels at all on words ≥ 4 chars
|
|
155
|
+
*
|
|
156
|
+
* All checks run on the skeleton so leet/homoglyphs are already resolved.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns true if any word in the text looks like gibberish.
|
|
161
|
+
*
|
|
162
|
+
* Filters out very short words (< 4 chars) before applying heuristics
|
|
163
|
+
* to prevent false positives on "by", "mr", "st", "nth", etc.
|
|
164
|
+
*/
|
|
165
|
+
declare function isGibberish(text: string, sensitivity?: GibberishSensitivity): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Returns true if the text contains 5+ of the same character consecutively.
|
|
168
|
+
* e.g. "aaaaaaa", "!!!!!!", "heeeeey" (5 e's)
|
|
169
|
+
* Runs on skeleton so leet chars are already resolved.
|
|
170
|
+
*/
|
|
171
|
+
declare function hasRepeatingChars(text: string): boolean;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* structure.ts
|
|
175
|
+
*
|
|
176
|
+
* Structural quality checks. These run on the NFKC-normalized original text
|
|
177
|
+
* (not the full skeleton), because they're measuring the *shape* of the input
|
|
178
|
+
* (symbol density, letter presence) — not its semantic content.
|
|
179
|
+
*
|
|
180
|
+
* Running these on the skeleton would give wrong results because the skeleton
|
|
181
|
+
* strips ALL symbols, making everything look clean structurally.
|
|
182
|
+
*/
|
|
183
|
+
/**
|
|
184
|
+
* Returns true if more than 40% of characters are symbols
|
|
185
|
+
* (not letters, digits, or whitespace).
|
|
186
|
+
*
|
|
187
|
+
* Short strings (< 5 chars) are excluded — too noisy to judge.
|
|
188
|
+
* e.g. "!!??@@##" → 100% symbols → flagged
|
|
189
|
+
* "Hello!!" → 22% symbols → pass
|
|
190
|
+
*/
|
|
191
|
+
declare function hasExcessiveSymbols(text: string): boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Returns true if fewer than 20% of characters are letters AND
|
|
194
|
+
* there are fewer than 3 total letter characters.
|
|
195
|
+
*
|
|
196
|
+
* This catches strings like "123 456", "--- ---", "42", "!2!"
|
|
197
|
+
* but allows legitimate short inputs like "QA", "IT", "Go".
|
|
198
|
+
*
|
|
199
|
+
* Both conditions must be true to avoid false positives.
|
|
200
|
+
*/
|
|
201
|
+
declare function hasLowAlphabetRatio(text: string): boolean;
|
|
202
|
+
/**
|
|
203
|
+
* Detects repeated *content* words (length > 3).
|
|
204
|
+
* Stop words ("the", "and", "for") are excluded to avoid false positives
|
|
205
|
+
* in natural English sentences.
|
|
206
|
+
*
|
|
207
|
+
* Flags only when 2+ distinct content words appear more than once.
|
|
208
|
+
* "the cat sat on the mat" → 0 repeated content words → pass
|
|
209
|
+
* "cat cat cat dog dog" → "cat" repeated, "dog" repeated → flag
|
|
210
|
+
*/
|
|
211
|
+
declare function hasRepeatedContentWords(text: string): boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Returns true if the skeleton of `text` exactly matches a known low-effort phrase.
|
|
214
|
+
*/
|
|
215
|
+
declare function isLowEffortExact(skeletonText: string): boolean;
|
|
216
|
+
|
|
217
|
+
export { GibberishSensitivity, ValidationResult, containsProfanity, containsSpam, getMatchedProfanityPattern, hasExcessiveSymbols, hasLowAlphabetRatio, hasRepeatedContentWords, hasRepeatingChars, isGibberish, isLowEffortExact, toSkeleton, toStructural, validateBio, validateLongText, validateSearchQuery, validateShortText, validateUsername };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { validateBio, validateLongText, validateSearchQuery, validateShortText, validateUsername } from './chunk-WACGX73I.mjs';
|
|
2
|
+
export { InputShieldValidator, containsProfanity, containsSpam, createValidator, getMatchedProfanityPattern, hasExcessiveSymbols, hasLowAlphabetRatio, hasRepeatedContentWords, hasRepeatingChars, isGibberish, isLowEffortExact, toSkeleton, toStructural } from './chunk-ADGSP522.mjs';
|
|
3
|
+
//# sourceMappingURL=index.mjs.map
|
|
4
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
|
package/dist/zod.cjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkKVXEPETW_cjs = require('./chunk-KVXEPETW.cjs');
|
|
4
|
+
var chunk67CBN3U4_cjs = require('./chunk-67CBN3U4.cjs');
|
|
5
|
+
var zod = require('zod');
|
|
6
|
+
|
|
7
|
+
function shieldString(configure) {
|
|
8
|
+
const validator = configure(chunk67CBN3U4_cjs.createValidator());
|
|
9
|
+
return zod.z.string().superRefine((val, ctx) => {
|
|
10
|
+
const result = validator.validate(val);
|
|
11
|
+
if (!result.isValid) {
|
|
12
|
+
ctx.addIssue({
|
|
13
|
+
code: zod.z.ZodIssueCode.custom,
|
|
14
|
+
message: result.message,
|
|
15
|
+
params: { reason: result.reason }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function zodUsername() {
|
|
21
|
+
return zod.z.string().superRefine((val, ctx) => {
|
|
22
|
+
const result = chunkKVXEPETW_cjs.validateUsername(val);
|
|
23
|
+
if (!result.isValid) {
|
|
24
|
+
ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: result.message });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function zodBio() {
|
|
29
|
+
return zod.z.string().superRefine((val, ctx) => {
|
|
30
|
+
const result = chunkKVXEPETW_cjs.validateBio(val);
|
|
31
|
+
if (!result.isValid) {
|
|
32
|
+
ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: result.message });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function zodShortText(fieldName) {
|
|
37
|
+
return zod.z.string().superRefine((val, ctx) => {
|
|
38
|
+
const result = chunkKVXEPETW_cjs.validateShortText(val, fieldName);
|
|
39
|
+
if (!result.isValid) {
|
|
40
|
+
ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: result.message });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function zodLongText(fieldName) {
|
|
45
|
+
return zod.z.string().superRefine((val, ctx) => {
|
|
46
|
+
const result = chunkKVXEPETW_cjs.validateLongText(val, fieldName);
|
|
47
|
+
if (!result.isValid) {
|
|
48
|
+
ctx.addIssue({ code: zod.z.ZodIssueCode.custom, message: result.message });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.shieldString = shieldString;
|
|
54
|
+
exports.zodBio = zodBio;
|
|
55
|
+
exports.zodLongText = zodLongText;
|
|
56
|
+
exports.zodShortText = zodShortText;
|
|
57
|
+
exports.zodUsername = zodUsername;
|
|
58
|
+
//# sourceMappingURL=zod.cjs.map
|
|
59
|
+
//# sourceMappingURL=zod.cjs.map
|
package/dist/zod.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/zod.ts"],"names":["createValidator","z","validateUsername","validateBio","validateShortText","validateLongText"],"mappings":";;;;;;AA0CO,SAAS,aACd,SAAA,EAC2B;AAC3B,EAAA,MAAM,SAAA,GAAY,SAAA,CAAUA,iCAAA,EAAiB,CAAA;AAC7C,EAAA,OAAOC,MAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAAS,SAAA,CAAU,QAAA,CAAS,GAAG,CAAA;AACrC,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAA,EAAMA,MAAE,YAAA,CAAa,MAAA;AAAA,QACrB,SAAS,MAAA,CAAO,OAAA;AAAA,QAChB,MAAA,EAAQ,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAA;AAAO,OACjC,CAAA;AAAA,IACH;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,WAAA,GAAyC;AACvD,EAAA,OAAOA,MAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAASC,mCAAiB,GAAG,CAAA;AACnC,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAMD,KAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,MAAA,GAAoC;AAClD,EAAA,OAAOA,MAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAASE,8BAAY,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAMF,KAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,aAAa,SAAA,EAA+C;AAC1E,EAAA,OAAOA,MAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAASG,mCAAA,CAAkB,GAAA,EAAK,SAAS,CAAA;AAC/C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAMH,KAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,YAAY,SAAA,EAA+C;AACzE,EAAA,OAAOA,MAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAASI,kCAAA,CAAiB,GAAA,EAAK,SAAS,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAMJ,KAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH","file":"zod.cjs","sourcesContent":["/**\n * zod.ts (exported as 'input-shield/zod')\n *\n * Zod integration helpers. Import from the subpath to keep Zod out of the\n * main bundle if you don't use it — zero cost if unused.\n *\n * Usage:\n * import { z } from 'zod';\n * import { shieldString } from 'input-shield/zod';\n *\n * const schema = z.object({\n * username: shieldString(v => v.min(3).max(20).noProfanity().noGibberish()),\n * bio: shieldString(v => v.min(10).max(300).noProfanity().noSpam()),\n * });\n *\n * Or use the preset helpers:\n * import { zodUsername, zodBio } from 'input-shield/zod';\n *\n * const schema = z.object({\n * username: zodUsername(),\n * bio: zodBio(),\n * });\n */\n\nimport { z } from 'zod';\nimport { createValidator, InputShieldValidator } from './validators/builder.js';\nimport {\n validateUsername,\n validateBio,\n validateShortText,\n validateLongText,\n} from './validators/presets.js';\n\n/**\n * Wraps a configured InputShieldValidator as a Zod string schema.\n *\n * @param configure - Callback that receives a fresh validator and returns it configured\n * @returns z.ZodEffects<z.ZodString>\n *\n * @example\n * shieldString(v => v.field('Bio').min(10).noProfanity().noSpam())\n */\nexport function shieldString(\n configure: (v: InputShieldValidator) => InputShieldValidator\n): z.ZodEffects<z.ZodString> {\n const validator = configure(createValidator());\n return z.string().superRefine((val, ctx) => {\n const result = validator.validate(val);\n if (!result.isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: result.message,\n params: { reason: result.reason },\n });\n }\n });\n}\n\n/** Zod schema for usernames (3–30 chars, no profanity, strict gibberish) */\nexport function zodUsername(): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateUsername(val);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n\n/** Zod schema for bios (10–300 chars, no profanity, no spam) */\nexport function zodBio(): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateBio(val);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n\n/** Zod schema for short text fields (2–100 chars) */\nexport function zodShortText(fieldName?: string): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateShortText(val, fieldName);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n\n/** Zod schema for long text / comments (5–2000 chars) */\nexport function zodLongText(fieldName?: string): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateLongText(val, fieldName);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n"]}
|
package/dist/zod.d.cts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { I as InputShieldValidator } from './builder-C10YQjbV.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* zod.ts (exported as 'input-shield/zod')
|
|
6
|
+
*
|
|
7
|
+
* Zod integration helpers. Import from the subpath to keep Zod out of the
|
|
8
|
+
* main bundle if you don't use it — zero cost if unused.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { z } from 'zod';
|
|
12
|
+
* import { shieldString } from 'input-shield/zod';
|
|
13
|
+
*
|
|
14
|
+
* const schema = z.object({
|
|
15
|
+
* username: shieldString(v => v.min(3).max(20).noProfanity().noGibberish()),
|
|
16
|
+
* bio: shieldString(v => v.min(10).max(300).noProfanity().noSpam()),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* Or use the preset helpers:
|
|
20
|
+
* import { zodUsername, zodBio } from 'input-shield/zod';
|
|
21
|
+
*
|
|
22
|
+
* const schema = z.object({
|
|
23
|
+
* username: zodUsername(),
|
|
24
|
+
* bio: zodBio(),
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a configured InputShieldValidator as a Zod string schema.
|
|
30
|
+
*
|
|
31
|
+
* @param configure - Callback that receives a fresh validator and returns it configured
|
|
32
|
+
* @returns z.ZodEffects<z.ZodString>
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* shieldString(v => v.field('Bio').min(10).noProfanity().noSpam())
|
|
36
|
+
*/
|
|
37
|
+
declare function shieldString(configure: (v: InputShieldValidator) => InputShieldValidator): z.ZodEffects<z.ZodString>;
|
|
38
|
+
/** Zod schema for usernames (3–30 chars, no profanity, strict gibberish) */
|
|
39
|
+
declare function zodUsername(): z.ZodEffects<z.ZodString>;
|
|
40
|
+
/** Zod schema for bios (10–300 chars, no profanity, no spam) */
|
|
41
|
+
declare function zodBio(): z.ZodEffects<z.ZodString>;
|
|
42
|
+
/** Zod schema for short text fields (2–100 chars) */
|
|
43
|
+
declare function zodShortText(fieldName?: string): z.ZodEffects<z.ZodString>;
|
|
44
|
+
/** Zod schema for long text / comments (5–2000 chars) */
|
|
45
|
+
declare function zodLongText(fieldName?: string): z.ZodEffects<z.ZodString>;
|
|
46
|
+
|
|
47
|
+
export { shieldString, zodBio, zodLongText, zodShortText, zodUsername };
|
package/dist/zod.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { I as InputShieldValidator } from './builder-C10YQjbV.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* zod.ts (exported as 'input-shield/zod')
|
|
6
|
+
*
|
|
7
|
+
* Zod integration helpers. Import from the subpath to keep Zod out of the
|
|
8
|
+
* main bundle if you don't use it — zero cost if unused.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { z } from 'zod';
|
|
12
|
+
* import { shieldString } from 'input-shield/zod';
|
|
13
|
+
*
|
|
14
|
+
* const schema = z.object({
|
|
15
|
+
* username: shieldString(v => v.min(3).max(20).noProfanity().noGibberish()),
|
|
16
|
+
* bio: shieldString(v => v.min(10).max(300).noProfanity().noSpam()),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* Or use the preset helpers:
|
|
20
|
+
* import { zodUsername, zodBio } from 'input-shield/zod';
|
|
21
|
+
*
|
|
22
|
+
* const schema = z.object({
|
|
23
|
+
* username: zodUsername(),
|
|
24
|
+
* bio: zodBio(),
|
|
25
|
+
* });
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a configured InputShieldValidator as a Zod string schema.
|
|
30
|
+
*
|
|
31
|
+
* @param configure - Callback that receives a fresh validator and returns it configured
|
|
32
|
+
* @returns z.ZodEffects<z.ZodString>
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* shieldString(v => v.field('Bio').min(10).noProfanity().noSpam())
|
|
36
|
+
*/
|
|
37
|
+
declare function shieldString(configure: (v: InputShieldValidator) => InputShieldValidator): z.ZodEffects<z.ZodString>;
|
|
38
|
+
/** Zod schema for usernames (3–30 chars, no profanity, strict gibberish) */
|
|
39
|
+
declare function zodUsername(): z.ZodEffects<z.ZodString>;
|
|
40
|
+
/** Zod schema for bios (10–300 chars, no profanity, no spam) */
|
|
41
|
+
declare function zodBio(): z.ZodEffects<z.ZodString>;
|
|
42
|
+
/** Zod schema for short text fields (2–100 chars) */
|
|
43
|
+
declare function zodShortText(fieldName?: string): z.ZodEffects<z.ZodString>;
|
|
44
|
+
/** Zod schema for long text / comments (5–2000 chars) */
|
|
45
|
+
declare function zodLongText(fieldName?: string): z.ZodEffects<z.ZodString>;
|
|
46
|
+
|
|
47
|
+
export { shieldString, zodBio, zodLongText, zodShortText, zodUsername };
|
package/dist/zod.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { validateUsername, validateBio, validateShortText, validateLongText } from './chunk-WACGX73I.mjs';
|
|
2
|
+
import { createValidator } from './chunk-ADGSP522.mjs';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
function shieldString(configure) {
|
|
6
|
+
const validator = configure(createValidator());
|
|
7
|
+
return z.string().superRefine((val, ctx) => {
|
|
8
|
+
const result = validator.validate(val);
|
|
9
|
+
if (!result.isValid) {
|
|
10
|
+
ctx.addIssue({
|
|
11
|
+
code: z.ZodIssueCode.custom,
|
|
12
|
+
message: result.message,
|
|
13
|
+
params: { reason: result.reason }
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function zodUsername() {
|
|
19
|
+
return z.string().superRefine((val, ctx) => {
|
|
20
|
+
const result = validateUsername(val);
|
|
21
|
+
if (!result.isValid) {
|
|
22
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function zodBio() {
|
|
27
|
+
return z.string().superRefine((val, ctx) => {
|
|
28
|
+
const result = validateBio(val);
|
|
29
|
+
if (!result.isValid) {
|
|
30
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function zodShortText(fieldName) {
|
|
35
|
+
return z.string().superRefine((val, ctx) => {
|
|
36
|
+
const result = validateShortText(val, fieldName);
|
|
37
|
+
if (!result.isValid) {
|
|
38
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function zodLongText(fieldName) {
|
|
43
|
+
return z.string().superRefine((val, ctx) => {
|
|
44
|
+
const result = validateLongText(val, fieldName);
|
|
45
|
+
if (!result.isValid) {
|
|
46
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { shieldString, zodBio, zodLongText, zodShortText, zodUsername };
|
|
52
|
+
//# sourceMappingURL=zod.mjs.map
|
|
53
|
+
//# sourceMappingURL=zod.mjs.map
|
package/dist/zod.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/zod.ts"],"names":[],"mappings":";;;;AA0CO,SAAS,aACd,SAAA,EAC2B;AAC3B,EAAA,MAAM,SAAA,GAAY,SAAA,CAAU,eAAA,EAAiB,CAAA;AAC7C,EAAA,OAAO,EAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAAS,SAAA,CAAU,QAAA,CAAS,GAAG,CAAA;AACrC,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS;AAAA,QACX,IAAA,EAAM,EAAE,YAAA,CAAa,MAAA;AAAA,QACrB,SAAS,MAAA,CAAO,OAAA;AAAA,QAChB,MAAA,EAAQ,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAA;AAAO,OACjC,CAAA;AAAA,IACH;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,WAAA,GAAyC;AACvD,EAAA,OAAO,EAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAAS,iBAAiB,GAAG,CAAA;AACnC,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAM,CAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,MAAA,GAAoC;AAClD,EAAA,OAAO,EAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAAS,YAAY,GAAG,CAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAM,CAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,aAAa,SAAA,EAA+C;AAC1E,EAAA,OAAO,EAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,GAAA,EAAK,SAAS,CAAA;AAC/C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAM,CAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH;AAGO,SAAS,YAAY,SAAA,EAA+C;AACzE,EAAA,OAAO,EAAE,MAAA,EAAO,CAAE,WAAA,CAAY,CAAC,KAAK,GAAA,KAAQ;AAC1C,IAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,GAAA,EAAK,SAAS,CAAA;AAC9C,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,GAAA,CAAI,QAAA,CAAS,EAAE,IAAA,EAAM,CAAA,CAAE,aAAa,MAAA,EAAQ,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,CAAA;AAAA,IACvE;AAAA,EACF,CAAC,CAAA;AACH","file":"zod.mjs","sourcesContent":["/**\n * zod.ts (exported as 'input-shield/zod')\n *\n * Zod integration helpers. Import from the subpath to keep Zod out of the\n * main bundle if you don't use it — zero cost if unused.\n *\n * Usage:\n * import { z } from 'zod';\n * import { shieldString } from 'input-shield/zod';\n *\n * const schema = z.object({\n * username: shieldString(v => v.min(3).max(20).noProfanity().noGibberish()),\n * bio: shieldString(v => v.min(10).max(300).noProfanity().noSpam()),\n * });\n *\n * Or use the preset helpers:\n * import { zodUsername, zodBio } from 'input-shield/zod';\n *\n * const schema = z.object({\n * username: zodUsername(),\n * bio: zodBio(),\n * });\n */\n\nimport { z } from 'zod';\nimport { createValidator, InputShieldValidator } from './validators/builder.js';\nimport {\n validateUsername,\n validateBio,\n validateShortText,\n validateLongText,\n} from './validators/presets.js';\n\n/**\n * Wraps a configured InputShieldValidator as a Zod string schema.\n *\n * @param configure - Callback that receives a fresh validator and returns it configured\n * @returns z.ZodEffects<z.ZodString>\n *\n * @example\n * shieldString(v => v.field('Bio').min(10).noProfanity().noSpam())\n */\nexport function shieldString(\n configure: (v: InputShieldValidator) => InputShieldValidator\n): z.ZodEffects<z.ZodString> {\n const validator = configure(createValidator());\n return z.string().superRefine((val, ctx) => {\n const result = validator.validate(val);\n if (!result.isValid) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: result.message,\n params: { reason: result.reason },\n });\n }\n });\n}\n\n/** Zod schema for usernames (3–30 chars, no profanity, strict gibberish) */\nexport function zodUsername(): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateUsername(val);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n\n/** Zod schema for bios (10–300 chars, no profanity, no spam) */\nexport function zodBio(): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateBio(val);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n\n/** Zod schema for short text fields (2–100 chars) */\nexport function zodShortText(fieldName?: string): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateShortText(val, fieldName);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n\n/** Zod schema for long text / comments (5–2000 chars) */\nexport function zodLongText(fieldName?: string): z.ZodEffects<z.ZodString> {\n return z.string().superRefine((val, ctx) => {\n const result = validateLongText(val, fieldName);\n if (!result.isValid) {\n ctx.addIssue({ code: z.ZodIssueCode.custom, message: result.message });\n }\n });\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@marslanmustafa/input-shield",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One install. No config. Clean inputs. Profanity, spam, gibberish, and homoglyph detection in a single TypeScript-native zero-dependency package.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Muhammad Arslan Mustafa",
|
|
7
|
+
"email": "marslanmustafa391@gmail.com",
|
|
8
|
+
"url": "https://marslanmustafa.com"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/marslanmustafa/input-shield"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/marslanmustafa/input-shield#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/marslanmustafa/input-shield/issues"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/index.mjs",
|
|
24
|
+
"require": "./dist/index.cjs",
|
|
25
|
+
"types": "./dist/index.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./zod": {
|
|
28
|
+
"import": "./dist/zod.mjs",
|
|
29
|
+
"require": "./dist/zod.cjs",
|
|
30
|
+
"types": "./dist/zod.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./email": {
|
|
33
|
+
"import": "./dist/email.mjs",
|
|
34
|
+
"require": "./dist/email.cjs",
|
|
35
|
+
"types": "./dist/email.d.ts"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/index.cjs",
|
|
39
|
+
"module": "./dist/index.mjs",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"NODEMAILER.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"CHANGELOG.md"
|
|
47
|
+
],
|
|
48
|
+
"keywords": [
|
|
49
|
+
"profanity",
|
|
50
|
+
"profanity-filter",
|
|
51
|
+
"spam-detection",
|
|
52
|
+
"gibberish",
|
|
53
|
+
"input-validation",
|
|
54
|
+
"text-validation",
|
|
55
|
+
"content-moderation",
|
|
56
|
+
"leet-speak",
|
|
57
|
+
"homoglyph",
|
|
58
|
+
"unicode",
|
|
59
|
+
"typescript",
|
|
60
|
+
"zero-dependency",
|
|
61
|
+
"form-validation",
|
|
62
|
+
"zod"
|
|
63
|
+
],
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsup",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
"coverage": "vitest run --coverage",
|
|
69
|
+
"lint": "tsc --noEmit",
|
|
70
|
+
"size": "size-limit",
|
|
71
|
+
"prepublishOnly": "npm run lint && npm run test && npm run build",
|
|
72
|
+
"release:patch": "npm version patch && git push --follow-tags",
|
|
73
|
+
"release:minor": "npm version minor && git push --follow-tags",
|
|
74
|
+
"release:major": "npm version major && git push --follow-tags"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@size-limit/preset-small-lib": "^11.0.0",
|
|
78
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
79
|
+
"tsup": "^8.0.0",
|
|
80
|
+
"typescript": "^5.4.0",
|
|
81
|
+
"vitest": "^1.6.0",
|
|
82
|
+
"zod": "^3.22.0"
|
|
83
|
+
},
|
|
84
|
+
"peerDependencies": {
|
|
85
|
+
"zod": ">=3.0.0"
|
|
86
|
+
},
|
|
87
|
+
"peerDependenciesMeta": {
|
|
88
|
+
"zod": {
|
|
89
|
+
"optional": true
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"size-limit": [
|
|
93
|
+
{
|
|
94
|
+
"path": "./dist/index.mjs",
|
|
95
|
+
"limit": "8 KB"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"path": "./dist/zod.mjs",
|
|
99
|
+
"limit": "2 KB"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"engines": {
|
|
103
|
+
"node": ">=18.0.0"
|
|
104
|
+
}
|
|
105
|
+
}
|