@plasius/schema 1.0.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/.eslintrc.cjs +7 -0
- package/.github/workflows/cd.yml +54 -0
- package/.github/workflows/ci.yml +16 -0
- package/.vscode/launch.json +15 -0
- package/CODE_OF_CONDUCT.md +79 -0
- package/CONTRIBUTORS.md +27 -0
- package/LICENSE +203 -0
- package/README.md +45 -0
- package/SECURITY.md +17 -0
- package/legal/CLA-REGISTRY.csv +2 -0
- package/legal/CLA.md +22 -0
- package/legal/CORPORATE_CLA.md +55 -0
- package/legal/INDIVIDUAL_CLA.md +91 -0
- package/package.json +48 -0
- package/src/components.ts +39 -0
- package/src/field.builder.ts +119 -0
- package/src/field.ts +14 -0
- package/src/index.ts +7 -0
- package/src/infer.ts +34 -0
- package/src/pii.ts +165 -0
- package/src/schema.ts +757 -0
- package/src/types.ts +156 -0
- package/src/validation/countryCode.ISO3166.ts +256 -0
- package/src/validation/currencyCode.ISO4217.ts +191 -0
- package/src/validation/dateTime.ISO8601.ts +9 -0
- package/src/validation/email.RFC5322.ts +9 -0
- package/src/validation/generalText.OWASP.ts +39 -0
- package/src/validation/index.ts +13 -0
- package/src/validation/name.OWASP.ts +25 -0
- package/src/validation/percentage.ISO80000-1.ts +8 -0
- package/src/validation/phone.E.164.ts +9 -0
- package/src/validation/richtext.OWASP.ts +34 -0
- package/src/validation/url.WHATWG.ts +16 -0
- package/src/validation/user.MS-GOOGLE-APPLE.ts +31 -0
- package/src/validation/uuid.RFC4122.ts +10 -0
- package/src/validation/version.SEMVER2.0.0.ts +8 -0
- package/tests/pii.test.ts +139 -0
- package/tests/schema.test.ts +501 -0
- package/tests/test-utils.ts +97 -0
- package/tests/validate.test.ts +97 -0
- package/tests/validation.test.ts +98 -0
- package/tsconfig.build.json +19 -0
- package/tsconfig.json +7 -0
- package/tsup.config.ts +10 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import FieldBuilder from "./field.builder.js";
|
|
2
|
+
import { Infer } from "./infer.js";
|
|
3
|
+
import { PII, PIIAction, PIIClassification, PIILogHandling } from "./pii.js";
|
|
4
|
+
|
|
5
|
+
export type FieldTypeMap = {
|
|
6
|
+
string: string;
|
|
7
|
+
number: number;
|
|
8
|
+
boolean: boolean;
|
|
9
|
+
object: Record<string, unknown>;
|
|
10
|
+
"string[]": string[];
|
|
11
|
+
"number[]": number[];
|
|
12
|
+
"boolean[]": boolean[];
|
|
13
|
+
"object[]": Record<string, unknown>[];
|
|
14
|
+
ref: RefEntityId;
|
|
15
|
+
"ref[]": RefEntityId[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type FieldType = keyof FieldTypeMap;
|
|
19
|
+
|
|
20
|
+
export type PIIEnforcement = "strict" | "warn" | "none";
|
|
21
|
+
|
|
22
|
+
export interface SchemaOptions {
|
|
23
|
+
version?: string;
|
|
24
|
+
table?: string;
|
|
25
|
+
schemaValidator?: (value: any) => boolean;
|
|
26
|
+
piiEnforcement?: PIIEnforcement; // How should PII be enforced?
|
|
27
|
+
}
|
|
28
|
+
export type SchemaShape = Record<string, FieldBuilder<any>>;
|
|
29
|
+
|
|
30
|
+
export interface FieldDefinition<T = unknown> {
|
|
31
|
+
type: FieldType;
|
|
32
|
+
__valueType?: T;
|
|
33
|
+
optional?: boolean;
|
|
34
|
+
immutable?: boolean;
|
|
35
|
+
description?: string;
|
|
36
|
+
refType?: string;
|
|
37
|
+
version?: string;
|
|
38
|
+
deprecated?: boolean;
|
|
39
|
+
deprecatedVersion?: string;
|
|
40
|
+
system?: boolean;
|
|
41
|
+
autoValidate?: boolean;
|
|
42
|
+
refPolicy?: "eager" | "lazy";
|
|
43
|
+
enum?: string[];
|
|
44
|
+
_shape?: SchemaShape;
|
|
45
|
+
pii?: PII;
|
|
46
|
+
validator?: (value: any) => boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ValidateCompositionOptions {
|
|
50
|
+
resolveEntity: (type: string, id: string) => Promise<any | null>;
|
|
51
|
+
validatorContext?: { visited: Set<string> };
|
|
52
|
+
maxDepth?: number;
|
|
53
|
+
log?: (msg: string) => void; // Optional for trace/debug
|
|
54
|
+
onlyFields?: string[]; // NEW
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Schema<T extends SchemaShape> {
|
|
58
|
+
//// The shape of the schema.
|
|
59
|
+
_shape: T;
|
|
60
|
+
|
|
61
|
+
//// System metadata about the schema
|
|
62
|
+
meta: { entityType: string; version: string };
|
|
63
|
+
|
|
64
|
+
//// Methods for schema validation
|
|
65
|
+
schemaValidator: (entity: Infer<T>) => boolean;
|
|
66
|
+
|
|
67
|
+
// Validate an input object against the schema
|
|
68
|
+
validate: (
|
|
69
|
+
input: unknown,
|
|
70
|
+
existing?: Record<string, any>
|
|
71
|
+
) => ValidationResult<Infer<T>>;
|
|
72
|
+
|
|
73
|
+
// Validate an input object against the schema, with options for composition validation
|
|
74
|
+
validateComposition: (
|
|
75
|
+
entity: Infer<T>,
|
|
76
|
+
options: ValidateCompositionOptions
|
|
77
|
+
) => Promise<void>;
|
|
78
|
+
|
|
79
|
+
//// Optional methods for schema metadata
|
|
80
|
+
|
|
81
|
+
// Get the tableName for this schema
|
|
82
|
+
tableName?: () => string | undefined; // Optional method to get the table name
|
|
83
|
+
|
|
84
|
+
//// 🔒 Optional methods for PII handling
|
|
85
|
+
|
|
86
|
+
// 🔒 Auto-prepare for read (decrypt PII)
|
|
87
|
+
prepareForRead(
|
|
88
|
+
stored: Record<string, any>,
|
|
89
|
+
decryptFn: (value: string) => any | null
|
|
90
|
+
): Record<string, any>;
|
|
91
|
+
|
|
92
|
+
// 🔒 Auto-prepare for storage (encrypt/hash PII)
|
|
93
|
+
prepareForStorage(
|
|
94
|
+
input: Record<string, any>,
|
|
95
|
+
encryptFn: (value: any) => string,
|
|
96
|
+
hashFn: (value: any) => string
|
|
97
|
+
): Record<string, any>;
|
|
98
|
+
|
|
99
|
+
// 🔒 Sanitize data for logging (e.g., redact PII)
|
|
100
|
+
sanitizeForLog(
|
|
101
|
+
data: Record<string, any>,
|
|
102
|
+
pseudonymFn: (value: any) => string
|
|
103
|
+
): Record<string, any>;
|
|
104
|
+
|
|
105
|
+
// 🔒 Get PII audit information
|
|
106
|
+
getPiiAudit(): Array<{
|
|
107
|
+
field: string;
|
|
108
|
+
classification: PIIClassification;
|
|
109
|
+
action: PIIAction;
|
|
110
|
+
logHandling?: PIILogHandling;
|
|
111
|
+
purpose?: string;
|
|
112
|
+
}> | null;
|
|
113
|
+
|
|
114
|
+
// 🔒 Scrub PII for deletion (e.g., clear or hash sensitive data)
|
|
115
|
+
scrubPiiForDelete(stored: Record<string, any>): Record<string, any>;
|
|
116
|
+
|
|
117
|
+
describe(): {
|
|
118
|
+
entityType: string;
|
|
119
|
+
version: string;
|
|
120
|
+
shape: Record<
|
|
121
|
+
string,
|
|
122
|
+
{
|
|
123
|
+
type: FieldType;
|
|
124
|
+
optional: boolean;
|
|
125
|
+
immutable: boolean;
|
|
126
|
+
description: string;
|
|
127
|
+
version: string;
|
|
128
|
+
deprecated: boolean;
|
|
129
|
+
deprecatedVersion: string | null;
|
|
130
|
+
system: boolean;
|
|
131
|
+
enum: string[] | null;
|
|
132
|
+
refType: string | null;
|
|
133
|
+
pii: PII | null;
|
|
134
|
+
}
|
|
135
|
+
>;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type DeepReadonly<T> = {
|
|
140
|
+
readonly [K in keyof T]: T[K] extends object
|
|
141
|
+
? T[K] extends (...args: any[]) => any
|
|
142
|
+
? T[K]
|
|
143
|
+
: DeepReadonly<T[K]>
|
|
144
|
+
: T[K];
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export interface ValidationResult<T> {
|
|
148
|
+
valid: boolean;
|
|
149
|
+
value?: DeepReadonly<T>;
|
|
150
|
+
errors?: string[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type RefEntityId<T extends string = string> = {
|
|
154
|
+
type: T;
|
|
155
|
+
id: string;
|
|
156
|
+
};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export const isoCountryCodes = new Set([
|
|
2
|
+
"AD",
|
|
3
|
+
"AE",
|
|
4
|
+
"AF",
|
|
5
|
+
"AG",
|
|
6
|
+
"AI",
|
|
7
|
+
"AL",
|
|
8
|
+
"AM",
|
|
9
|
+
"AO",
|
|
10
|
+
"AQ",
|
|
11
|
+
"AR",
|
|
12
|
+
"AS",
|
|
13
|
+
"AT",
|
|
14
|
+
"AU",
|
|
15
|
+
"AW",
|
|
16
|
+
"AX",
|
|
17
|
+
"AZ",
|
|
18
|
+
"BA",
|
|
19
|
+
"BB",
|
|
20
|
+
"BD",
|
|
21
|
+
"BE",
|
|
22
|
+
"BF",
|
|
23
|
+
"BG",
|
|
24
|
+
"BH",
|
|
25
|
+
"BI",
|
|
26
|
+
"BJ",
|
|
27
|
+
"BL",
|
|
28
|
+
"BM",
|
|
29
|
+
"BN",
|
|
30
|
+
"BO",
|
|
31
|
+
"BQ",
|
|
32
|
+
"BR",
|
|
33
|
+
"BS",
|
|
34
|
+
"BT",
|
|
35
|
+
"BV",
|
|
36
|
+
"BW",
|
|
37
|
+
"BY",
|
|
38
|
+
"BZ",
|
|
39
|
+
"CA",
|
|
40
|
+
"CC",
|
|
41
|
+
"CD",
|
|
42
|
+
"CF",
|
|
43
|
+
"CG",
|
|
44
|
+
"CH",
|
|
45
|
+
"CI",
|
|
46
|
+
"CK",
|
|
47
|
+
"CL",
|
|
48
|
+
"CM",
|
|
49
|
+
"CN",
|
|
50
|
+
"CO",
|
|
51
|
+
"CR",
|
|
52
|
+
"CU",
|
|
53
|
+
"CV",
|
|
54
|
+
"CW",
|
|
55
|
+
"CX",
|
|
56
|
+
"CY",
|
|
57
|
+
"CZ",
|
|
58
|
+
"DE",
|
|
59
|
+
"DJ",
|
|
60
|
+
"DK",
|
|
61
|
+
"DM",
|
|
62
|
+
"DO",
|
|
63
|
+
"DZ",
|
|
64
|
+
"EC",
|
|
65
|
+
"EE",
|
|
66
|
+
"EG",
|
|
67
|
+
"EH",
|
|
68
|
+
"ER",
|
|
69
|
+
"ES",
|
|
70
|
+
"ET",
|
|
71
|
+
"FI",
|
|
72
|
+
"FJ",
|
|
73
|
+
"FM",
|
|
74
|
+
"FO",
|
|
75
|
+
"FR",
|
|
76
|
+
"GA",
|
|
77
|
+
"GB",
|
|
78
|
+
"GD",
|
|
79
|
+
"GE",
|
|
80
|
+
"GF",
|
|
81
|
+
"GG",
|
|
82
|
+
"GH",
|
|
83
|
+
"GI",
|
|
84
|
+
"GL",
|
|
85
|
+
"GM",
|
|
86
|
+
"GN",
|
|
87
|
+
"GP",
|
|
88
|
+
"GQ",
|
|
89
|
+
"GR",
|
|
90
|
+
"GT",
|
|
91
|
+
"GU",
|
|
92
|
+
"GW",
|
|
93
|
+
"GY",
|
|
94
|
+
"HK",
|
|
95
|
+
"HM",
|
|
96
|
+
"HN",
|
|
97
|
+
"HR",
|
|
98
|
+
"HT",
|
|
99
|
+
"HU",
|
|
100
|
+
"ID",
|
|
101
|
+
"IE",
|
|
102
|
+
"IL",
|
|
103
|
+
"IM",
|
|
104
|
+
"IN",
|
|
105
|
+
"IO",
|
|
106
|
+
"IQ",
|
|
107
|
+
"IR",
|
|
108
|
+
"IS",
|
|
109
|
+
"IT",
|
|
110
|
+
"JE",
|
|
111
|
+
"JM",
|
|
112
|
+
"JO",
|
|
113
|
+
"JP",
|
|
114
|
+
"KE",
|
|
115
|
+
"KG",
|
|
116
|
+
"KH",
|
|
117
|
+
"KI",
|
|
118
|
+
"KM",
|
|
119
|
+
"KN",
|
|
120
|
+
"KP",
|
|
121
|
+
"KR",
|
|
122
|
+
"KW",
|
|
123
|
+
"KY",
|
|
124
|
+
"KZ",
|
|
125
|
+
"LA",
|
|
126
|
+
"LB",
|
|
127
|
+
"LC",
|
|
128
|
+
"LI",
|
|
129
|
+
"LK",
|
|
130
|
+
"LR",
|
|
131
|
+
"LS",
|
|
132
|
+
"LT",
|
|
133
|
+
"LU",
|
|
134
|
+
"LV",
|
|
135
|
+
"LY",
|
|
136
|
+
"MA",
|
|
137
|
+
"MC",
|
|
138
|
+
"MD",
|
|
139
|
+
"ME",
|
|
140
|
+
"MF",
|
|
141
|
+
"MG",
|
|
142
|
+
"MH",
|
|
143
|
+
"MK",
|
|
144
|
+
"ML",
|
|
145
|
+
"MM",
|
|
146
|
+
"MN",
|
|
147
|
+
"MO",
|
|
148
|
+
"MP",
|
|
149
|
+
"MQ",
|
|
150
|
+
"MR",
|
|
151
|
+
"MS",
|
|
152
|
+
"MT",
|
|
153
|
+
"MU",
|
|
154
|
+
"MV",
|
|
155
|
+
"MW",
|
|
156
|
+
"MX",
|
|
157
|
+
"MY",
|
|
158
|
+
"MZ",
|
|
159
|
+
"NA",
|
|
160
|
+
"NC",
|
|
161
|
+
"NE",
|
|
162
|
+
"NF",
|
|
163
|
+
"NG",
|
|
164
|
+
"NI",
|
|
165
|
+
"NL",
|
|
166
|
+
"NO",
|
|
167
|
+
"NP",
|
|
168
|
+
"NR",
|
|
169
|
+
"NU",
|
|
170
|
+
"NZ",
|
|
171
|
+
"OM",
|
|
172
|
+
"PA",
|
|
173
|
+
"PE",
|
|
174
|
+
"PF",
|
|
175
|
+
"PG",
|
|
176
|
+
"PH",
|
|
177
|
+
"PK",
|
|
178
|
+
"PL",
|
|
179
|
+
"PM",
|
|
180
|
+
"PN",
|
|
181
|
+
"PR",
|
|
182
|
+
"PT",
|
|
183
|
+
"PW",
|
|
184
|
+
"PY",
|
|
185
|
+
"QA",
|
|
186
|
+
"RE",
|
|
187
|
+
"RO",
|
|
188
|
+
"RS",
|
|
189
|
+
"RU",
|
|
190
|
+
"RW",
|
|
191
|
+
"SA",
|
|
192
|
+
"SB",
|
|
193
|
+
"SC",
|
|
194
|
+
"SD",
|
|
195
|
+
"SE",
|
|
196
|
+
"SG",
|
|
197
|
+
"SH",
|
|
198
|
+
"SI",
|
|
199
|
+
"SJ",
|
|
200
|
+
"SK",
|
|
201
|
+
"SL",
|
|
202
|
+
"SM",
|
|
203
|
+
"SN",
|
|
204
|
+
"SO",
|
|
205
|
+
"SR",
|
|
206
|
+
"SS",
|
|
207
|
+
"ST",
|
|
208
|
+
"SV",
|
|
209
|
+
"SX",
|
|
210
|
+
"SY",
|
|
211
|
+
"SZ",
|
|
212
|
+
"TC",
|
|
213
|
+
"TD",
|
|
214
|
+
"TF",
|
|
215
|
+
"TG",
|
|
216
|
+
"TH",
|
|
217
|
+
"TJ",
|
|
218
|
+
"TK",
|
|
219
|
+
"TL",
|
|
220
|
+
"TM",
|
|
221
|
+
"TN",
|
|
222
|
+
"TO",
|
|
223
|
+
"TR",
|
|
224
|
+
"TT",
|
|
225
|
+
"TV",
|
|
226
|
+
"TZ",
|
|
227
|
+
"UA",
|
|
228
|
+
"UG",
|
|
229
|
+
"UM",
|
|
230
|
+
"US",
|
|
231
|
+
"UY",
|
|
232
|
+
"UZ",
|
|
233
|
+
"VA",
|
|
234
|
+
"VC",
|
|
235
|
+
"VE",
|
|
236
|
+
"VG",
|
|
237
|
+
"VI",
|
|
238
|
+
"VN",
|
|
239
|
+
"VU",
|
|
240
|
+
"WF",
|
|
241
|
+
"WS",
|
|
242
|
+
"YE",
|
|
243
|
+
"YT",
|
|
244
|
+
"ZA",
|
|
245
|
+
"ZM",
|
|
246
|
+
"ZW",
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validates whether a string is a valid ISO 3166-1 alpha-2 country code.
|
|
251
|
+
* Performs a case-insensitive lookup against a predefined set of known codes.
|
|
252
|
+
*/
|
|
253
|
+
export const validateCountryCode = (value: unknown): boolean => {
|
|
254
|
+
if (typeof value !== "string") return false;
|
|
255
|
+
return isoCountryCodes.has(value.toUpperCase());
|
|
256
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export const isoCurrencyCodes = new Set([
|
|
2
|
+
"AED",
|
|
3
|
+
"AFN",
|
|
4
|
+
"ALL",
|
|
5
|
+
"AMD",
|
|
6
|
+
"ANG",
|
|
7
|
+
"AOA",
|
|
8
|
+
"ARS",
|
|
9
|
+
"AUD",
|
|
10
|
+
"AWG",
|
|
11
|
+
"AZN",
|
|
12
|
+
"BAM",
|
|
13
|
+
"BBD",
|
|
14
|
+
"BDT",
|
|
15
|
+
"BGN",
|
|
16
|
+
"BHD",
|
|
17
|
+
"BIF",
|
|
18
|
+
"BMD",
|
|
19
|
+
"BND",
|
|
20
|
+
"BOB",
|
|
21
|
+
"BOV",
|
|
22
|
+
"BRL",
|
|
23
|
+
"BSD",
|
|
24
|
+
"BTN",
|
|
25
|
+
"BWP",
|
|
26
|
+
"BYN",
|
|
27
|
+
"BZD",
|
|
28
|
+
"CAD",
|
|
29
|
+
"CDF",
|
|
30
|
+
"CHE",
|
|
31
|
+
"CHF",
|
|
32
|
+
"CHW",
|
|
33
|
+
"CLF",
|
|
34
|
+
"CLP",
|
|
35
|
+
"CNY",
|
|
36
|
+
"COP",
|
|
37
|
+
"COU",
|
|
38
|
+
"CRC",
|
|
39
|
+
"CUC",
|
|
40
|
+
"CUP",
|
|
41
|
+
"CVE",
|
|
42
|
+
"CZK",
|
|
43
|
+
"DJF",
|
|
44
|
+
"DKK",
|
|
45
|
+
"DOP",
|
|
46
|
+
"DZD",
|
|
47
|
+
"EGP",
|
|
48
|
+
"ERN",
|
|
49
|
+
"ETB",
|
|
50
|
+
"EUR",
|
|
51
|
+
"FJD",
|
|
52
|
+
"FKP",
|
|
53
|
+
"GBP",
|
|
54
|
+
"GEL",
|
|
55
|
+
"GHS",
|
|
56
|
+
"GIP",
|
|
57
|
+
"GMD",
|
|
58
|
+
"GNF",
|
|
59
|
+
"GTQ",
|
|
60
|
+
"GYD",
|
|
61
|
+
"HKD",
|
|
62
|
+
"HNL",
|
|
63
|
+
"HRK",
|
|
64
|
+
"HTG",
|
|
65
|
+
"HUF",
|
|
66
|
+
"IDR",
|
|
67
|
+
"ILS",
|
|
68
|
+
"INR",
|
|
69
|
+
"IQD",
|
|
70
|
+
"IRR",
|
|
71
|
+
"ISK",
|
|
72
|
+
"JMD",
|
|
73
|
+
"JOD",
|
|
74
|
+
"JPY",
|
|
75
|
+
"KES",
|
|
76
|
+
"KGS",
|
|
77
|
+
"KHR",
|
|
78
|
+
"KMF",
|
|
79
|
+
"KPW",
|
|
80
|
+
"KRW",
|
|
81
|
+
"KWD",
|
|
82
|
+
"KYD",
|
|
83
|
+
"KZT",
|
|
84
|
+
"LAK",
|
|
85
|
+
"LBP",
|
|
86
|
+
"LKR",
|
|
87
|
+
"LRD",
|
|
88
|
+
"LSL",
|
|
89
|
+
"LYD",
|
|
90
|
+
"MAD",
|
|
91
|
+
"MDL",
|
|
92
|
+
"MGA",
|
|
93
|
+
"MKD",
|
|
94
|
+
"MMK",
|
|
95
|
+
"MNT",
|
|
96
|
+
"MOP",
|
|
97
|
+
"MRU",
|
|
98
|
+
"MUR",
|
|
99
|
+
"MVR",
|
|
100
|
+
"MWK",
|
|
101
|
+
"MXN",
|
|
102
|
+
"MXV",
|
|
103
|
+
"MYR",
|
|
104
|
+
"MZN",
|
|
105
|
+
"NAD",
|
|
106
|
+
"NGN",
|
|
107
|
+
"NIO",
|
|
108
|
+
"NOK",
|
|
109
|
+
"NPR",
|
|
110
|
+
"NZD",
|
|
111
|
+
"OMR",
|
|
112
|
+
"PAB",
|
|
113
|
+
"PEN",
|
|
114
|
+
"PGK",
|
|
115
|
+
"PHP",
|
|
116
|
+
"PKR",
|
|
117
|
+
"PLN",
|
|
118
|
+
"PYG",
|
|
119
|
+
"QAR",
|
|
120
|
+
"RON",
|
|
121
|
+
"RSD",
|
|
122
|
+
"RUB",
|
|
123
|
+
"RWF",
|
|
124
|
+
"SAR",
|
|
125
|
+
"SBD",
|
|
126
|
+
"SCR",
|
|
127
|
+
"SDG",
|
|
128
|
+
"SEK",
|
|
129
|
+
"SGD",
|
|
130
|
+
"SHP",
|
|
131
|
+
"SLL",
|
|
132
|
+
"SOS",
|
|
133
|
+
"SRD",
|
|
134
|
+
"SSP",
|
|
135
|
+
"STN",
|
|
136
|
+
"SVC",
|
|
137
|
+
"SYP",
|
|
138
|
+
"SZL",
|
|
139
|
+
"THB",
|
|
140
|
+
"TJS",
|
|
141
|
+
"TMT",
|
|
142
|
+
"TND",
|
|
143
|
+
"TOP",
|
|
144
|
+
"TRY",
|
|
145
|
+
"TTD",
|
|
146
|
+
"TWD",
|
|
147
|
+
"TZS",
|
|
148
|
+
"UAH",
|
|
149
|
+
"UGX",
|
|
150
|
+
"USD",
|
|
151
|
+
"USN",
|
|
152
|
+
"UYI",
|
|
153
|
+
"UYU",
|
|
154
|
+
"UYW",
|
|
155
|
+
"UZS",
|
|
156
|
+
"VES",
|
|
157
|
+
"VND",
|
|
158
|
+
"VUV",
|
|
159
|
+
"WST",
|
|
160
|
+
"XAF",
|
|
161
|
+
"XAG",
|
|
162
|
+
"XAU",
|
|
163
|
+
"XBA",
|
|
164
|
+
"XBB",
|
|
165
|
+
"XBC",
|
|
166
|
+
"XBD",
|
|
167
|
+
"XCD",
|
|
168
|
+
"XDR",
|
|
169
|
+
"XOF",
|
|
170
|
+
"XPD",
|
|
171
|
+
"XPF",
|
|
172
|
+
"XPT",
|
|
173
|
+
"XSU",
|
|
174
|
+
"XTS",
|
|
175
|
+
"XUA",
|
|
176
|
+
"XXX",
|
|
177
|
+
"YER",
|
|
178
|
+
"ZAR",
|
|
179
|
+
"ZMW",
|
|
180
|
+
"ZWL",
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Validates whether a string is a valid ISO 4217 currency code.
|
|
186
|
+
* Performs a case-insensitive lookup against a predefined set of known codes.
|
|
187
|
+
*/
|
|
188
|
+
export const validateCurrencyCode = (value: unknown): boolean => {
|
|
189
|
+
if (typeof value !== "string") return false;
|
|
190
|
+
return isoCurrencyCodes.has(value.toUpperCase());
|
|
191
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates whether a string is a properly formatted ISO 8601 datetime.
|
|
3
|
+
* Ensures the string parses as a valid Date and matches the canonical toISOString() format.
|
|
4
|
+
*/
|
|
5
|
+
export const validateDateTimeISO = (value: unknown): boolean => {
|
|
6
|
+
if (typeof value !== "string") return false;
|
|
7
|
+
const date = new Date(value);
|
|
8
|
+
return !isNaN(date.getTime()) && value === date.toISOString();
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates an email string using a simplified RFC 5322-compliant regex.
|
|
3
|
+
* Returns true if the input is a string in the format `name@domain.tld`.
|
|
4
|
+
*/
|
|
5
|
+
export const validateEmail = (value: unknown): boolean => {
|
|
6
|
+
if (typeof value !== "string") return false;
|
|
7
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
8
|
+
return emailRegex.test(value);
|
|
9
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates that a text field is safe for storage (per OWASP guidelines).
|
|
3
|
+
* Applies to general text fields: names, descriptions, titles, etc.
|
|
4
|
+
* Global Standard: OWASP Input Validation Cheat Sheet (2024)
|
|
5
|
+
*/
|
|
6
|
+
export function validateSafeText(value: unknown): boolean {
|
|
7
|
+
if (typeof value !== "string") return false;
|
|
8
|
+
|
|
9
|
+
// Trimmed version should not be empty
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
if (trimmed.length === 0) return false;
|
|
12
|
+
|
|
13
|
+
// Reject control chars
|
|
14
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
15
|
+
const code = trimmed.codePointAt(i);
|
|
16
|
+
if (code !== undefined && (code >= 0x00 && code <= 0x1F || code === 0x7F)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Reject dangerous characters
|
|
22
|
+
if (/['"<>\\{}();]/.test(trimmed)) return false;
|
|
23
|
+
|
|
24
|
+
// Reject SQL-style injection patterns
|
|
25
|
+
if (
|
|
26
|
+
/(--|\b(SELECT|UPDATE|DELETE|INSERT|DROP|ALTER|EXEC|UNION|GRANT|REVOKE)\b|\/\*|\*\/|@@)/i.test(
|
|
27
|
+
trimmed
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
return false;
|
|
31
|
+
|
|
32
|
+
// Reject null char
|
|
33
|
+
if (trimmed.includes("\u0000")) return false;
|
|
34
|
+
|
|
35
|
+
// Optional: limit length
|
|
36
|
+
if (trimmed.length > 1024) return false;
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./email.RFC5322.js";
|
|
2
|
+
export * from "./phone.E.164.js";
|
|
3
|
+
export * from "./url.WHATWG.js";
|
|
4
|
+
export * from "./uuid.RFC4122.js";
|
|
5
|
+
export * from "./dateTime.ISO8601.js";
|
|
6
|
+
export * from "./countryCode.ISO3166.js";
|
|
7
|
+
export * from "./currencyCode.ISO4217.js";
|
|
8
|
+
export * from "./generalText.OWASP.js";
|
|
9
|
+
export * from "./version.SEMVER2.0.0.js";
|
|
10
|
+
export * from "./percentage.ISO80000-1.js";
|
|
11
|
+
export * from "./richtext.OWASP.js";
|
|
12
|
+
export * from "./name.OWASP.js";
|
|
13
|
+
export * from "./user.MS-GOOGLE-APPLE.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates that a name is safe, culturally inclusive, and matches global best practice.
|
|
3
|
+
* Global Standard: OWASP Input Validation Cheat Sheet + ICAO Doc 9303 + IETF PRECIS
|
|
4
|
+
*/
|
|
5
|
+
export function validateName(value: unknown): boolean {
|
|
6
|
+
if (typeof value !== "string") return false;
|
|
7
|
+
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
if (trimmed.length === 0) return false;
|
|
10
|
+
|
|
11
|
+
// Limit length (ISO guidance: max 256 is typical)
|
|
12
|
+
if (trimmed.length > 256) return false;
|
|
13
|
+
|
|
14
|
+
// Reject ASCII control chars (U+0000–U+001F and U+007F)
|
|
15
|
+
for (const ch of trimmed) {
|
|
16
|
+
const cp = ch.codePointAt(0)!;
|
|
17
|
+
if ((cp >= 0x00 && cp <= 0x1F) || cp === 0x7F) return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Core pattern
|
|
21
|
+
const namePattern = /^[\p{L}\p{M}'\- ]+$/u;
|
|
22
|
+
if (!namePattern.test(trimmed)) return false;
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates that a number is a percentage value (0 to 100 inclusive).
|
|
3
|
+
* Global Standard: ISO 80000-1 percentage definition.
|
|
4
|
+
*/
|
|
5
|
+
export function validatePercentage(value: unknown): boolean {
|
|
6
|
+
if (typeof value !== "number") return false;
|
|
7
|
+
return value >= 0 && value <= 100;
|
|
8
|
+
}
|