@classytic/validation 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/README.md +45 -0
- package/dist/barcode.d.ts +99 -0
- package/dist/barcode.js +174 -0
- package/dist/identifiers.d.ts +26 -0
- package/dist/identifiers.js +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Classytic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @classytic/validation
|
|
2
|
+
|
|
3
|
+
Isomorphic **zod** validation schemas shared server↔client. Framework-agnostic
|
|
4
|
+
(`zod` is the only peer), so the same rules run in the catalog kernel (Arc
|
|
5
|
+
auto-CRUD, repository writes) and in admin/storefront forms — one
|
|
6
|
+
implementation, zero drift.
|
|
7
|
+
|
|
8
|
+
**Charter:** zod-based validation shared between server and client lives here.
|
|
9
|
+
Zod-free value objects / pure functions belong in `@classytic/primitives`;
|
|
10
|
+
wire/transport types in `@classytic/repo-core`.
|
|
11
|
+
|
|
12
|
+
## Modules
|
|
13
|
+
|
|
14
|
+
### `@classytic/validation/barcode`
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {
|
|
18
|
+
validateGTIN,
|
|
19
|
+
validateISBN,
|
|
20
|
+
detectBarcodeFormat,
|
|
21
|
+
optionalScannableBarcodeSchema,
|
|
22
|
+
} from "@classytic/validation/barcode";
|
|
23
|
+
|
|
24
|
+
validateGTIN("4006381333931"); // true
|
|
25
|
+
detectBarcodeFormat("9780306406157"); // "ISBN13"
|
|
26
|
+
const product = z.object({ barcode: optionalScannableBarcodeSchema });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Format | Length | Checksum |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| EAN-13 | 13 | mod-10 (1,3,1,3…) |
|
|
32
|
+
| EAN-8 | 8 | mod-10 (3,1,3,1…) |
|
|
33
|
+
| UPC-A | 12 | mod-10 (3,1,3,1…) |
|
|
34
|
+
| GTIN-14 | 14 | mod-10 (3,1,3,1…) |
|
|
35
|
+
| ISBN-10 | 10 (or 9+`X`) | mod-11 |
|
|
36
|
+
| ISBN-13 | 13 (`978`/`979`) | EAN-13 |
|
|
37
|
+
| CODE128 | ≥4 | printable ASCII (no checksum) |
|
|
38
|
+
|
|
39
|
+
### `@classytic/validation/identifiers`
|
|
40
|
+
|
|
41
|
+
`productIdentifiersSchema` — a product's standard IDs (`gtin`/`upc`/`ean`/`isbn`
|
|
42
|
+
checksum-validated, `mpn`/`plu` length-only, `custom` open map).
|
|
43
|
+
|
|
44
|
+
Both modules are also re-exported flat from the package root
|
|
45
|
+
(`@classytic/validation`).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/barcode.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Barcode primitives — pure checksum + format detection.
|
|
5
|
+
*
|
|
6
|
+
* These are framework-agnostic predicates. Zod schemas in this file compose
|
|
7
|
+
* them via `.refine()`/`.superRefine()` so the same validation runs on the
|
|
8
|
+
* server (Arc auto-CRUD, Express, Nest, direct `repository.create()`) AND in
|
|
9
|
+
* the browser (admin product forms, storefront) — one implementation, zero
|
|
10
|
+
* drift.
|
|
11
|
+
*
|
|
12
|
+
* Format coverage (the formats POS scanners actually emit):
|
|
13
|
+
* - EAN-13 — 13 digits, mod-10 with positions weighted 1,3,1,3,…
|
|
14
|
+
* - EAN-8 — 8 digits, mod-10 with positions weighted 3,1,3,1,…
|
|
15
|
+
* - UPC-A — 12 digits, mod-10 with positions weighted 3,1,3,1,…
|
|
16
|
+
* - GTIN-14 — 14 digits, mod-10 with positions weighted 3,1,3,1,…
|
|
17
|
+
* - ISBN-10 — 10 digits or 9 digits + 'X', mod-11 weighted 10..1
|
|
18
|
+
* - ISBN-13 — 13 digits with '978'/'979' prefix, EAN-13 checksum
|
|
19
|
+
*
|
|
20
|
+
* For non-numeric scan codes (CODE128, internal alphanumerics) we accept
|
|
21
|
+
* any printable ASCII >= 4 chars — JsBarcode and most retail scanners
|
|
22
|
+
* round-trip those losslessly.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* EAN-13 mod-10 checksum.
|
|
26
|
+
*
|
|
27
|
+
* Sum of digits at odd positions × 1 + digits at even positions × 3, then
|
|
28
|
+
* the check digit is `(10 - sum % 10) % 10`. Used for EAN-13 and ISBN-13.
|
|
29
|
+
*/
|
|
30
|
+
declare function validateEAN13(value: string): boolean;
|
|
31
|
+
declare function validateUPCA(value: string): boolean;
|
|
32
|
+
declare function validateEAN8(value: string): boolean;
|
|
33
|
+
declare function validateGTIN14(value: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* GTIN — accepts 8, 12, 13, or 14 digits and routes to the corresponding
|
|
36
|
+
* mod-10 validator. GTIN is the umbrella standard; EAN-13 is GTIN-13 with
|
|
37
|
+
* a leading zero, UPC-A is GTIN-12 with a leading zero, etc.
|
|
38
|
+
*/
|
|
39
|
+
declare function validateGTIN(value: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* ISBN-10 mod-11 checksum. Last position can be 'X' representing 10.
|
|
42
|
+
*/
|
|
43
|
+
declare function validateISBN10(value: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* ISBN-13 — must start with 978 or 979 and pass the EAN-13 checksum.
|
|
46
|
+
*/
|
|
47
|
+
declare function validateISBN13(value: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Accepts ISBN-10 or ISBN-13. Allows hyphens for human readability — they
|
|
50
|
+
* are stripped before validation. Caller decides whether to persist the
|
|
51
|
+
* stripped or original form.
|
|
52
|
+
*/
|
|
53
|
+
declare function validateISBN(value: string): boolean;
|
|
54
|
+
type BarcodeFormat = 'EAN13' | 'EAN8' | 'UPCA' | 'GTIN14' | 'ISBN10' | 'ISBN13' | 'CODE128' | 'UNKNOWN';
|
|
55
|
+
/**
|
|
56
|
+
* Detect the most specific scannable format that matches `value`.
|
|
57
|
+
* Returns `'UNKNOWN'` for values that don't match any standard format.
|
|
58
|
+
*/
|
|
59
|
+
declare function detectBarcodeFormat(value: string): BarcodeFormat;
|
|
60
|
+
/**
|
|
61
|
+
* Validate any user-entered scannable barcode value.
|
|
62
|
+
*
|
|
63
|
+
* - Empty string → invalid (callers should mark the field optional and
|
|
64
|
+
* skip empties before calling).
|
|
65
|
+
* - Any recognised format → must pass its checksum.
|
|
66
|
+
* - Numeric strings of an unrecognised length (9, 10, 11, 14+ digits not
|
|
67
|
+
* matching ISBN/GTIN-14) → rejected — POS scanners can't read them.
|
|
68
|
+
* - Printable ASCII ≥ 4 chars → accepted as CODE128.
|
|
69
|
+
*
|
|
70
|
+
* Returns `null` for valid input, otherwise a human-readable reason.
|
|
71
|
+
*/
|
|
72
|
+
declare function validateScannableBarcode(value: string): string | null;
|
|
73
|
+
/**
|
|
74
|
+
* A scannable barcode value. Empty strings are rejected — callers wanting
|
|
75
|
+
* an optional field wrap this in `.optional()` (or pass `undefined`/omit
|
|
76
|
+
* the field). Length and checksum errors surface a human-readable reason
|
|
77
|
+
* via Zod's standard error message channel.
|
|
78
|
+
*/
|
|
79
|
+
declare const scannableBarcodeSchema: z.ZodString;
|
|
80
|
+
/**
|
|
81
|
+
* Optional variant of the above. Use this on schema fields where the
|
|
82
|
+
* barcode may be unset — `barcode?: string` style.
|
|
83
|
+
*/
|
|
84
|
+
declare const optionalScannableBarcodeSchema: z.ZodOptional<z.ZodString>;
|
|
85
|
+
/** GTIN — 8/12/13/14 digits, checksum-validated. */
|
|
86
|
+
declare const gtinSchema: z.ZodString;
|
|
87
|
+
/** UPC-A — 12 digits, mod-10 checksum. */
|
|
88
|
+
declare const upcaSchema: z.ZodString;
|
|
89
|
+
/** EAN-13 — 13 digits, mod-10 checksum. */
|
|
90
|
+
declare const ean13Schema: z.ZodString;
|
|
91
|
+
/** EAN-8 — 8 digits, mod-10 checksum. */
|
|
92
|
+
declare const ean8Schema: z.ZodString;
|
|
93
|
+
/**
|
|
94
|
+
* ISBN — accepts ISBN-10 or ISBN-13 (with or without hyphens). The stored
|
|
95
|
+
* value is whatever the caller passed; downstream consumers can normalise.
|
|
96
|
+
*/
|
|
97
|
+
declare const isbnSchema: z.ZodString;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { BarcodeFormat, detectBarcodeFormat, ean13Schema, ean8Schema, gtinSchema, isbnSchema, optionalScannableBarcodeSchema, scannableBarcodeSchema, upcaSchema, validateEAN13, validateEAN8, validateGTIN, validateGTIN14, validateISBN, validateISBN10, validateISBN13, validateScannableBarcode, validateUPCA };
|
package/dist/barcode.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/barcode.ts
|
|
3
|
+
/**
|
|
4
|
+
* Barcode primitives — pure checksum + format detection.
|
|
5
|
+
*
|
|
6
|
+
* These are framework-agnostic predicates. Zod schemas in this file compose
|
|
7
|
+
* them via `.refine()`/`.superRefine()` so the same validation runs on the
|
|
8
|
+
* server (Arc auto-CRUD, Express, Nest, direct `repository.create()`) AND in
|
|
9
|
+
* the browser (admin product forms, storefront) — one implementation, zero
|
|
10
|
+
* drift.
|
|
11
|
+
*
|
|
12
|
+
* Format coverage (the formats POS scanners actually emit):
|
|
13
|
+
* - EAN-13 — 13 digits, mod-10 with positions weighted 1,3,1,3,…
|
|
14
|
+
* - EAN-8 — 8 digits, mod-10 with positions weighted 3,1,3,1,…
|
|
15
|
+
* - UPC-A — 12 digits, mod-10 with positions weighted 3,1,3,1,…
|
|
16
|
+
* - GTIN-14 — 14 digits, mod-10 with positions weighted 3,1,3,1,…
|
|
17
|
+
* - ISBN-10 — 10 digits or 9 digits + 'X', mod-11 weighted 10..1
|
|
18
|
+
* - ISBN-13 — 13 digits with '978'/'979' prefix, EAN-13 checksum
|
|
19
|
+
*
|
|
20
|
+
* For non-numeric scan codes (CODE128, internal alphanumerics) we accept
|
|
21
|
+
* any printable ASCII >= 4 chars — JsBarcode and most retail scanners
|
|
22
|
+
* round-trip those losslessly.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* EAN-13 mod-10 checksum.
|
|
26
|
+
*
|
|
27
|
+
* Sum of digits at odd positions × 1 + digits at even positions × 3, then
|
|
28
|
+
* the check digit is `(10 - sum % 10) % 10`. Used for EAN-13 and ISBN-13.
|
|
29
|
+
*/
|
|
30
|
+
function validateEAN13(value) {
|
|
31
|
+
if (!/^\d{13}$/.test(value)) return false;
|
|
32
|
+
const digits = value.split("").map(Number);
|
|
33
|
+
const check = digits.pop();
|
|
34
|
+
let sum = 0;
|
|
35
|
+
for (let i = 0; i < 12; i++) sum += digits[i] * (i % 2 === 0 ? 1 : 3);
|
|
36
|
+
return (10 - sum % 10) % 10 === check;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* UPC-A mod-10 checksum (and GTIN-14, EAN-8 — same mod-10, different length
|
|
40
|
+
* with 3,1,3,1 weighting starting from the leftmost digit).
|
|
41
|
+
*/
|
|
42
|
+
function mod10WithLeading3(digits) {
|
|
43
|
+
let sum = 0;
|
|
44
|
+
for (let i = 0; i < digits.length; i++) sum += digits[i] * (i % 2 === 0 ? 3 : 1);
|
|
45
|
+
return (10 - sum % 10) % 10;
|
|
46
|
+
}
|
|
47
|
+
function validateUPCA(value) {
|
|
48
|
+
if (!/^\d{12}$/.test(value)) return false;
|
|
49
|
+
const digits = value.split("").map(Number);
|
|
50
|
+
const check = digits.pop();
|
|
51
|
+
return mod10WithLeading3(digits) === check;
|
|
52
|
+
}
|
|
53
|
+
function validateEAN8(value) {
|
|
54
|
+
if (!/^\d{8}$/.test(value)) return false;
|
|
55
|
+
const digits = value.split("").map(Number);
|
|
56
|
+
const check = digits.pop();
|
|
57
|
+
return mod10WithLeading3(digits) === check;
|
|
58
|
+
}
|
|
59
|
+
function validateGTIN14(value) {
|
|
60
|
+
if (!/^\d{14}$/.test(value)) return false;
|
|
61
|
+
const digits = value.split("").map(Number);
|
|
62
|
+
const check = digits.pop();
|
|
63
|
+
return mod10WithLeading3(digits) === check;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* GTIN — accepts 8, 12, 13, or 14 digits and routes to the corresponding
|
|
67
|
+
* mod-10 validator. GTIN is the umbrella standard; EAN-13 is GTIN-13 with
|
|
68
|
+
* a leading zero, UPC-A is GTIN-12 with a leading zero, etc.
|
|
69
|
+
*/
|
|
70
|
+
function validateGTIN(value) {
|
|
71
|
+
switch (value.length) {
|
|
72
|
+
case 8: return validateEAN8(value);
|
|
73
|
+
case 12: return validateUPCA(value);
|
|
74
|
+
case 13: return validateEAN13(value);
|
|
75
|
+
case 14: return validateGTIN14(value);
|
|
76
|
+
default: return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* ISBN-10 mod-11 checksum. Last position can be 'X' representing 10.
|
|
81
|
+
*/
|
|
82
|
+
function validateISBN10(value) {
|
|
83
|
+
if (!/^\d{9}[\dX]$/.test(value)) return false;
|
|
84
|
+
let sum = 0;
|
|
85
|
+
for (let i = 0; i < 9; i++) sum += Number(value[i]) * (10 - i);
|
|
86
|
+
const last = value[9] === "X" ? 10 : Number(value[9]);
|
|
87
|
+
sum += last;
|
|
88
|
+
return sum % 11 === 0;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* ISBN-13 — must start with 978 or 979 and pass the EAN-13 checksum.
|
|
92
|
+
*/
|
|
93
|
+
function validateISBN13(value) {
|
|
94
|
+
if (!/^97[89]\d{10}$/.test(value)) return false;
|
|
95
|
+
return validateEAN13(value);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Accepts ISBN-10 or ISBN-13. Allows hyphens for human readability — they
|
|
99
|
+
* are stripped before validation. Caller decides whether to persist the
|
|
100
|
+
* stripped or original form.
|
|
101
|
+
*/
|
|
102
|
+
function validateISBN(value) {
|
|
103
|
+
const stripped = value.replace(/-/g, "");
|
|
104
|
+
return validateISBN10(stripped) || validateISBN13(stripped);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Detect the most specific scannable format that matches `value`.
|
|
108
|
+
* Returns `'UNKNOWN'` for values that don't match any standard format.
|
|
109
|
+
*/
|
|
110
|
+
function detectBarcodeFormat(value) {
|
|
111
|
+
if (!value) return "UNKNOWN";
|
|
112
|
+
if (/^\d{14}$/.test(value)) return validateGTIN14(value) ? "GTIN14" : "UNKNOWN";
|
|
113
|
+
if (/^97[89]\d{10}$/.test(value)) return validateISBN13(value) ? "ISBN13" : "UNKNOWN";
|
|
114
|
+
if (/^\d{13}$/.test(value)) return validateEAN13(value) ? "EAN13" : "UNKNOWN";
|
|
115
|
+
if (/^\d{12}$/.test(value)) return validateUPCA(value) ? "UPCA" : "UNKNOWN";
|
|
116
|
+
if (/^\d{9}[\dX]$/.test(value)) return validateISBN10(value) ? "ISBN10" : "UNKNOWN";
|
|
117
|
+
if (/^\d{8}$/.test(value)) return validateEAN8(value) ? "EAN8" : "UNKNOWN";
|
|
118
|
+
if (/^\d+$/.test(value)) return "UNKNOWN";
|
|
119
|
+
if (/^[\x20-\x7E]{4,}$/.test(value)) return "CODE128";
|
|
120
|
+
return "UNKNOWN";
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Validate any user-entered scannable barcode value.
|
|
124
|
+
*
|
|
125
|
+
* - Empty string → invalid (callers should mark the field optional and
|
|
126
|
+
* skip empties before calling).
|
|
127
|
+
* - Any recognised format → must pass its checksum.
|
|
128
|
+
* - Numeric strings of an unrecognised length (9, 10, 11, 14+ digits not
|
|
129
|
+
* matching ISBN/GTIN-14) → rejected — POS scanners can't read them.
|
|
130
|
+
* - Printable ASCII ≥ 4 chars → accepted as CODE128.
|
|
131
|
+
*
|
|
132
|
+
* Returns `null` for valid input, otherwise a human-readable reason.
|
|
133
|
+
*/
|
|
134
|
+
function validateScannableBarcode(value) {
|
|
135
|
+
if (!value) return "Barcode is empty";
|
|
136
|
+
if (detectBarcodeFormat(value) === "UNKNOWN") {
|
|
137
|
+
if (/^\d+$/.test(value)) return `Numeric barcode of length ${value.length} is not a valid scannable format (expected 8/12/13/14 digits)`;
|
|
138
|
+
return "Barcode must be a recognised scannable format (EAN/UPC/GTIN/ISBN) or printable ASCII ≥ 4 characters";
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* A scannable barcode value. Empty strings are rejected — callers wanting
|
|
144
|
+
* an optional field wrap this in `.optional()` (or pass `undefined`/omit
|
|
145
|
+
* the field). Length and checksum errors surface a human-readable reason
|
|
146
|
+
* via Zod's standard error message channel.
|
|
147
|
+
*/
|
|
148
|
+
const scannableBarcodeSchema = z.string().min(1, "Barcode cannot be empty").superRefine((value, ctx) => {
|
|
149
|
+
const reason = validateScannableBarcode(value);
|
|
150
|
+
if (reason) ctx.addIssue({
|
|
151
|
+
code: "custom",
|
|
152
|
+
message: reason
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
/**
|
|
156
|
+
* Optional variant of the above. Use this on schema fields where the
|
|
157
|
+
* barcode may be unset — `barcode?: string` style.
|
|
158
|
+
*/
|
|
159
|
+
const optionalScannableBarcodeSchema = scannableBarcodeSchema.optional();
|
|
160
|
+
/** GTIN — 8/12/13/14 digits, checksum-validated. */
|
|
161
|
+
const gtinSchema = z.string().regex(/^\d{8,14}$/, "GTIN must be 8, 12, 13, or 14 digits").refine(validateGTIN, "Invalid GTIN checksum");
|
|
162
|
+
/** UPC-A — 12 digits, mod-10 checksum. */
|
|
163
|
+
const upcaSchema = z.string().regex(/^\d{12}$/, "UPC-A must be 12 digits").refine(validateUPCA, "Invalid UPC-A checksum");
|
|
164
|
+
/** EAN-13 — 13 digits, mod-10 checksum. */
|
|
165
|
+
const ean13Schema = z.string().regex(/^\d{13}$/, "EAN-13 must be 13 digits").refine(validateEAN13, "Invalid EAN-13 checksum");
|
|
166
|
+
/** EAN-8 — 8 digits, mod-10 checksum. */
|
|
167
|
+
const ean8Schema = z.string().regex(/^\d{8}$/, "EAN-8 must be 8 digits").refine(validateEAN8, "Invalid EAN-8 checksum");
|
|
168
|
+
/**
|
|
169
|
+
* ISBN — accepts ISBN-10 or ISBN-13 (with or without hyphens). The stored
|
|
170
|
+
* value is whatever the caller passed; downstream consumers can normalise.
|
|
171
|
+
*/
|
|
172
|
+
const isbnSchema = z.string().regex(/^[\dX-]{10,17}$/, "ISBN must be 10 or 13 digits, optionally hyphenated").refine(validateISBN, "Invalid ISBN checksum");
|
|
173
|
+
//#endregion
|
|
174
|
+
export { detectBarcodeFormat, ean13Schema, ean8Schema, gtinSchema, isbnSchema, optionalScannableBarcodeSchema, scannableBarcodeSchema, upcaSchema, validateEAN13, validateEAN8, validateGTIN, validateGTIN14, validateISBN, validateISBN10, validateISBN13, validateScannableBarcode, validateUPCA };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/identifiers.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema for a product's standard identifiers.
|
|
5
|
+
*
|
|
6
|
+
* Each standard identifier (GTIN/UPC/EAN/ISBN) is length-validated AND
|
|
7
|
+
* checksum-validated — see `barcode.ts` for the math. A wrong check digit is
|
|
8
|
+
* rejected at the Zod boundary, so it is also rejected wherever this schema
|
|
9
|
+
* is composed: server-side auto-CRUD, Express/Nest handlers, direct
|
|
10
|
+
* `repository.create()`, and client-side product forms.
|
|
11
|
+
*
|
|
12
|
+
* `mpn` and `plu` carry no checksum standard, so they're length-only.
|
|
13
|
+
* `custom` is an open map for platform-specific IDs (Amazon ASIN, Shopify
|
|
14
|
+
* GID, etc.) that no global standard can validate.
|
|
15
|
+
*/
|
|
16
|
+
declare const productIdentifiersSchema: z.ZodObject<{
|
|
17
|
+
gtin: z.ZodOptional<z.ZodString>;
|
|
18
|
+
upc: z.ZodOptional<z.ZodString>;
|
|
19
|
+
ean: z.ZodOptional<z.ZodString>;
|
|
20
|
+
isbn: z.ZodOptional<z.ZodString>;
|
|
21
|
+
mpn: z.ZodOptional<z.ZodString>;
|
|
22
|
+
plu: z.ZodOptional<z.ZodString>;
|
|
23
|
+
custom: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { productIdentifiersSchema };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ean13Schema, gtinSchema, isbnSchema, upcaSchema } from "./barcode.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/identifiers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for a product's standard identifiers.
|
|
6
|
+
*
|
|
7
|
+
* Each standard identifier (GTIN/UPC/EAN/ISBN) is length-validated AND
|
|
8
|
+
* checksum-validated — see `barcode.ts` for the math. A wrong check digit is
|
|
9
|
+
* rejected at the Zod boundary, so it is also rejected wherever this schema
|
|
10
|
+
* is composed: server-side auto-CRUD, Express/Nest handlers, direct
|
|
11
|
+
* `repository.create()`, and client-side product forms.
|
|
12
|
+
*
|
|
13
|
+
* `mpn` and `plu` carry no checksum standard, so they're length-only.
|
|
14
|
+
* `custom` is an open map for platform-specific IDs (Amazon ASIN, Shopify
|
|
15
|
+
* GID, etc.) that no global standard can validate.
|
|
16
|
+
*/
|
|
17
|
+
const productIdentifiersSchema = z.object({
|
|
18
|
+
gtin: gtinSchema.optional(),
|
|
19
|
+
upc: upcaSchema.optional(),
|
|
20
|
+
ean: ean13Schema.optional(),
|
|
21
|
+
isbn: isbnSchema.optional(),
|
|
22
|
+
mpn: z.string().min(1).max(100).optional(),
|
|
23
|
+
plu: z.string().regex(/^\d{4,5}$/).optional(),
|
|
24
|
+
custom: z.record(z.string(), z.string()).optional()
|
|
25
|
+
});
|
|
26
|
+
//#endregion
|
|
27
|
+
export { productIdentifiersSchema };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { BarcodeFormat, detectBarcodeFormat, ean13Schema, ean8Schema, gtinSchema, isbnSchema, optionalScannableBarcodeSchema, scannableBarcodeSchema, upcaSchema, validateEAN13, validateEAN8, validateGTIN, validateGTIN14, validateISBN, validateISBN10, validateISBN13, validateScannableBarcode, validateUPCA } from "./barcode.js";
|
|
2
|
+
import { productIdentifiersSchema } from "./identifiers.js";
|
|
3
|
+
export { type BarcodeFormat, detectBarcodeFormat, ean13Schema, ean8Schema, gtinSchema, isbnSchema, optionalScannableBarcodeSchema, productIdentifiersSchema, scannableBarcodeSchema, upcaSchema, validateEAN13, validateEAN8, validateGTIN, validateGTIN14, validateISBN, validateISBN10, validateISBN13, validateScannableBarcode, validateUPCA };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { detectBarcodeFormat, ean13Schema, ean8Schema, gtinSchema, isbnSchema, optionalScannableBarcodeSchema, scannableBarcodeSchema, upcaSchema, validateEAN13, validateEAN8, validateGTIN, validateGTIN14, validateISBN, validateISBN10, validateISBN13, validateScannableBarcode, validateUPCA } from "./barcode.js";
|
|
2
|
+
import { productIdentifiersSchema } from "./identifiers.js";
|
|
3
|
+
export { detectBarcodeFormat, ean13Schema, ean8Schema, gtinSchema, isbnSchema, optionalScannableBarcodeSchema, productIdentifiersSchema, scannableBarcodeSchema, upcaSchema, validateEAN13, validateEAN8, validateGTIN, validateGTIN14, validateISBN, validateISBN10, validateISBN13, validateScannableBarcode, validateUPCA };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@classytic/validation",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Isomorphic zod validation schemas shared server<->client. Framework-agnostic, zod the only peer, so the same rules run in the catalog kernel (server auto-CRUD, repository writes) and in admin/storefront forms (browser). First module: barcode (GTIN/EAN/UPC/ISBN predicates + schemas).",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsdown",
|
|
15
|
+
"dev": "tsdown --watch",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"zod": ">=4.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsdown": "^0.22.3",
|
|
25
|
+
"typescript": "^6.0.3",
|
|
26
|
+
"vitest": "^3.2.0",
|
|
27
|
+
"zod": "^4.4.3"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./barcode": {
|
|
39
|
+
"types": "./dist/barcode.d.ts",
|
|
40
|
+
"default": "./dist/barcode.js"
|
|
41
|
+
},
|
|
42
|
+
"./identifiers": {
|
|
43
|
+
"types": "./dist/identifiers.d.ts",
|
|
44
|
+
"default": "./dist/identifiers.js"
|
|
45
|
+
},
|
|
46
|
+
"./package.json": "./package.json"
|
|
47
|
+
}
|
|
48
|
+
}
|