gs1-core 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/dist/ai.d.ts +3 -0
- package/dist/ai.js +244 -0
- package/dist/ai.js.map +1 -0
- package/dist/check-digit.d.ts +3 -0
- package/dist/check-digit.js +22 -0
- package/dist/check-digit.js.map +1 -0
- package/dist/digital-link.d.ts +7 -0
- package/dist/digital-link.js +87 -0
- package/dist/digital-link.js.map +1 -0
- package/dist/element.d.ts +12 -0
- package/dist/element.js +41 -0
- package/dist/element.js.map +1 -0
- package/dist/generate.d.ts +8 -0
- package/dist/generate.js +30 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/interpret.d.ts +5 -0
- package/dist/interpret.js +107 -0
- package/dist/interpret.js.map +1 -0
- package/dist/parse.d.ts +10 -0
- package/dist/parse.js +94 -0
- package/dist/parse.js.map +1 -0
- package/dist/spec.d.ts +4 -0
- package/dist/spec.js +70 -0
- package/dist/spec.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
- package/readme.md +195 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AIDefinition, GS1Date, GS1DateTime, InterpretedValue } from './types.js';
|
|
2
|
+
export declare function resolveYear(yy: number, now?: Date): number;
|
|
3
|
+
export declare function parseDate6(value: string, ai?: string): GS1Date;
|
|
4
|
+
export declare function parseDateTime(value: string, ai?: string): GS1DateTime;
|
|
5
|
+
export declare function interpretValue(ai: string, definition: AIDefinition, value: string): InterpretedValue | undefined;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { GS1Error, } from './types.js';
|
|
2
|
+
export function resolveYear(yy, now = new Date()) {
|
|
3
|
+
// GS1 sliding window: the resolved year is within -49/+50 of the current one
|
|
4
|
+
const currentYear = now.getFullYear();
|
|
5
|
+
const currentCentury = currentYear - (currentYear % 100);
|
|
6
|
+
const diff = yy - (currentYear % 100);
|
|
7
|
+
if (diff >= 51)
|
|
8
|
+
return currentCentury - 100 + yy;
|
|
9
|
+
if (diff <= -50)
|
|
10
|
+
return currentCentury + 100 + yy;
|
|
11
|
+
return currentCentury + yy;
|
|
12
|
+
}
|
|
13
|
+
function lastDayOfMonth(year, month) {
|
|
14
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
15
|
+
}
|
|
16
|
+
function pad(n, width = 2) {
|
|
17
|
+
return String(n).padStart(width, '0');
|
|
18
|
+
}
|
|
19
|
+
export function parseDate6(value, ai) {
|
|
20
|
+
if (!/^\d{6}$/.test(value)) {
|
|
21
|
+
throw new GS1Error(`date "${value}" must be 6 digits (YYMMDD)`, ai);
|
|
22
|
+
}
|
|
23
|
+
const year = resolveYear(Number(value.slice(0, 2)));
|
|
24
|
+
const month = Number(value.slice(2, 4));
|
|
25
|
+
let day = Number(value.slice(4, 6));
|
|
26
|
+
if (month < 1 || month > 12) {
|
|
27
|
+
throw new GS1Error(`date "${value}" has invalid month ${month}`, ai);
|
|
28
|
+
}
|
|
29
|
+
const maxDay = lastDayOfMonth(year, month);
|
|
30
|
+
if (day === 0)
|
|
31
|
+
day = maxDay; // DD "00" means end of month
|
|
32
|
+
if (day > maxDay) {
|
|
33
|
+
throw new GS1Error(`date "${value}" has invalid day ${day}`, ai);
|
|
34
|
+
}
|
|
35
|
+
return { year, month, day, iso: `${year}-${pad(month)}-${pad(day)}` };
|
|
36
|
+
}
|
|
37
|
+
export function parseDateTime(value, ai) {
|
|
38
|
+
if (!/^\d+$/.test(value) || ![8, 10, 12].includes(value.length)) {
|
|
39
|
+
if (/^\d{6}$/.test(value))
|
|
40
|
+
return { ...parseDate6(value, ai), hour: 0 };
|
|
41
|
+
throw new GS1Error(`date/time "${value}" must be YYMMDDHH[MM[SS]]`, ai);
|
|
42
|
+
}
|
|
43
|
+
const date = parseDate6(value.slice(0, 6), ai);
|
|
44
|
+
const hour = Number(value.slice(6, 8));
|
|
45
|
+
if (hour > 23) {
|
|
46
|
+
throw new GS1Error(`date/time "${value}" has invalid hour ${hour}`, ai);
|
|
47
|
+
}
|
|
48
|
+
const result = { ...date, hour };
|
|
49
|
+
result.iso = `${date.iso}T${pad(hour)}`;
|
|
50
|
+
if (value.length >= 10) {
|
|
51
|
+
const minute = Number(value.slice(8, 10));
|
|
52
|
+
if (minute > 59) {
|
|
53
|
+
throw new GS1Error(`date/time "${value}" has invalid minute ${minute}`, ai);
|
|
54
|
+
}
|
|
55
|
+
result.minute = minute;
|
|
56
|
+
result.iso += `:${pad(minute)}`;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
result.iso += ':00';
|
|
60
|
+
}
|
|
61
|
+
if (value.length === 12) {
|
|
62
|
+
const second = Number(value.slice(10, 12));
|
|
63
|
+
if (second > 59) {
|
|
64
|
+
throw new GS1Error(`date/time "${value}" has invalid second ${second}`, ai);
|
|
65
|
+
}
|
|
66
|
+
result.second = second;
|
|
67
|
+
result.iso += `:${pad(second)}`;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
export function interpretValue(ai, definition, value) {
|
|
72
|
+
switch (definition.kind) {
|
|
73
|
+
case 'date':
|
|
74
|
+
return parseDate6(value, ai);
|
|
75
|
+
case 'datetime':
|
|
76
|
+
return parseDateTime(value, ai);
|
|
77
|
+
case 'dateRange': {
|
|
78
|
+
const start = parseDate6(value.slice(0, 6), ai);
|
|
79
|
+
if (value.length === 6)
|
|
80
|
+
return { start };
|
|
81
|
+
return { start, end: parseDate6(value.slice(6, 12), ai) };
|
|
82
|
+
}
|
|
83
|
+
case 'count':
|
|
84
|
+
return Number(value);
|
|
85
|
+
case 'decimal': {
|
|
86
|
+
const decimals = decimalPosition(ai, definition);
|
|
87
|
+
return Number(value) / 10 ** decimals;
|
|
88
|
+
}
|
|
89
|
+
case 'currencyDecimal': {
|
|
90
|
+
const decimals = decimalPosition(ai, definition);
|
|
91
|
+
return {
|
|
92
|
+
currency: value.slice(0, 3),
|
|
93
|
+
amount: Number(value.slice(3)) / 10 ** decimals,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function decimalPosition(ai, definition) {
|
|
101
|
+
// for families like "310n" the 4th digit of the concrete AI is the number
|
|
102
|
+
// of digits after the implied decimal point
|
|
103
|
+
if (definition.ai.endsWith('n'))
|
|
104
|
+
return Number(ai[3]);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=interpret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interpret.js","sourceRoot":"","sources":["../src/interpret.ts"],"names":[],"mappings":"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"}
|
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GS1Element } from './types.js';
|
|
2
|
+
import { ValidationOptions } from './element.js';
|
|
3
|
+
export declare const GS = "\u001D";
|
|
4
|
+
export interface ParseRawOptions extends ValidationOptions {
|
|
5
|
+
fnc1?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseElementString(input: string, options?: ValidationOptions): GS1Element[];
|
|
8
|
+
export declare function parseRawString(input: string, options?: ParseRawOptions): GS1Element[];
|
|
9
|
+
export declare function parse(input: string, options?: ParseRawOptions): GS1Element[];
|
|
10
|
+
export declare function toRecord(elements: GS1Element[]): Record<string, string>;
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { GS1Error } from './types.js';
|
|
2
|
+
import { getAIDefinition } from './ai.js';
|
|
3
|
+
import { createElement } from './element.js';
|
|
4
|
+
// ASCII Group Separator, the standard FNC1 representation in transmitted data
|
|
5
|
+
export const GS = '\x1D';
|
|
6
|
+
// ]C1 = GS1-128, ]e0-]e2 = DataBar, ]d2 = GS1 DataMatrix, ]Q3 = GS1 QR, ]J1 = DotCode
|
|
7
|
+
const SYMBOLOGY_ID = /^\](C1|e0|e1|e2|d2|Q3|J1)/;
|
|
8
|
+
export function parseElementString(input, options = {}) {
|
|
9
|
+
if (!input.startsWith('(')) {
|
|
10
|
+
throw new GS1Error(`element string must start with "(", got "${input.slice(0, 8)}..."`);
|
|
11
|
+
}
|
|
12
|
+
const elements = [];
|
|
13
|
+
const aiToken = /\((\d{2,4})\)/g;
|
|
14
|
+
let match = aiToken.exec(input);
|
|
15
|
+
if (!match || match.index !== 0) {
|
|
16
|
+
throw new GS1Error(`expected "(ai)" at start of element string`);
|
|
17
|
+
}
|
|
18
|
+
while (match) {
|
|
19
|
+
const ai = match[1];
|
|
20
|
+
const valueStart = aiToken.lastIndex;
|
|
21
|
+
// parentheses are legal inside CSET 82 values, so tokens that are not
|
|
22
|
+
// valid AIs belong to the value
|
|
23
|
+
let next = aiToken.exec(input);
|
|
24
|
+
while (next && !getAIDefinition(next[1]))
|
|
25
|
+
next = aiToken.exec(input);
|
|
26
|
+
const valueEnd = next ? next.index : input.length;
|
|
27
|
+
const value = input.slice(valueStart, valueEnd);
|
|
28
|
+
if (value.length === 0) {
|
|
29
|
+
throw new GS1Error(`empty value in element string`, ai);
|
|
30
|
+
}
|
|
31
|
+
elements.push(createElement(ai, value, options));
|
|
32
|
+
match = next;
|
|
33
|
+
}
|
|
34
|
+
return elements;
|
|
35
|
+
}
|
|
36
|
+
export function parseRawString(input, options = {}) {
|
|
37
|
+
const fnc1 = options.fnc1 ?? GS;
|
|
38
|
+
let data = input.replace(SYMBOLOGY_ID, '');
|
|
39
|
+
while (data.startsWith(fnc1))
|
|
40
|
+
data = data.slice(fnc1.length);
|
|
41
|
+
const elements = [];
|
|
42
|
+
let pos = 0;
|
|
43
|
+
while (pos < data.length) {
|
|
44
|
+
if (data.startsWith(fnc1, pos)) {
|
|
45
|
+
pos += fnc1.length;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
// GS1 AIs are prefix-free: try 2-, 3-, then 4-digit AIs
|
|
49
|
+
let ai;
|
|
50
|
+
let definition;
|
|
51
|
+
for (const len of [2, 3, 4]) {
|
|
52
|
+
const candidate = data.slice(pos, pos + len);
|
|
53
|
+
if (candidate.length < len || !/^\d+$/.test(candidate))
|
|
54
|
+
break;
|
|
55
|
+
const def = getAIDefinition(candidate);
|
|
56
|
+
if (def) {
|
|
57
|
+
ai = candidate;
|
|
58
|
+
definition = def;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!ai || !definition) {
|
|
63
|
+
throw new GS1Error(`unrecognised application identifier at position ${pos + 1} ("${data.slice(pos, pos + 4)}")`);
|
|
64
|
+
}
|
|
65
|
+
pos += ai.length;
|
|
66
|
+
let value;
|
|
67
|
+
if (definition.fixedLength !== undefined) {
|
|
68
|
+
value = data.slice(pos, pos + definition.fixedLength);
|
|
69
|
+
if (value.length < definition.fixedLength) {
|
|
70
|
+
throw new GS1Error(`expected ${definition.fixedLength} characters but only ${value.length} remain`, ai);
|
|
71
|
+
}
|
|
72
|
+
pos += definition.fixedLength;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const sep = data.indexOf(fnc1, pos);
|
|
76
|
+
value = sep === -1 ? data.slice(pos) : data.slice(pos, sep);
|
|
77
|
+
pos = sep === -1 ? data.length : sep;
|
|
78
|
+
}
|
|
79
|
+
elements.push(createElement(ai, value, options));
|
|
80
|
+
}
|
|
81
|
+
return elements;
|
|
82
|
+
}
|
|
83
|
+
export function parse(input, options = {}) {
|
|
84
|
+
if (input.startsWith('('))
|
|
85
|
+
return parseElementString(input, options);
|
|
86
|
+
return parseRawString(input, options);
|
|
87
|
+
}
|
|
88
|
+
export function toRecord(elements) {
|
|
89
|
+
const record = {};
|
|
90
|
+
for (const element of elements)
|
|
91
|
+
record[element.ai] = element.value;
|
|
92
|
+
return record;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
package/dist/spec.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SpecComponent } from './types.js';
|
|
2
|
+
export declare function compileSpec(spec: string): SpecComponent[];
|
|
3
|
+
export declare function validateValue(ai: string, components: SpecComponent[], value: string): void;
|
|
4
|
+
export declare function describeSpec(components: SpecComponent[]): string;
|
package/dist/spec.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { GS1Error } from './types.js';
|
|
2
|
+
const CSET82 = /^[!"%&'()*+,\-./0-9:;<=>?A-Z_a-z]$/;
|
|
3
|
+
const CSET39 = /^[#\-/0-9A-Z]$/;
|
|
4
|
+
function inSet(ch, set) {
|
|
5
|
+
switch (set) {
|
|
6
|
+
case 'N':
|
|
7
|
+
return ch >= '0' && ch <= '9';
|
|
8
|
+
case 'X':
|
|
9
|
+
return CSET82.test(ch);
|
|
10
|
+
case 'Y':
|
|
11
|
+
return CSET39.test(ch);
|
|
12
|
+
case 'M':
|
|
13
|
+
return ch === '-';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const SET_NAMES = {
|
|
17
|
+
N: 'digits',
|
|
18
|
+
X: 'CSET 82 characters',
|
|
19
|
+
Y: 'CSET 39 characters',
|
|
20
|
+
M: '"-"',
|
|
21
|
+
};
|
|
22
|
+
export function compileSpec(spec) {
|
|
23
|
+
const components = spec.split('+').map((part, index) => {
|
|
24
|
+
const m = /^([NXYM])(?:(\d+)|\.\.(\d+))$/.exec(part);
|
|
25
|
+
if (!m)
|
|
26
|
+
throw new GS1Error(`invalid format spec "${part}" in "${spec}"`);
|
|
27
|
+
const set = m[1];
|
|
28
|
+
if (m[2] !== undefined) {
|
|
29
|
+
const len = Number(m[2]);
|
|
30
|
+
return { set, min: len, max: len, fixed: true };
|
|
31
|
+
}
|
|
32
|
+
// a variable component after the first is optional (e.g. GDTI serial)
|
|
33
|
+
const max = Number(m[3]);
|
|
34
|
+
return { set, min: index === 0 ? 1 : 0, max, fixed: false };
|
|
35
|
+
});
|
|
36
|
+
for (let i = 0; i < components.length - 1; i++) {
|
|
37
|
+
if (!components[i].fixed) {
|
|
38
|
+
throw new GS1Error(`invalid spec "${spec}": variable component before the end`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return components;
|
|
42
|
+
}
|
|
43
|
+
export function validateValue(ai, components, value) {
|
|
44
|
+
let pos = 0;
|
|
45
|
+
for (const comp of components) {
|
|
46
|
+
const remaining = value.length - pos;
|
|
47
|
+
const take = comp.fixed ? comp.min : remaining;
|
|
48
|
+
if (take < comp.min || remaining < take) {
|
|
49
|
+
throw new GS1Error(`value "${value}" is too short, expected ${describeSpec(components)}`, ai);
|
|
50
|
+
}
|
|
51
|
+
if (take > comp.max) {
|
|
52
|
+
throw new GS1Error(`value "${value}" is too long, expected ${describeSpec(components)}`, ai);
|
|
53
|
+
}
|
|
54
|
+
for (let i = pos; i < pos + take; i++) {
|
|
55
|
+
if (!inSet(value[i], comp.set)) {
|
|
56
|
+
throw new GS1Error(`character "${value[i]}" at position ${i + 1} of "${value}" is not allowed (expected ${SET_NAMES[comp.set]})`, ai);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
pos += take;
|
|
60
|
+
}
|
|
61
|
+
if (pos < value.length) {
|
|
62
|
+
throw new GS1Error(`value "${value}" is too long, expected ${describeSpec(components)}`, ai);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function describeSpec(components) {
|
|
66
|
+
return components
|
|
67
|
+
.map((c) => (c.fixed ? `${c.set}${c.min}` : `${c.set}..${c.max}`))
|
|
68
|
+
.join('+');
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=spec.js.map
|
package/dist/spec.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type CharacterSet = 'N' | 'X' | 'Y' | 'M';
|
|
2
|
+
export interface SpecComponent {
|
|
3
|
+
set: CharacterSet;
|
|
4
|
+
min: number;
|
|
5
|
+
max: number;
|
|
6
|
+
fixed: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type InterpretationKind = 'date' | 'datetime' | 'dateRange' | 'decimal' | 'currencyDecimal' | 'count';
|
|
9
|
+
export interface AIDefinition {
|
|
10
|
+
ai: string;
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
spec: string;
|
|
14
|
+
components: SpecComponent[];
|
|
15
|
+
predefinedLength: boolean;
|
|
16
|
+
fixedLength?: number;
|
|
17
|
+
checkDigitPosition?: number;
|
|
18
|
+
kind?: InterpretationKind;
|
|
19
|
+
}
|
|
20
|
+
export interface GS1Date {
|
|
21
|
+
year: number;
|
|
22
|
+
month: number;
|
|
23
|
+
day: number;
|
|
24
|
+
iso: string;
|
|
25
|
+
}
|
|
26
|
+
export interface GS1DateTime extends GS1Date {
|
|
27
|
+
hour: number;
|
|
28
|
+
minute?: number;
|
|
29
|
+
second?: number;
|
|
30
|
+
}
|
|
31
|
+
export type InterpretedValue = number | GS1Date | GS1DateTime | {
|
|
32
|
+
start: GS1Date;
|
|
33
|
+
end?: GS1Date;
|
|
34
|
+
} | {
|
|
35
|
+
currency: string;
|
|
36
|
+
amount: number;
|
|
37
|
+
};
|
|
38
|
+
export interface GS1Element {
|
|
39
|
+
ai: string;
|
|
40
|
+
value: string;
|
|
41
|
+
definition: AIDefinition;
|
|
42
|
+
interpreted?: InterpretedValue;
|
|
43
|
+
}
|
|
44
|
+
export type ElementInput = Array<{
|
|
45
|
+
ai: string;
|
|
46
|
+
value: string;
|
|
47
|
+
}> | Record<string, string>;
|
|
48
|
+
export declare class GS1Error extends Error {
|
|
49
|
+
readonly ai?: string;
|
|
50
|
+
constructor(message: string, ai?: string);
|
|
51
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA6DA,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,EAAE,CAAU;IAErB,YAAY,OAAe,EAAE,EAAW;QACtC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gs1-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency library to parse, validate and generate GS1 element strings, raw barcode data and GS1 Digital Link URIs",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"gs1",
|
|
7
|
+
"barcode",
|
|
8
|
+
"gtin",
|
|
9
|
+
"sscc",
|
|
10
|
+
"application-identifier",
|
|
11
|
+
"datamatrix",
|
|
12
|
+
"gs1-128",
|
|
13
|
+
"digital-link"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "rmingon",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/rmingon/gs1-core.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/rmingon/gs1-core#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/rmingon/gs1-core/issues"
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:watch": "vitest",
|
|
42
|
+
"prepublishOnly": "npm test && npm run build"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"typescript": "^5.5.0",
|
|
49
|
+
"vitest": "^3.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# gs1-core
|
|
2
|
+
|
|
3
|
+
Parse, validate and generate GS1 strings in JavaScript/TypeScript. Zero dependencies.
|
|
4
|
+
|
|
5
|
+
Supports the three ways GS1 data travels:
|
|
6
|
+
|
|
7
|
+
| Format | Example |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Human-readable element string | `(01)09506000134352(17)261231(10)ABC123` |
|
|
10
|
+
| Raw barcode data (GS1-128, DataMatrix, QR) | `]d201095060001343521726123110ABC123` |
|
|
11
|
+
| GS1 Digital Link URI | `https://id.gs1.org/01/09506000134352/10/ABC123?17=261231` |
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Full AI dictionary: ~180 Application Identifiers from the GS1 General Specifications, including the decimal families (`310n`-`369n`, `390n`-`395n`)
|
|
16
|
+
- Validation of format specs, CSET 82 / CSET 39 character sets, dates, and check digits (GTIN, SSCC, GLN, GSIN, GSRN, GDTI, GCN, GRAI, ITIP)
|
|
17
|
+
- One `parse()` call handles element strings and raw scanner data, including symbology identifiers (`]C1`, `]d2`, `]Q3`, ...)
|
|
18
|
+
- Dates, weights and measures with implied decimal point, currency amounts and counts are returned as typed values, not strings
|
|
19
|
+
- GS1 Digital Link URIs with correct primary key and qualifier ordering
|
|
20
|
+
- ESM, fully typed, works in Node.js >= 18 and modern bundlers
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install gs1-core
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { parse } from 'gs1-core';
|
|
32
|
+
|
|
33
|
+
const elements = parse('(01)09506000134352(17)261231(3103)000189(10)ABC123');
|
|
34
|
+
|
|
35
|
+
for (const el of elements) {
|
|
36
|
+
console.log(el.ai, el.definition.title, el.value, el.interpreted);
|
|
37
|
+
}
|
|
38
|
+
// 01 GTIN 09506000134352
|
|
39
|
+
// 17 USE BY OR EXPIRY 261231 { year: 2026, month: 12, day: 31, iso: '2026-12-31' }
|
|
40
|
+
// 3103 NET WEIGHT (kg) 000189 0.189
|
|
41
|
+
// 10 BATCH/LOT ABC123
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Parsing scanner output
|
|
47
|
+
|
|
48
|
+
Raw data uses ASCII GS (`\x1D`) as the FNC1 separator by default, and a leading symbology identifier is skipped automatically:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { parseRawString, toRecord } from 'gs1-core';
|
|
52
|
+
|
|
53
|
+
const elements = parseRawString(']C101095060001343521726123110ABC123\x1D21SER99');
|
|
54
|
+
|
|
55
|
+
toRecord(elements);
|
|
56
|
+
// { '01': '09506000134352', '17': '261231', '10': 'ABC123', '21': 'SER99' }
|
|
57
|
+
|
|
58
|
+
// Some scanners are configured to substitute FNC1 with another character:
|
|
59
|
+
parseRawString('10LOT1|21SER1', { fnc1: '|' });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Parsing validates by default. Unknown AIs, wrong lengths, illegal characters, bad check digits and impossible dates all throw a `GS1Error` that tells you which AI failed. Pass `{ validate: false }` to parse structure only.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { GS1Error, parse } from 'gs1-core';
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
parse('(01)09506000134353'); // wrong check digit
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof GS1Error) console.error(err.ai, err.message);
|
|
71
|
+
// '01' 'AI (01): invalid check digit in "09506000134353" (position 14)'
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Generating strings
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { generateElementString, generateRawString } from 'gs1-core';
|
|
79
|
+
|
|
80
|
+
const items = [
|
|
81
|
+
{ ai: '01', value: '09506000134352' },
|
|
82
|
+
{ ai: '10', value: 'ABC123' },
|
|
83
|
+
{ ai: '17', value: '261231' },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
generateElementString(items);
|
|
87
|
+
// '(01)09506000134352(10)ABC123(17)261231'
|
|
88
|
+
|
|
89
|
+
generateRawString(items);
|
|
90
|
+
// '010950600013435217261231' + '10ABC123'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`generateRawString` produces the content to encode after the leading FNC1 of a GS1-128 / GS1 DataMatrix / GS1 QR symbol. Predefined-length AIs are moved to the front so fewer separators are needed (disable with `{ optimize: false }`), and variable-length values are GS-terminated only when another element follows.
|
|
94
|
+
|
|
95
|
+
Values are validated before generation, so you cannot emit an invalid GS1 string. Check digit helpers are available if you need to compute one first:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { withCheckDigit, computeCheckDigit, verifyCheckDigit } from 'gs1-core';
|
|
99
|
+
|
|
100
|
+
withCheckDigit('0950600013435'); // '09506000134352'
|
|
101
|
+
computeCheckDigit('0950600013435'); // 2
|
|
102
|
+
verifyCheckDigit('09506000134352'); // true
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### GS1 Digital Link
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { generateDigitalLink, parseDigitalLink } from 'gs1-core';
|
|
109
|
+
|
|
110
|
+
generateDigitalLink(
|
|
111
|
+
[
|
|
112
|
+
{ ai: '01', value: '09506000134352' },
|
|
113
|
+
{ ai: '10', value: 'ABC123' },
|
|
114
|
+
{ ai: '17', value: '261231' },
|
|
115
|
+
],
|
|
116
|
+
{ domain: 'https://id.example.com' }, // default: https://id.gs1.org
|
|
117
|
+
);
|
|
118
|
+
// 'https://id.example.com/01/09506000134352/10/ABC123?17=261231'
|
|
119
|
+
|
|
120
|
+
parseDigitalLink('https://id.gs1.org/01/4006381333931?17=261231');
|
|
121
|
+
// GTINs are normalised to 14 digits, resolver path prefixes are handled,
|
|
122
|
+
// and non-AI query parameters (linkType, ...) are ignored.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### AI dictionary
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { AI_DEFINITIONS, getAIDefinition } from 'gs1-core';
|
|
129
|
+
|
|
130
|
+
getAIDefinition('3103');
|
|
131
|
+
// {
|
|
132
|
+
// ai: '310n', title: 'NET WEIGHT (kg)', spec: 'N6',
|
|
133
|
+
// predefinedLength: true, fixedLength: 6, kind: 'decimal', ...
|
|
134
|
+
// }
|
|
135
|
+
|
|
136
|
+
for (const def of AI_DEFINITIONS.values()) {
|
|
137
|
+
console.log(def.ai, def.spec, def.title);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Interpreted values
|
|
142
|
+
|
|
143
|
+
`element.interpreted` carries a typed value when the AI has a known semantic:
|
|
144
|
+
|
|
145
|
+
| Kind | AIs (examples) | Type |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `date` | 11, 13, 15, 17, 7006, ... | `{ year, month, day, iso }`. `DD = 00` resolves to the last day of the month; 2-digit years follow the GS1 -49/+50 sliding window |
|
|
148
|
+
| `datetime` | 7003, 7011, 8008, 4324, 4325 | `{ year, ..., hour, minute?, second?, iso }` |
|
|
149
|
+
| `dateRange` | 7007 | `{ start, end? }` |
|
|
150
|
+
| `decimal` | 310n-369n, 390n, 392n, 394n, 395n | `number` (implied decimal point from the 4th AI digit) |
|
|
151
|
+
| `currencyDecimal` | 391n, 393n | `{ currency, amount }` (ISO 4217 numeric code) |
|
|
152
|
+
| `count` | 30, 37, 8111, ... | `number` |
|
|
153
|
+
|
|
154
|
+
## API
|
|
155
|
+
|
|
156
|
+
| Function | Description |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| `parse(input, options?)` | Parse any GS1 string, auto-detecting the format |
|
|
159
|
+
| `parseElementString(input, options?)` | Parse `(01)...(10)...` element strings |
|
|
160
|
+
| `parseRawString(input, options?)` | Parse raw barcode data (`fnc1` option for custom separators) |
|
|
161
|
+
| `parseDigitalLink(uri, options?)` | Parse a GS1 Digital Link URI |
|
|
162
|
+
| `generateElementString(input, options?)` | Build a human-readable element string |
|
|
163
|
+
| `generateRawString(input, options?)` | Build raw barcode content (`fnc1`, `optimize` options) |
|
|
164
|
+
| `generateDigitalLink(input, options?)` | Build a Digital Link URI (`domain` option) |
|
|
165
|
+
| `computeCheckDigit(digits)` / `verifyCheckDigit(digits)` / `withCheckDigit(digits)` | GS1 mod-10 check digit helpers |
|
|
166
|
+
| `getAIDefinition(ai)` / `AI_DEFINITIONS` | AI dictionary lookup / full table |
|
|
167
|
+
| `toRecord(elements)` | Convert parsed elements to `{ ai: value }` |
|
|
168
|
+
| `parseDate6`, `parseDateTime`, `resolveYear`, `interpretValue` | Low-level interpretation helpers |
|
|
169
|
+
|
|
170
|
+
Generator functions accept either an array of `{ ai, value }` pairs (order preserved) or a plain record `{ '01': '...', '10': '...' }`. Note that JavaScript iterates integer-like keys such as `'10'` before keys like `'01'`, so use the array form when order matters.
|
|
171
|
+
|
|
172
|
+
All errors are instances of `GS1Error`, which exposes the offending `ai` when known.
|
|
173
|
+
|
|
174
|
+
## Known limitations
|
|
175
|
+
|
|
176
|
+
- In human-readable element strings, a value that itself contains a `"(AI)"`-shaped sequence (parentheses are legal CSET 82 characters) is ambiguous and will be split. Use the raw format for such data; this ambiguity is inherent to the human-readable notation.
|
|
177
|
+
- Trailing components documented as mandatory by GS1 for a few AIs (e.g. the postal code of AI 421) are treated as optional.
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
Issues and pull requests are welcome.
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
git clone https://github.com/rmingon/gs1-core.git
|
|
185
|
+
cd gs1-core
|
|
186
|
+
npm install
|
|
187
|
+
npm test # run the vitest suite
|
|
188
|
+
npm run build # type-check and emit dist/
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If you spot a missing or outdated Application Identifier, please open an issue with a reference to the relevant GS1 General Specifications section. The whole dictionary lives in [src/ai.ts](src/ai.ts).
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
[MIT](./LICENSE)
|