@aidc-toolkit/gs1 1.0.40-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.
Files changed (39) hide show
  1. package/README.md +2 -19
  2. package/dist/gcp-length-cache.d.ts +6 -1
  3. package/dist/gcp-length-cache.d.ts.map +1 -1
  4. package/dist/gcp-length-cache.js +7 -2
  5. package/dist/gcp-length-cache.js.map +1 -1
  6. package/dist/gcp-length-tree.d.ts +33 -0
  7. package/dist/gcp-length-tree.d.ts.map +1 -0
  8. package/dist/gcp-length-tree.js +13 -0
  9. package/dist/gcp-length-tree.js.map +1 -0
  10. package/dist/gcp-length.d.ts +63 -49
  11. package/dist/gcp-length.d.ts.map +1 -1
  12. package/dist/gcp-length.js +319 -262
  13. package/dist/gcp-length.js.map +1 -1
  14. package/dist/index.d.ts +2 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/locale/en/locale-resources.d.ts +2 -0
  19. package/dist/locale/en/locale-resources.d.ts.map +1 -1
  20. package/dist/locale/en/locale-resources.js +3 -1
  21. package/dist/locale/en/locale-resources.js.map +1 -1
  22. package/dist/locale/fr/locale-resources.d.ts +2 -0
  23. package/dist/locale/fr/locale-resources.d.ts.map +1 -1
  24. package/dist/locale/fr/locale-resources.js +3 -1
  25. package/dist/locale/fr/locale-resources.js.map +1 -1
  26. package/dist/prefix-manager.d.ts +0 -35
  27. package/dist/prefix-manager.d.ts.map +1 -1
  28. package/dist/prefix-manager.js +0 -56
  29. package/dist/prefix-manager.js.map +1 -1
  30. package/package.json +4 -4
  31. package/src/gcp-length-cache.ts +7 -2
  32. package/src/gcp-length-tree.ts +39 -0
  33. package/src/gcp-length.ts +348 -297
  34. package/src/index.ts +3 -1
  35. package/src/locale/en/locale-resources.ts +3 -1
  36. package/src/locale/fr/locale-resources.ts +3 -1
  37. package/src/prefix-manager.ts +0 -65
  38. package/test/gcp-length.test.ts +39 -31
  39. 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, type GCPLengthHeader, isGCPLengthData } from "./gcp-length-data.js";
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
- * Leaf of GS1 Company Prefix length tree.
13
+ * Interim branch of GS1 Company Prefix length tree with writeable child nodes.
12
14
  */
13
- export interface Leaf {
14
- length: number;
15
+ export interface InterimBranch {
16
+ readonly childNodes: Array<InterimBranch | GCPLengthTree.Leaf | undefined>;
15
17
  }
16
18
 
17
19
  /**
18
- * Branch of GS1 Company Prefix length tree.
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
- * @param node
39
- * Node.
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
- * @returns
42
- * True if node is a leaf.
43
- */
44
- export function isLeaf(node: Node): node is Leaf {
45
- return "length" in node;
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
- * @param childNodes
65
- * Child nodes array to fill.
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
- * @param startIndex
68
- * Start index into binary data array.
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
- function fromBinary(binaryData: Uint8Array, childNodes: Array<Node | undefined>, startIndex: number): number {
74
- let endIndex = startIndex;
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
- const decompressedLengths = new Array<number>(10);
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
- // Decompress lengths for the child nodes.
79
- for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex += 2) {
80
- const byte = binaryData[endIndex++];
84
+ return this.#root;
85
+ }
81
86
 
82
- decompressedLengths[childNodeIndex] = byte >> 4;
83
- decompressedLengths[childNodeIndex + 1] = byte & 0x0F;
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
- for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex++) {
87
- const length = decompressedLengths[childNodeIndex];
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
- if (length !== BINARY_UNDEFINED) {
90
- let childNode: Node;
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
- if (length === BINARY_BRANCH) {
93
- childNode = {
94
- childNodes: []
95
- };
129
+ for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex++) {
130
+ const length = decompressedLengths[childNodeIndex];
96
131
 
97
- endIndex = fromBinary(binaryData, childNode.childNodes, endIndex);
98
- } else {
99
- childNode = {
100
- length
101
- };
102
- }
132
+ if (length !== GCPLength.#BINARY_UNDEFINED) {
133
+ let childNode: GCPLengthTree.Node;
103
134
 
104
- // eslint-disable-next-line no-param-reassign -- Purpose is to build tree.
105
- childNodes[childNodeIndex] = childNode;
106
- }
107
- }
135
+ if (length === GCPLength.#BINARY_BRANCH) {
136
+ const childNodes: Array<GCPLengthTree.Node | undefined> = [];
108
137
 
109
- return endIndex;
110
- }
138
+ endIndex = GCPLength.#fromBinary(binaryData, childNodes, endIndex);
111
139
 
112
- /**
113
- * Add an entry to the tree by recursively walking the tree to a leaf.
114
- *
115
- * @param branch
116
- * Current branch.
117
- *
118
- * @param prefix
119
- * Remainder of current prefix.
120
- *
121
- * @param length
122
- * Current prefix length.
123
- *
124
- * @returns
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
- const existingChildNode = branch.childNodes[digit];
154
+ return endIndex;
155
+ }
131
156
 
132
- let branchesAdded = 0;
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
- if (prefix.length !== 1) {
135
- let childBranch: Branch;
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
- if (existingChildNode === undefined) {
138
- childBranch = {
139
- childNodes: []
140
- };
194
+ childBranch = existingChildNode;
195
+ }
141
196
 
142
- branchesAdded++;
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 (isLeaf(existingChildNode)) {
203
+ if (existingChildNode !== undefined) {
145
204
  // File format error or application bug; localization not necessary.
146
- throw new Error("Overlapping entry");
205
+ throw new Error("Duplicate entry");
147
206
  }
148
207
 
149
- childBranch = existingChildNode;
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
- // eslint-disable-next-line no-param-reassign -- Purpose is to build tree.
153
- branch.childNodes[digit] = childBranch;
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
- // Continue with remainder of prefix.
156
- branchesAdded += addEntry(childBranch, prefix.substring(1), length);
157
- } else {
158
- if (existingChildNode !== undefined) {
159
- // File format error or application bug; localization not necessary.
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
- // eslint-disable-next-line no-param-reassign -- Purpose is to build tree.
164
- branch.childNodes[digit] = {
165
- length
166
- };
264
+ return endIndex;
167
265
  }
168
266
 
169
- return branchesAdded;
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
- * Get the length of a child node if defined and it's a leaf, otherwise mark it with 0x0E if defined (branch) or 0x0F
174
- * (no branch) if not.
175
- *
176
- * @param childNode
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
- const childNodes = branch.childNodes;
295
+ let nextCheckDateTime = await gcpLengthCache.nextCheckDateTime;
296
+ let cacheDateTime = await gcpLengthCache.cacheDateTime;
205
297
 
206
- // Add length or non-leaf indicators, compressing 10 nibbles into 5 bytes.
207
- for (let childNodeIndex = 0; childNodeIndex < 10; childNodeIndex += 2) {
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
- // Process child nodes.
213
- for (const childNode of childNodes) {
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
- return endIndex;
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
- return futureDate;
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
- let nextCheckDateTime = await gcpLengthCache.nextCheckDateTime;
254
- let cacheDateTime = await gcpLengthCache.cacheDateTime;
314
+ root = {
315
+ dateTime: sourceData.dateTime,
316
+ disclaimer: sourceData.disclaimer,
317
+ childNodes
318
+ };
255
319
 
256
- const now = new Date();
257
- const tomorrow = addDays(now, 1);
320
+ cacheData = sourceData;
321
+ } else {
322
+ const interimRoot: InterimBranch = {
323
+ childNodes: []
324
+ };
258
325
 
259
- if (nextCheckDateTime === undefined || nextCheckDateTime.getTime() <= now.getTime()) {
260
- const sourceDateTime = await gcpLengthCache.sourceDateTime;
326
+ let branchesAdded = 1;
261
327
 
262
- if (cacheDateTime === undefined || cacheDateTime.getTime() < sourceDateTime.getTime()) {
263
- const sourceData = await gcpLengthCache.sourceData;
328
+ for (const entry of sourceData.GCPPrefixFormatList.entry) {
329
+ branchesAdded += GCPLength.#addEntry(interimRoot, entry.prefix, entry.gcpLength);
330
+ }
264
331
 
265
- let cacheData: GCPLengthData;
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
- if (isGCPLengthData(sourceData)) {
268
- root = {
269
- dateTime: sourceData.dateTime,
270
- disclaimer: sourceData.disclaimer,
271
- childNodes: []
272
- };
273
-
274
- fromBinary(sourceData.data, root.childNodes, 0);
343
+ return lines;
344
+ }, []).join("\n")}\n`,
345
+ ...interimRoot
346
+ };
275
347
 
276
- cacheData = sourceData;
277
- } else {
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
- let branchesAdded = 1;
351
+ GCPLength.#toBinary(data, root, 0);
295
352
 
296
- for (const entry of sourceData.GCPPrefixFormatList.entry) {
297
- branchesAdded += addEntry(root, entry.prefix, entry.gcpLength);
353
+ cacheData = {
354
+ ...omit(root, "childNodes"),
355
+ data
356
+ };
298
357
  }
299
358
 
300
- // Each branch has ten (some possibly undefined) entries, two per byte.
301
- const data = new Uint8Array(branchesAdded * 5);
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
- toBinary(data, root, 0);
365
+ cacheDateTime = sourceDateTime;
304
366
 
305
- cacheData = {
306
- ...omit(root, "childNodes"),
307
- data
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
- cacheDateTime = sourceDateTime;
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
- await gcpLengthCache.update(nextCheckDateTime, cacheDateTime, cacheData);
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
- // Root is undefined if cache data is still valid or retrying after prior failure.
327
- if (root === undefined && cacheDateTime !== undefined) {
328
- const cacheData = await gcpLengthCache.cacheData;
380
+ GCPLength.#fromBinary(cacheData.data, childNodes, 0);
329
381
 
330
- root = {
331
- ...omit(cacheData, "data"),
332
- childNodes: []
333
- };
382
+ root = {
383
+ ...omit(cacheData, "data"),
384
+ childNodes
385
+ };
386
+ }
334
387
 
335
- fromBinary(cacheData.data, root.childNodes, 0);
388
+ // Update root only if successful.
389
+ if (root !== undefined) {
390
+ this.#root = root;
391
+ }
336
392
  }
337
393
 
338
- return root;
339
- }
340
-
341
- /**
342
- * Get the length of a GS1 Company Prefix for an identifier.
343
- *
344
- * @param root
345
- * Root of tree.
346
- *
347
- * @param identifierType
348
- * Identifier type.
349
- *
350
- * @param identifier
351
- * Identifier.
352
- *
353
- * @returns
354
- * Length of GS1 Company Prefix, 0 if not a GS1 Company Prefix, or -1 if not found.
355
- */
356
- export function getFor(root: Root, identifierType: IdentifierType, identifier: string): number {
357
- let identifierPrefix: string;
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
- if (identifierType === IdentifierTypes.GTIN) {
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
- identifierValidator.validate(identifier);
417
+ identifierPrefix = !isNumericIdentifierValidator(identifierValidator) || identifierValidator.leaderType !== LeaderTypes.ExtensionDigit ? identifier : identifier.substring(1);
418
+ }
366
419
 
367
- identifierPrefix = !isNumericIdentifierValidator(identifierValidator) || identifierValidator.leaderType !== LeaderTypes.ExtensionDigit ? identifier : identifier.substring(1);
368
- }
420
+ let node: GCPLengthTree.Node | undefined = this.root;
369
421
 
370
- let node: Node | undefined = root;
422
+ let digitIndex = 0;
371
423
 
372
- let digitIndex = 0;
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
- // Traverse tree until exhausted or at a leaf.
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
  }