@plasius/schema 1.1.0 → 1.2.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.
Files changed (59) hide show
  1. package/README.md +31 -18
  2. package/dist/index.cjs +2370 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +627 -0
  5. package/dist/index.d.ts +627 -0
  6. package/dist/index.js +2308 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +18 -6
  9. package/.eslintrc.cjs +0 -7
  10. package/.github/workflows/cd.yml +0 -236
  11. package/.github/workflows/ci.yml +0 -16
  12. package/.nvmrc +0 -1
  13. package/.vscode/launch.json +0 -15
  14. package/CHANGELOG.md +0 -120
  15. package/CODE_OF_CONDUCT.md +0 -79
  16. package/CONTRIBUTING.md +0 -201
  17. package/CONTRIBUTORS.md +0 -27
  18. package/SECURITY.md +0 -17
  19. package/docs/adrs/adr-0001: schema.md +0 -45
  20. package/docs/adrs/adr-template.md +0 -67
  21. package/legal/CLA-REGISTRY.csv +0 -2
  22. package/legal/CLA.md +0 -22
  23. package/legal/CORPORATE_CLA.md +0 -57
  24. package/legal/INDIVIDUAL_CLA.md +0 -91
  25. package/sbom.cdx.json +0 -66
  26. package/src/components.ts +0 -39
  27. package/src/field.builder.ts +0 -239
  28. package/src/field.ts +0 -153
  29. package/src/index.ts +0 -7
  30. package/src/infer.ts +0 -34
  31. package/src/pii.ts +0 -165
  32. package/src/schema.ts +0 -893
  33. package/src/types.ts +0 -156
  34. package/src/validation/countryCode.ISO3166.ts +0 -256
  35. package/src/validation/currencyCode.ISO4217.ts +0 -191
  36. package/src/validation/dateTime.ISO8601.ts +0 -60
  37. package/src/validation/email.RFC5322.ts +0 -9
  38. package/src/validation/generalText.OWASP.ts +0 -39
  39. package/src/validation/index.ts +0 -13
  40. package/src/validation/languageCode.BCP47.ts +0 -299
  41. package/src/validation/name.OWASP.ts +0 -25
  42. package/src/validation/percentage.ISO80000-1.ts +0 -8
  43. package/src/validation/phone.E.164.ts +0 -9
  44. package/src/validation/richtext.OWASP.ts +0 -34
  45. package/src/validation/url.WHATWG.ts +0 -16
  46. package/src/validation/user.MS-GOOGLE-APPLE.ts +0 -31
  47. package/src/validation/uuid.RFC4122.ts +0 -10
  48. package/src/validation/version.SEMVER2.0.0.ts +0 -10
  49. package/tests/field.builder.test.ts +0 -81
  50. package/tests/fields.test.ts +0 -213
  51. package/tests/pii.test.ts +0 -139
  52. package/tests/schema.test.ts +0 -501
  53. package/tests/test-utils.ts +0 -97
  54. package/tests/validate.test.ts +0 -97
  55. package/tests/validation.test.ts +0 -98
  56. package/tsconfig.build.json +0 -19
  57. package/tsconfig.json +0 -7
  58. package/tsup.config.ts +0 -10
  59. package/vitest.config.js +0 -20
package/src/types.ts DELETED
@@ -1,156 +0,0 @@
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
- };
@@ -1,256 +0,0 @@
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
- };
@@ -1,191 +0,0 @@
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
- };
@@ -1,60 +0,0 @@
1
- /**
2
- * Validates whether a string is a properly formatted ISO 8601 datetime, date, or time.
3
- *
4
- * @param value - The value to validate.
5
- * @param options - Optional settings for validation mode.
6
- * @param options.mode - The mode of validation:
7
- * - "datetime" (default): Checks full ISO 8601 datetime and equality with toISOString().
8
- * - "date": Validates that the string is in YYYY-MM-DD format and represents a valid date.
9
- * - "time": Validates that the string matches a valid HH:MM:SS(.sss)?Z? ISO 8601 time pattern.
10
- * @returns True if the value is valid according to the specified mode; otherwise, false.
11
- */
12
- export const validateDateTimeISO = (
13
- value: unknown,
14
- options?: { mode?: "datetime" | "date" | "time" }
15
- ): boolean => {
16
- const mode = options?.mode ?? "datetime";
17
-
18
- if (typeof value !== "string") return false;
19
-
20
- if (mode === "datetime") {
21
- // Strict ISO 8601 date-time: YYYY-MM-DDTHH:mm:ss(.fraction)?(Z|±HH:MM)
22
- const isoDateTimeRegex =
23
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
24
-
25
- if (!isoDateTimeRegex.test(value)) return false;
26
-
27
- const date = new Date(value);
28
- if (Number.isNaN(date.getTime())) return false;
29
-
30
- // We purposely do NOT require `value === date.toISOString()` because
31
- // valid ISO8601 inputs may include offsets (e.g., "+01:00") or omit
32
- // milliseconds, both of which produce a different canonical ISO string.
33
- // The regex ensures strict shape; Date parsing ensures it is a real moment.
34
- return true;
35
- }
36
-
37
- if (mode === "date") {
38
- // YYYY-MM-DD format
39
- const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
40
- if (!dateRegex.test(value)) return false;
41
- const date = new Date(value);
42
- if (isNaN(date.getTime())) return false;
43
- // Ensure the date parts match exactly (to avoid 2023-02-30 being accepted)
44
- const [year, month, day] = value.split("-").map(Number);
45
- return (
46
- date.getUTCFullYear() === year &&
47
- date.getUTCMonth() + 1 === month &&
48
- date.getUTCDate() === day
49
- );
50
- }
51
-
52
- if (mode === "time") {
53
- // HH:MM:SS(.sss)?Z?
54
- // Hours: 00-23, Minutes: 00-59, Seconds: 00-59, optional fractional seconds, optional Z
55
- const timeRegex = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?Z?$/;
56
- return timeRegex.test(value);
57
- }
58
-
59
- return false;
60
- };
@@ -1,9 +0,0 @@
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
- };
@@ -1,39 +0,0 @@
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
- }