@plasius/schema 1.0.18 → 1.1.1

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 (56) hide show
  1. package/README.md +140 -1
  2. package/dist/index.cjs +1934 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +391 -0
  5. package/dist/index.d.ts +391 -0
  6. package/dist/index.js +1883 -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 -186
  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 -86
  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 -119
  28. package/src/field.ts +0 -14
  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 -826
  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 -9
  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/name.OWASP.ts +0 -25
  41. package/src/validation/percentage.ISO80000-1.ts +0 -8
  42. package/src/validation/phone.E.164.ts +0 -9
  43. package/src/validation/richtext.OWASP.ts +0 -34
  44. package/src/validation/url.WHATWG.ts +0 -16
  45. package/src/validation/user.MS-GOOGLE-APPLE.ts +0 -31
  46. package/src/validation/uuid.RFC4122.ts +0 -10
  47. package/src/validation/version.SEMVER2.0.0.ts +0 -8
  48. package/tests/pii.test.ts +0 -139
  49. package/tests/schema.test.ts +0 -501
  50. package/tests/test-utils.ts +0 -97
  51. package/tests/validate.test.ts +0 -97
  52. package/tests/validation.test.ts +0 -98
  53. package/tsconfig.build.json +0 -19
  54. package/tsconfig.json +0 -7
  55. package/tsup.config.ts +0 -10
  56. package/vitest.config.js +0 -20
@@ -1,9 +0,0 @@
1
- /**
2
- * Validates a phone number string in strict E.164 format.
3
- * Returns true for strings like "+441632960960" (max 15 digits, starting with a '+').
4
- */
5
- export const validatePhone = (value: unknown): boolean => {
6
- if (typeof value !== "string") return false;
7
- const phoneRegex = /^\+[1-9]\d{1,14}$/; // E.164 format
8
- return phoneRegex.test(value);
9
- };
@@ -1,34 +0,0 @@
1
- /**
2
- * Validates rich text input to ensure it contains only safe HTML/Markdown.
3
- * Global Standard: OWASP HTML Sanitization Guidelines (2024).
4
- * This validator checks for dangerous patterns — does not sanitize — assumes text will be sanitized downstream.
5
- */
6
- export function validateRichText(value: unknown): boolean {
7
- if (typeof value !== "string") return false;
8
- const trimmed = value.trim();
9
- if (trimmed.length === 0) return true; // Allow empty rich text
10
-
11
- // Reject known dangerous tags
12
- if (
13
- /<(script|iframe|object|embed|style|link|meta|base|form|input|button|textarea|select)\b/i.test(
14
- trimmed
15
- )
16
- ) {
17
- return false;
18
- }
19
-
20
- // Reject javascript: links
21
- if (/javascript:/i.test(trimmed)) {
22
- return false;
23
- }
24
-
25
- // Reject event handlers (onload, onclick, etc)
26
- if (/on\w+=["']?/i.test(trimmed)) {
27
- return false;
28
- }
29
-
30
- // Optionally: limit max length (e.g. 10,000 chars)
31
- if (trimmed.length > 10000) return false;
32
-
33
- return true;
34
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Validates a URL string using the WHATWG URL API.
3
- * Accepts only 'http' or 'https' protocols.
4
- * Returns true if the URL is syntactically valid.
5
- */
6
- export const validateUrl = (value: unknown): boolean => {
7
- if (typeof value !== "string") return false;
8
- try {
9
- const url = new URL(value);
10
- // Enforce scheme:
11
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
12
- return true;
13
- } catch {
14
- return false;
15
- }
16
- };
@@ -1,31 +0,0 @@
1
- /**
2
- * Validates that a user ID is a valid `sub` from one of the supported identity providers.
3
- * Global Standard: OpenID Connect Core 1.0 `sub` claim.
4
- */
5
- export function validateUserId(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
- // Google: all digits
12
- const googlePattern = /^\d{21,22}$/;
13
-
14
- // Microsoft: UUID v4
15
- const microsoftPattern =
16
- /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
17
-
18
- // Apple: opaque, usually UUID but allow any printable string up to 255 chars
19
- const applePattern = /^[\w\-.]{6,255}$/; // Alphanumeric + - . _ (common safe characters)
20
-
21
- return (
22
- googlePattern.test(trimmed) ||
23
- microsoftPattern.test(trimmed) ||
24
- applePattern.test(trimmed)
25
- );
26
- }
27
-
28
- export function validateUserIdArray(value: unknown): boolean {
29
- if (!Array.isArray(value)) return false;
30
- return value.every(validateUserId);
31
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Validates a string against the RFC 4122 format for UUIDs.
3
- * Matches UUIDs of versions 1 to 5, e.g., "123e4567-e89b-12d3-a456-426614174000".
4
- */
5
- export const validateUUID = (value: unknown): boolean => {
6
- if (typeof value !== "string") return false;
7
- const uuidRegex =
8
- /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
9
- return uuidRegex.test(value);
10
- };
@@ -1,8 +0,0 @@
1
- /**
2
- * Validates that a version string conforms to Semantic Versioning (SemVer 2.0.0).
3
- * Global Standard: https://semver.org/
4
- */
5
- export function validateSemVer(value: unknown): boolean {
6
- if (typeof value !== "string") return false;
7
- return /^(\d+)\.(\d+)\.(\d+)$/.test(value);
8
- }
package/tests/pii.test.ts DELETED
@@ -1,139 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createSchema } from "../src/schema.js";
3
- import { field } from "../src/field.js";
4
- import { expectValid, expectInvalid } from "./test-utils.js";
5
-
6
- // --- PII: sanitizeForLog should clean output correctly ---
7
- describe("PII log sanitization", () => {
8
- const LogSchema = createSchema(
9
- {
10
- email: field.string().PID({
11
- classification: "high",
12
- action: "encrypt",
13
- logHandling: "redact",
14
- purpose: "user contact",
15
- }),
16
- sessionId: field.string().PID({
17
- classification: "low",
18
- action: "hash",
19
- logHandling: "pseudonym",
20
- purpose: "session correlation",
21
- }),
22
- nickname: field.string().PID({
23
- classification: "none",
24
- action: "none",
25
- logHandling: "plain",
26
- }),
27
- internalNote: field.string().PID({
28
- classification: "low",
29
- action: "clear",
30
- logHandling: "omit",
31
- purpose: "ops-only",
32
- }),
33
- },
34
- "LogSchema",
35
- { version: "1.0", piiEnforcement: "strict" }
36
- );
37
-
38
- it("should redact, pseudonymize, pass-through, and omit according to logHandling", () => {
39
- const input = {
40
- email: "a@b.com",
41
- sessionId: "abc123",
42
- nickname: "phil",
43
- internalNote: "sensitive",
44
- other: "visible-but-not-in-shape",
45
- } as const;
46
-
47
- const pseudo = (v: any) => `pseudo(${String(v).length})`;
48
- const out = LogSchema.sanitizeForLog(input as any, pseudo);
49
-
50
- // redact
51
- expect(out.email).toBe("[REDACTED]");
52
- // pseudonym
53
- expect(out.sessionId).toBe("pseudo(6)");
54
- // plain
55
- expect(out.nickname).toBe("phil");
56
- // omitted entirely
57
- expect(Object.prototype.hasOwnProperty.call(out, "internalNote")).toBe(
58
- false
59
- );
60
- // unknown fields are not included (shape-driven)
61
- expect(Object.prototype.hasOwnProperty.call(out, "other")).toBe(false);
62
-
63
- // ensure no storage artifacts leak into logs
64
- expect(Object.keys(out).some((k) => k.endsWith("Encrypted"))).toBe(false);
65
- expect(Object.keys(out).some((k) => k.endsWith("Hash"))).toBe(false);
66
- });
67
-
68
- it("should handle missing optional fields by simply excluding them from sanitized output", () => {
69
- const minimal = { email: "x@y.com", sessionId: "id" } as const;
70
- const out = LogSchema.sanitizeForLog(minimal as any, (v) => `p(${v})`);
71
- expect(out.email).toBe("[REDACTED]");
72
- expect(out.sessionId).toBe("p(id)");
73
- // nickname/internalNote omitted if not present
74
- expect(out.nickname).toBe(undefined);
75
- expect(Object.prototype.hasOwnProperty.call(out, "internalNote")).toBe(
76
- false
77
- );
78
- });
79
-
80
- // --- PII: auditing of shape metadata ---
81
- describe("PII auditing", () => {
82
- it("getPiiAudit should include only fields with classification !== 'none' and capture action/logHandling/purpose", () => {
83
- const audit = LogSchema.getPiiAudit();
84
- expect(audit).toEqual([
85
- {
86
- field: "email",
87
- classification: "high",
88
- action: "encrypt",
89
- logHandling: "redact",
90
- purpose: "user contact",
91
- },
92
- {
93
- field: "sessionId",
94
- classification: "low",
95
- action: "hash",
96
- logHandling: "pseudonym",
97
- purpose: "session correlation",
98
- },
99
- {
100
- field: "internalNote",
101
- classification: "low",
102
- action: "clear",
103
- logHandling: "omit",
104
- purpose: "ops-only",
105
- },
106
- ]);
107
- });
108
-
109
- it("getPiiAudit should not include fields with classification 'none' (e.g., nickname)", () => {
110
- const audit = LogSchema.getPiiAudit();
111
- const fields = audit?.map((a) => a.field);
112
- expect(fields?.includes("nickname")).toBe(false);
113
- expect(fields).toEqual(["email", "sessionId", "internalNote"]);
114
- });
115
-
116
- it("audit content should be independent of enforcement mode", () => {
117
- const AltSchema = createSchema(
118
- {
119
- email: field.string().PID({
120
- classification: "high",
121
- action: "encrypt",
122
- logHandling: "redact",
123
- }),
124
- },
125
- "Alt",
126
- { version: "1.0", piiEnforcement: "none" }
127
- );
128
- expect(AltSchema.getPiiAudit()).toEqual([
129
- {
130
- field: "email",
131
- classification: "high",
132
- action: "encrypt",
133
- logHandling: "redact",
134
- purpose: undefined,
135
- },
136
- ]);
137
- });
138
- });
139
- });