@aidc-toolkit/gs1 1.0.39-beta → 1.0.41-beta
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/README.md +84 -101
- package/dist/gcp-length-cache.d.ts +6 -1
- package/dist/gcp-length-cache.d.ts.map +1 -1
- package/dist/gcp-length-cache.js +7 -2
- package/dist/gcp-length-cache.js.map +1 -1
- package/dist/gcp-length-tree.d.ts +33 -0
- package/dist/gcp-length-tree.d.ts.map +1 -0
- package/dist/gcp-length-tree.js +13 -0
- package/dist/gcp-length-tree.js.map +1 -0
- package/dist/gcp-length.d.ts +63 -49
- package/dist/gcp-length.d.ts.map +1 -1
- package/dist/gcp-length.js +319 -262
- package/dist/gcp-length.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/locale/en/locale-resources.d.ts +2 -0
- package/dist/locale/en/locale-resources.d.ts.map +1 -1
- package/dist/locale/en/locale-resources.js +3 -1
- package/dist/locale/en/locale-resources.js.map +1 -1
- package/dist/locale/fr/locale-resources.d.ts +2 -0
- package/dist/locale/fr/locale-resources.d.ts.map +1 -1
- package/dist/locale/fr/locale-resources.js +3 -1
- package/dist/locale/fr/locale-resources.js.map +1 -1
- package/dist/prefix-manager.d.ts +0 -35
- package/dist/prefix-manager.d.ts.map +1 -1
- package/dist/prefix-manager.js +0 -56
- package/dist/prefix-manager.js.map +1 -1
- package/package.json +4 -4
- package/src/gcp-length-cache.ts +7 -2
- package/src/gcp-length-tree.ts +39 -0
- package/src/gcp-length.ts +348 -297
- package/src/index.ts +3 -1
- package/src/locale/en/locale-resources.ts +3 -1
- package/src/locale/fr/locale-resources.ts +3 -1
- package/src/prefix-manager.ts +0 -65
- package/test/gcp-length.test.ts +39 -31
- package/tsconfig-src.tsbuildinfo +1 -1
package/src/gcp-length.ts
CHANGED
|
@@ -1,380 +1,431 @@
|
|
|
1
1
|
import { omit } from "@aidc-toolkit/core";
|
|
2
2
|
import type { GCPLengthCache } from "./gcp-length-cache.js";
|
|
3
|
-
import { type GCPLengthData,
|
|
3
|
+
import { type GCPLengthData, isGCPLengthData } from "./gcp-length-data.js";
|
|
4
|
+
import * as GCPLengthTree from "./gcp-length-tree.js";
|
|
4
5
|
import { GTINLengths } from "./gtin-length.js";
|
|
5
6
|
import { GTINValidator } from "./gtin-validator.js";
|
|
6
7
|
import { type IdentifierType, IdentifierTypes } from "./identifier-type.js";
|
|
7
8
|
import { IdentifierValidators, isNumericIdentifierValidator } from "./identifier-validators.js";
|
|
8
9
|
import { LeaderTypes } from "./leader-type.js";
|
|
10
|
+
import { i18nextGS1 } from "./locale/i18n.js";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
13
|
+
* Interim branch of GS1 Company Prefix length tree with writeable child nodes.
|
|
12
14
|
*/
|
|
13
|
-
export interface
|
|
14
|
-
|
|
15
|
+
export interface InterimBranch {
|
|
16
|
+
readonly childNodes: Array<InterimBranch | GCPLengthTree.Leaf | undefined>;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
export interface Branch {
|
|
21
|
-
childNodes: Array<Node | undefined>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Root of GS1 Company Prefix length tree.
|
|
26
|
-
*/
|
|
27
|
-
export interface Root extends GCPLengthHeader, Branch {
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Node in GS1 Company Prefix length tree.
|
|
32
|
-
*/
|
|
33
|
-
export type Node = Root | Branch | Leaf;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Determine if a node is a leaf.
|
|
20
|
+
* GS1 Company Prefix length service. The constructor takes a {@linkcode GCPLengthCache} object, which is responsible
|
|
21
|
+
* for managing the cache and source.
|
|
37
22
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
23
|
+
* The first step is to load the GS1 Company Prefix length data. This is done using the
|
|
24
|
+
* {@linkcode GCPLength.load | load()} method, which works as follows:
|
|
40
25
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Entry in binary data to indicate that child node is undefined.
|
|
50
|
-
*/
|
|
51
|
-
const BINARY_UNDEFINED = 0x0F;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Entry in binary data to indicate that child node is a branch.
|
|
55
|
-
*/
|
|
56
|
-
const BINARY_BRANCH = 0x0E;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Build the GS1 Company Prefix length tree from a binary data array.
|
|
60
|
-
*
|
|
61
|
-
* @param binaryData
|
|
62
|
-
* Binary data array.
|
|
26
|
+
* - If the next check date/time is in the future, the method returns immediately, regardless of whether any data is
|
|
27
|
+
* available. It does this to prevent a large number of requests to the source in the event of a failure.
|
|
28
|
+
* - Otherwise, if the cache date/time is undefined or less than the source date/time, it loads from the source,
|
|
29
|
+
* converts the data if necessary, and updates the cache.
|
|
30
|
+
* - Otherwise, it continues with the cached data.
|
|
31
|
+
* - The next check date/time is updated to the later of the source date/time plus one week and the current date/time
|
|
32
|
+
* plus one day.
|
|
63
33
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
34
|
+
* The base class implementation of the `GCPLengthCache` manages only the cache itself, and it requires an
|
|
35
|
+
* application-provided storage implementation. The source is expected to be either a {@linkcode GCPLengthData} object
|
|
36
|
+
* (created via another cache implementation) or a {@linkcode GCPLengthSourceJSON} object, which is the format of the
|
|
37
|
+
* file provided by GS1.
|
|
66
38
|
*
|
|
67
|
-
* @
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* @returns
|
|
71
|
-
* End index into binary data array.
|
|
39
|
+
* Once the data is loaded, the {@linkcode GCPLength.lengthOf | lengthOf()} method can be used to get the length of a
|
|
40
|
+
* GS1 Company Prefix for an identifier type and identifier.
|
|
72
41
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
42
|
+
export class GCPLength {
|
|
43
|
+
/**
|
|
44
|
+
* Entry in binary data to indicate that child node is undefined.
|
|
45
|
+
*/
|
|
46
|
+
static readonly #BINARY_UNDEFINED = 0x0F;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Entry in binary data to indicate that child node is a branch.
|
|
50
|
+
*/
|
|
51
|
+
static readonly #BINARY_BRANCH = 0x0E;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* GS1 Company Prefix length cache.
|
|
55
|
+
*/
|
|
56
|
+
readonly #gcpLengthCache: GCPLengthCache;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* GS1 Company Prefix length tree root.
|
|
60
|
+
*/
|
|
61
|
+
#root?: GCPLengthTree.Root;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Constructor.
|
|
65
|
+
*
|
|
66
|
+
* @param gcpLengthCache
|
|
67
|
+
* GS1 Company Prefix length cache.
|
|
68
|
+
*/
|
|
69
|
+
constructor(gcpLengthCache: GCPLengthCache) {
|
|
70
|
+
this.#gcpLengthCache = gcpLengthCache;
|
|
71
|
+
}
|
|
75
72
|
|
|
76
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Get the GS1 Company Prefix length tree root.
|
|
75
|
+
*
|
|
76
|
+
* @returns
|
|
77
|
+
* GS1 Company Prefix length tree root.
|
|
78
|
+
*/
|
|
79
|
+
get root(): GCPLengthTree.Root {
|
|
80
|
+
if (this.#root === undefined) {
|
|
81
|
+
throw new RangeError(i18nextGS1.t("GCPLength.gs1CompanyPrefixLengthDataNotLoaded"));
|
|
82
|
+
}
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const byte = binaryData[endIndex++];
|
|
84
|
+
return this.#root;
|
|
85
|
+
}
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Get the date/time the GS1 Company Prefix length data was last updated.
|
|
89
|
+
*/
|
|
90
|
+
get dateTime(): Date {
|
|
91
|
+
return this.root.dateTime;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Get the disclaimer for the GS1 Company Prefix length data.
|
|
96
|
+
*/
|
|
97
|
+
get disclaimer(): string {
|
|
98
|
+
return this.root.disclaimer;
|
|
99
|
+
}
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Build the GS1 Company Prefix length tree from a binary data array.
|
|
103
|
+
*
|
|
104
|
+
* @param binaryData
|
|
105
|
+
* Binary data array.
|
|
106
|
+
*
|
|
107
|
+
* @param childNodes
|
|
108
|
+
* Child nodes array to fill.
|
|
109
|
+
*
|
|
110
|
+
* @param startIndex
|
|
111
|
+
* Start index into binary data array.
|
|
112
|
+
*
|
|
113
|
+
* @returns
|
|
114
|
+
* End index into binary data array.
|
|
115
|
+
*/
|
|
116
|
+
static #fromBinary(binaryData: Uint8Array, childNodes: Array<GCPLengthTree.Node | undefined>, startIndex: number): number {
|
|
117
|
+
let endIndex = startIndex;
|
|
118
|
+
|
|
119
|
+
const decompressedLengths = new Array<number>(10);
|
|
120
|
+
|
|
121
|
+
// Decompress lengths for the child nodes.
|
|
122
|
+
for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex += 2) {
|
|
123
|
+
const byte = binaryData[endIndex++];
|
|
124
|
+
|
|
125
|
+
decompressedLengths[childNodeIndex] = byte >> 4;
|
|
126
|
+
decompressedLengths[childNodeIndex + 1] = byte & 0x0F;
|
|
127
|
+
}
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
childNodes: []
|
|
95
|
-
};
|
|
129
|
+
for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex++) {
|
|
130
|
+
const length = decompressedLengths[childNodeIndex];
|
|
96
131
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
childNode = {
|
|
100
|
-
length
|
|
101
|
-
};
|
|
102
|
-
}
|
|
132
|
+
if (length !== GCPLength.#BINARY_UNDEFINED) {
|
|
133
|
+
let childNode: GCPLengthTree.Node;
|
|
103
134
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
}
|
|
135
|
+
if (length === GCPLength.#BINARY_BRANCH) {
|
|
136
|
+
const childNodes: Array<GCPLengthTree.Node | undefined> = [];
|
|
108
137
|
|
|
109
|
-
|
|
110
|
-
}
|
|
138
|
+
endIndex = GCPLength.#fromBinary(binaryData, childNodes, endIndex);
|
|
111
139
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
* Number of branches added; used to determine size of binary array.
|
|
126
|
-
*/
|
|
127
|
-
function addEntry(branch: Branch, prefix: string, length: number): number {
|
|
128
|
-
const digit = Number(prefix.charAt(0));
|
|
140
|
+
childNode = {
|
|
141
|
+
childNodes
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
childNode = {
|
|
145
|
+
length
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// eslint-disable-next-line no-param-reassign -- Purpose is to build tree.
|
|
150
|
+
childNodes[childNodeIndex] = childNode;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
129
153
|
|
|
130
|
-
|
|
154
|
+
return endIndex;
|
|
155
|
+
}
|
|
131
156
|
|
|
132
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Add an entry to the tree by recursively walking the tree to a leaf.
|
|
159
|
+
*
|
|
160
|
+
* @param branch
|
|
161
|
+
* Current (interim) branch under construction.
|
|
162
|
+
*
|
|
163
|
+
* @param prefix
|
|
164
|
+
* Remainder of current prefix.
|
|
165
|
+
*
|
|
166
|
+
* @param length
|
|
167
|
+
* Current prefix length.
|
|
168
|
+
*
|
|
169
|
+
* @returns
|
|
170
|
+
* Number of branches added; used to determine size of binary array.
|
|
171
|
+
*/
|
|
172
|
+
static #addEntry(branch: InterimBranch, prefix: string, length: number): number {
|
|
173
|
+
const digit = Number(prefix.charAt(0));
|
|
174
|
+
|
|
175
|
+
const existingChildNode = branch.childNodes[digit];
|
|
176
|
+
|
|
177
|
+
let branchesAdded = 0;
|
|
178
|
+
|
|
179
|
+
if (prefix.length !== 1) {
|
|
180
|
+
let childBranch: InterimBranch;
|
|
181
|
+
|
|
182
|
+
if (existingChildNode === undefined) {
|
|
183
|
+
childBranch = {
|
|
184
|
+
childNodes: []
|
|
185
|
+
};
|
|
133
186
|
|
|
134
|
-
|
|
135
|
-
|
|
187
|
+
branchesAdded++;
|
|
188
|
+
} else {
|
|
189
|
+
if (GCPLengthTree.isLeaf(existingChildNode)) {
|
|
190
|
+
// File format error or application bug; localization not necessary.
|
|
191
|
+
throw new Error("Overlapping entry");
|
|
192
|
+
}
|
|
136
193
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
childNodes: []
|
|
140
|
-
};
|
|
194
|
+
childBranch = existingChildNode;
|
|
195
|
+
}
|
|
141
196
|
|
|
142
|
-
|
|
197
|
+
// eslint-disable-next-line no-param-reassign -- Purpose is to build tree.
|
|
198
|
+
branch.childNodes[digit] = childBranch;
|
|
199
|
+
|
|
200
|
+
// Continue with remainder of prefix.
|
|
201
|
+
branchesAdded += GCPLength.#addEntry(childBranch, prefix.substring(1), length);
|
|
143
202
|
} else {
|
|
144
|
-
if (
|
|
203
|
+
if (existingChildNode !== undefined) {
|
|
145
204
|
// File format error or application bug; localization not necessary.
|
|
146
|
-
throw new Error("
|
|
205
|
+
throw new Error("Duplicate entry");
|
|
147
206
|
}
|
|
148
207
|
|
|
149
|
-
|
|
208
|
+
// eslint-disable-next-line no-param-reassign -- Purpose is to build tree.
|
|
209
|
+
branch.childNodes[digit] = {
|
|
210
|
+
length
|
|
211
|
+
};
|
|
150
212
|
}
|
|
151
213
|
|
|
152
|
-
|
|
153
|
-
|
|
214
|
+
return branchesAdded;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get the length of a child node if defined and it's a leaf, otherwise mark it with 0x0E if defined (branch) or 0x0F
|
|
219
|
+
* (no branch) if not.
|
|
220
|
+
*
|
|
221
|
+
* @param childNode
|
|
222
|
+
* Child node.
|
|
223
|
+
*
|
|
224
|
+
* @returns
|
|
225
|
+
* Child node length, 0x0E (branch), or 0x0F (undefined).
|
|
226
|
+
*/
|
|
227
|
+
static #childNodeValue(childNode: GCPLengthTree.Node | undefined): number {
|
|
228
|
+
return childNode !== undefined ? GCPLengthTree.isLeaf(childNode) ? childNode.length : GCPLength.#BINARY_BRANCH : GCPLength.#BINARY_UNDEFINED;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Add a branch to a binary data array.
|
|
233
|
+
*
|
|
234
|
+
* @param binaryData
|
|
235
|
+
* Binary data array.
|
|
236
|
+
*
|
|
237
|
+
* @param branch
|
|
238
|
+
* Branch.
|
|
239
|
+
*
|
|
240
|
+
* @param startIndex
|
|
241
|
+
* Start index into binary data array.
|
|
242
|
+
*
|
|
243
|
+
* @returns
|
|
244
|
+
* End index into binary data array.
|
|
245
|
+
*/
|
|
246
|
+
static #toBinary(binaryData: Uint8Array, branch: GCPLengthTree.Branch, startIndex: number): number {
|
|
247
|
+
let endIndex = startIndex;
|
|
248
|
+
|
|
249
|
+
const childNodes = branch.childNodes;
|
|
250
|
+
|
|
251
|
+
// Add length or non-leaf indicators, compressing 10 nibbles into 5 bytes.
|
|
252
|
+
for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex += 2) {
|
|
253
|
+
// eslint-disable-next-line no-param-reassign -- Purpose is to build array.
|
|
254
|
+
binaryData[endIndex++] = (GCPLength.#childNodeValue(childNodes[childNodeIndex]) << 4) | GCPLength.#childNodeValue(childNodes[childNodeIndex + 1]);
|
|
255
|
+
}
|
|
154
256
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
throw new Error("Duplicate entry");
|
|
257
|
+
// Process child nodes.
|
|
258
|
+
for (const childNode of childNodes) {
|
|
259
|
+
if (childNode !== undefined && !GCPLengthTree.isLeaf(childNode)) {
|
|
260
|
+
endIndex = GCPLength.#toBinary(binaryData, childNode, endIndex);
|
|
261
|
+
}
|
|
161
262
|
}
|
|
162
263
|
|
|
163
|
-
|
|
164
|
-
branch.childNodes[digit] = {
|
|
165
|
-
length
|
|
166
|
-
};
|
|
264
|
+
return endIndex;
|
|
167
265
|
}
|
|
168
266
|
|
|
169
|
-
|
|
170
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Add days to a date.
|
|
269
|
+
*
|
|
270
|
+
* @param date
|
|
271
|
+
* Date.
|
|
272
|
+
*
|
|
273
|
+
* @param days
|
|
274
|
+
* Days.
|
|
275
|
+
*
|
|
276
|
+
* @returns
|
|
277
|
+
* Future date.
|
|
278
|
+
*/
|
|
279
|
+
static #addDays(date: Date, days: number): Date {
|
|
280
|
+
const futureDate = new Date(date);
|
|
281
|
+
|
|
282
|
+
futureDate.setDate(futureDate.getDate() + days);
|
|
283
|
+
|
|
284
|
+
return futureDate;
|
|
285
|
+
}
|
|
171
286
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
* Child node.
|
|
178
|
-
*
|
|
179
|
-
* @returns
|
|
180
|
-
* Child node length, 0x0E (branch), or 0x0F (undefined).
|
|
181
|
-
*/
|
|
182
|
-
function childNodeValue(childNode: Node | undefined): number {
|
|
183
|
-
return childNode !== undefined ? isLeaf(childNode) ? childNode.length : BINARY_BRANCH : BINARY_UNDEFINED;
|
|
184
|
-
}
|
|
287
|
+
/**
|
|
288
|
+
* Load the GS1 Company Prefix length data.
|
|
289
|
+
*/
|
|
290
|
+
async load(): Promise<void> {
|
|
291
|
+
let root: GCPLengthTree.Root | undefined = undefined;
|
|
185
292
|
|
|
186
|
-
|
|
187
|
-
* Add a branch to a binary data array.
|
|
188
|
-
*
|
|
189
|
-
* @param binaryData
|
|
190
|
-
* Binary data array.
|
|
191
|
-
*
|
|
192
|
-
* @param branch
|
|
193
|
-
* Branch.
|
|
194
|
-
*
|
|
195
|
-
* @param startIndex
|
|
196
|
-
* Start index into binary data array.
|
|
197
|
-
*
|
|
198
|
-
* @returns
|
|
199
|
-
* End index into binary data array.
|
|
200
|
-
*/
|
|
201
|
-
function toBinary(binaryData: Uint8Array, branch: Branch, startIndex: number): number {
|
|
202
|
-
let endIndex = startIndex;
|
|
293
|
+
const gcpLengthCache = this.#gcpLengthCache;
|
|
203
294
|
|
|
204
|
-
|
|
295
|
+
let nextCheckDateTime = await gcpLengthCache.nextCheckDateTime;
|
|
296
|
+
let cacheDateTime = await gcpLengthCache.cacheDateTime;
|
|
205
297
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// eslint-disable-next-line no-param-reassign -- Purpose is to build array.
|
|
209
|
-
binaryData[endIndex++] = (childNodeValue(childNodes[childNodeIndex]) << 4) | childNodeValue(childNodes[childNodeIndex + 1]);
|
|
210
|
-
}
|
|
298
|
+
const now = new Date();
|
|
299
|
+
const tomorrow = GCPLength.#addDays(now, 1);
|
|
211
300
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (childNode !== undefined && !isLeaf(childNode)) {
|
|
215
|
-
endIndex = toBinary(binaryData, childNode, endIndex);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
301
|
+
if (nextCheckDateTime === undefined || nextCheckDateTime.getTime() <= now.getTime()) {
|
|
302
|
+
const sourceDateTime = await gcpLengthCache.sourceDateTime;
|
|
218
303
|
|
|
219
|
-
|
|
220
|
-
|
|
304
|
+
if (cacheDateTime === undefined || cacheDateTime.getTime() < sourceDateTime.getTime()) {
|
|
305
|
+
const sourceData = await gcpLengthCache.sourceData;
|
|
221
306
|
|
|
222
|
-
|
|
223
|
-
* Add days to a date.
|
|
224
|
-
*
|
|
225
|
-
* @param date
|
|
226
|
-
* Date.
|
|
227
|
-
*
|
|
228
|
-
* @param days
|
|
229
|
-
* Days.
|
|
230
|
-
*
|
|
231
|
-
* @returns
|
|
232
|
-
* New date.
|
|
233
|
-
*/
|
|
234
|
-
function addDays(date: Date, days: number): Date {
|
|
235
|
-
const futureDate = new Date(date);
|
|
236
|
-
futureDate.setDate(futureDate.getDate() + days);
|
|
307
|
+
let cacheData: GCPLengthData;
|
|
237
308
|
|
|
238
|
-
|
|
239
|
-
|
|
309
|
+
if (isGCPLengthData(sourceData)) {
|
|
310
|
+
const childNodes: Array<GCPLengthTree.Node | undefined> = [];
|
|
240
311
|
|
|
241
|
-
|
|
242
|
-
* Load the GS1 Company Prefix length data.
|
|
243
|
-
*
|
|
244
|
-
* @param gcpLengthCache
|
|
245
|
-
* GS1 Company Prefix length cache.
|
|
246
|
-
*
|
|
247
|
-
* @returns
|
|
248
|
-
* Root of tree.
|
|
249
|
-
*/
|
|
250
|
-
export async function loadData(gcpLengthCache: GCPLengthCache): Promise<Root | undefined> {
|
|
251
|
-
let root: Root | undefined = undefined;
|
|
312
|
+
GCPLength.#fromBinary(sourceData.data, childNodes, 0);
|
|
252
313
|
|
|
253
|
-
|
|
254
|
-
|
|
314
|
+
root = {
|
|
315
|
+
dateTime: sourceData.dateTime,
|
|
316
|
+
disclaimer: sourceData.disclaimer,
|
|
317
|
+
childNodes
|
|
318
|
+
};
|
|
255
319
|
|
|
256
|
-
|
|
257
|
-
|
|
320
|
+
cacheData = sourceData;
|
|
321
|
+
} else {
|
|
322
|
+
const interimRoot: InterimBranch = {
|
|
323
|
+
childNodes: []
|
|
324
|
+
};
|
|
258
325
|
|
|
259
|
-
|
|
260
|
-
const sourceDateTime = await gcpLengthCache.sourceDateTime;
|
|
326
|
+
let branchesAdded = 1;
|
|
261
327
|
|
|
262
|
-
|
|
263
|
-
|
|
328
|
+
for (const entry of sourceData.GCPPrefixFormatList.entry) {
|
|
329
|
+
branchesAdded += GCPLength.#addEntry(interimRoot, entry.prefix, entry.gcpLength);
|
|
330
|
+
}
|
|
264
331
|
|
|
265
|
-
|
|
332
|
+
root = {
|
|
333
|
+
dateTime: new Date(sourceData.GCPPrefixFormatList.date),
|
|
334
|
+
// Join disclaimer as a single string.
|
|
335
|
+
disclaimer: `${sourceData._disclaimer.reduce<string[]>((lines, line) => {
|
|
336
|
+
if (lines.length === 0 || lines[lines.length - 1] === "" || line === "") {
|
|
337
|
+
lines.push(line);
|
|
338
|
+
} else {
|
|
339
|
+
// Lines are part of the same paragraph.
|
|
340
|
+
lines.push(`${lines.pop()} ${line}`);
|
|
341
|
+
}
|
|
266
342
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
childNodes: []
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
fromBinary(sourceData.data, root.childNodes, 0);
|
|
343
|
+
return lines;
|
|
344
|
+
}, []).join("\n")}\n`,
|
|
345
|
+
...interimRoot
|
|
346
|
+
};
|
|
275
347
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
root = {
|
|
279
|
-
dateTime: new Date(sourceData.GCPPrefixFormatList.date),
|
|
280
|
-
// Join disclaimer as a single string.
|
|
281
|
-
disclaimer: `${sourceData._disclaimer.reduce<string[]>((lines, line) => {
|
|
282
|
-
if (lines.length === 0 || lines[lines.length - 1] === "" || line === "") {
|
|
283
|
-
lines.push(line);
|
|
284
|
-
} else {
|
|
285
|
-
// Lines are part of the same paragraph.
|
|
286
|
-
lines.push(`${lines.pop()} ${line}`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return lines;
|
|
290
|
-
}, []).join("\n")}\n`,
|
|
291
|
-
childNodes: []
|
|
292
|
-
};
|
|
348
|
+
// Each branch has ten (some possibly undefined) entries, two per byte.
|
|
349
|
+
const data = new Uint8Array(branchesAdded * 5);
|
|
293
350
|
|
|
294
|
-
|
|
351
|
+
GCPLength.#toBinary(data, root, 0);
|
|
295
352
|
|
|
296
|
-
|
|
297
|
-
|
|
353
|
+
cacheData = {
|
|
354
|
+
...omit(root, "childNodes"),
|
|
355
|
+
data
|
|
356
|
+
};
|
|
298
357
|
}
|
|
299
358
|
|
|
300
|
-
//
|
|
301
|
-
|
|
359
|
+
// Next check date/time is a week from source date/time or tomorrow, whichever is later.
|
|
360
|
+
nextCheckDateTime = GCPLength.#addDays(sourceDateTime, 7);
|
|
361
|
+
if (nextCheckDateTime.getTime() < tomorrow.getTime()) {
|
|
362
|
+
nextCheckDateTime = tomorrow;
|
|
363
|
+
}
|
|
302
364
|
|
|
303
|
-
|
|
365
|
+
cacheDateTime = sourceDateTime;
|
|
304
366
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Next check date/time is a week from source date/time or tomorrow, whichever is later.
|
|
312
|
-
nextCheckDateTime = addDays(sourceDateTime, 7);
|
|
313
|
-
if (nextCheckDateTime.getTime() < tomorrow.getTime()) {
|
|
314
|
-
nextCheckDateTime = tomorrow;
|
|
367
|
+
await gcpLengthCache.update(nextCheckDateTime, cacheDateTime, cacheData);
|
|
368
|
+
} else {
|
|
369
|
+
// Next check date/time is tomorrow.
|
|
370
|
+
await gcpLengthCache.update(tomorrow);
|
|
315
371
|
}
|
|
372
|
+
}
|
|
316
373
|
|
|
317
|
-
|
|
374
|
+
// Root is undefined if cache data is still valid or retrying after prior failure.
|
|
375
|
+
if (root === undefined && cacheDateTime !== undefined) {
|
|
376
|
+
const cacheData = await gcpLengthCache.cacheData;
|
|
318
377
|
|
|
319
|
-
|
|
320
|
-
} else {
|
|
321
|
-
// Next check date/time is tomorrow.
|
|
322
|
-
await gcpLengthCache.update(tomorrow);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
378
|
+
const childNodes: Array<GCPLengthTree.Node | undefined> = [];
|
|
325
379
|
|
|
326
|
-
|
|
327
|
-
if (root === undefined && cacheDateTime !== undefined) {
|
|
328
|
-
const cacheData = await gcpLengthCache.cacheData;
|
|
380
|
+
GCPLength.#fromBinary(cacheData.data, childNodes, 0);
|
|
329
381
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
382
|
+
root = {
|
|
383
|
+
...omit(cacheData, "data"),
|
|
384
|
+
childNodes
|
|
385
|
+
};
|
|
386
|
+
}
|
|
334
387
|
|
|
335
|
-
|
|
388
|
+
// Update root only if successful.
|
|
389
|
+
if (root !== undefined) {
|
|
390
|
+
this.#root = root;
|
|
391
|
+
}
|
|
336
392
|
}
|
|
337
393
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Get the length of a GS1 Company Prefix for an identifier.
|
|
396
|
+
*
|
|
397
|
+
* @param identifierType
|
|
398
|
+
* Identifier type.
|
|
399
|
+
*
|
|
400
|
+
* @param identifier
|
|
401
|
+
* Identifier.
|
|
402
|
+
*
|
|
403
|
+
* @returns
|
|
404
|
+
* Length of GS1 Company Prefix, 0 if not a GS1 Company Prefix, or -1 if not found.
|
|
405
|
+
*/
|
|
406
|
+
lengthOf(identifierType: IdentifierType, identifier: string): number {
|
|
407
|
+
let identifierPrefix: string;
|
|
408
|
+
|
|
409
|
+
if (identifierType === IdentifierTypes.GTIN) {
|
|
410
|
+
// Normalize the GTIN, pad to 14 digits, and extract the identifier prefix at the first character.
|
|
411
|
+
identifierPrefix = GTINValidator.normalize(identifier).padStart(GTINLengths.GTIN14, "0").substring(1);
|
|
412
|
+
} else {
|
|
413
|
+
const identifierValidator = IdentifierValidators[identifierType];
|
|
358
414
|
|
|
359
|
-
|
|
360
|
-
// Normalize the GTIN, pad to 14 digits, and extract the identifier prefix at the first character.
|
|
361
|
-
identifierPrefix = GTINValidator.normalize(identifier).padStart(GTINLengths.GTIN14, "0").substring(1);
|
|
362
|
-
} else {
|
|
363
|
-
const identifierValidator = IdentifierValidators[identifierType];
|
|
415
|
+
identifierValidator.validate(identifier);
|
|
364
416
|
|
|
365
|
-
|
|
417
|
+
identifierPrefix = !isNumericIdentifierValidator(identifierValidator) || identifierValidator.leaderType !== LeaderTypes.ExtensionDigit ? identifier : identifier.substring(1);
|
|
418
|
+
}
|
|
366
419
|
|
|
367
|
-
|
|
368
|
-
}
|
|
420
|
+
let node: GCPLengthTree.Node | undefined = this.root;
|
|
369
421
|
|
|
370
|
-
|
|
422
|
+
let digitIndex = 0;
|
|
371
423
|
|
|
372
|
-
|
|
424
|
+
// Traverse tree until exhausted or at a leaf.
|
|
425
|
+
while (node !== undefined && !GCPLengthTree.isLeaf(node)) {
|
|
426
|
+
node = node.childNodes[Number(identifierPrefix.charAt(digitIndex++))];
|
|
427
|
+
}
|
|
373
428
|
|
|
374
|
-
|
|
375
|
-
while (node !== undefined && !isLeaf(node)) {
|
|
376
|
-
node = node.childNodes[Number(identifierPrefix.charAt(digitIndex++))];
|
|
429
|
+
return node?.length ?? -1;
|
|
377
430
|
}
|
|
378
|
-
|
|
379
|
-
return node?.length ?? -1;
|
|
380
431
|
}
|