flatpack-json 9.7.0 → 9.8.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.
@@ -1,67 +1,6 @@
1
+ import type { StringifyOptions } from './stringify.mjs';
1
2
  import type { Flatpacked, FlatpackOptions, Serializable, Unpacked } from './types.mjs';
2
- export declare class CompactStorage {
3
- readonly options?: FlatpackOptions | undefined;
4
- private data;
5
- private dedupe;
6
- private sortKeys;
7
- private emptyObjIdx;
8
- /**
9
- * Cache of primitives and objects that have been added to the data.
10
- */
11
- private cache;
12
- /**
13
- * Set of indexes that have been referenced by other indexes.
14
- */
15
- private referenced;
16
- /**
17
- * Cache of arrays that have been deduped.
18
- * The key is a hash of the array elements as a function of the index of the element.
19
- */
20
- private cachedArrays;
21
- /**
22
- * Cache of strings used for prefix matching.
23
- */
24
- private knownStrings;
25
- /**
26
- * Cache of reversed strings used for suffix matching.
27
- */
28
- private knownStringsRev;
29
- private cachedElements;
30
- constructor(options?: FlatpackOptions | undefined);
31
- private primitiveToIdx;
32
- private addSubStringRef;
33
- private estimateSubStringCost;
34
- private addKnownString;
35
- private addStringPrimitive;
36
- private duplicateIndex;
37
- private stringToIdx;
38
- private findPrefix;
39
- private findSuffix;
40
- private objSetToIdx;
41
- private createUniqueKeys;
42
- private objMapToIdx;
43
- private objRegExpToIdx;
44
- private objDateToIdx;
45
- private bigintToIdx;
46
- private objToIdx;
47
- private storeElement;
48
- private cacheElement;
49
- private stashArray;
50
- private createArrayElementFromIndexValues;
51
- /**
52
- * Convert an array to an index.
53
- * @param value - The array to convert to an index.
54
- * @param cacheValue - Whether to cache the value.
55
- * @returns the index of the array.
56
- */
57
- private arrToIdx;
58
- private valueToIdx;
59
- /**
60
- * Reset things in a way that allows for reuse.
61
- */
62
- private softReset;
63
- toJSON<V extends Serializable>(json: V): Flatpacked;
64
- }
65
3
  export declare function toJSON<V extends Serializable>(json: V, options?: FlatpackOptions): Flatpacked;
66
- export declare function stringify(data: Unpacked, pretty?: boolean, options?: FlatpackOptions): string;
4
+ export declare function stringify(data: Unpacked, pretty?: boolean, options?: FlatpackOptions & StringifyOptions): string;
5
+ export declare function normalizeOptions(options?: FlatpackOptions): Required<FlatpackOptions>;
67
6
  //# sourceMappingURL=storage.d.mts.map
package/dist/storage.mjs CHANGED
@@ -1,445 +1,37 @@
1
- import assert from 'node:assert';
2
- import { optimizeFlatpacked } from './optimizeFlatpacked.mjs';
1
+ import { CompactStorageV1 } from './storageV1.mjs';
2
+ import { CompactStorageV2 } from './storageV2.mjs';
3
3
  import { stringifyFlatpacked } from './stringify.mjs';
4
- import { Trie } from './Trie.mjs';
5
- import { blockSplitRegex, dataHeader, ElementType } from './types.mjs';
6
- const collator = new Intl.Collator('en', {
7
- usage: 'sort',
8
- numeric: true,
9
- sensitivity: 'variant',
10
- caseFirst: 'upper',
11
- ignorePunctuation: false,
12
- });
13
- const compare = collator.compare;
14
- const forceStringPrimitives = false;
15
- const minSubStringLen = 4;
16
- export class CompactStorage {
17
- options;
18
- data = [dataHeader];
19
- dedupe = true;
20
- sortKeys = true;
21
- emptyObjIdx = 0;
22
- /**
23
- * Cache of primitives and objects that have been added to the data.
24
- */
25
- cache = new Map([[undefined, 0]]);
26
- /**
27
- * Set of indexes that have been referenced by other indexes.
28
- */
29
- referenced = new Set();
30
- /**
31
- * Cache of arrays that have been deduped.
32
- * The key is a hash of the array elements as a function of the index of the element.
33
- */
34
- cachedArrays = new Map();
35
- /**
36
- * Cache of strings used for prefix matching.
37
- */
38
- knownStrings = new Trie();
39
- /**
40
- * Cache of reversed strings used for suffix matching.
41
- */
42
- knownStringsRev = new Trie();
43
- cachedElements = new Map();
44
- constructor(options) {
45
- this.options = options;
46
- this.dedupe = options?.dedupe ?? true;
47
- this.sortKeys = options?.sortKeys || this.dedupe;
48
- }
49
- primitiveToIdx(value) {
50
- if (typeof value === 'string')
51
- return this.stringToIdx(value);
52
- if (typeof value === 'bigint')
53
- return this.bigintToIdx(value);
54
- const found = this.cache.get(value);
55
- if (found !== undefined) {
56
- return found;
57
- }
58
- const idx = this.data.push(value) - 1;
59
- this.cache.set(value, idx);
60
- return idx;
61
- }
62
- addSubStringRef(idxString, value, offset) {
63
- const found = this.cache.get(value);
64
- if (found !== undefined) {
65
- return found;
66
- }
67
- const sub = offset
68
- ? [ElementType.SubString, idxString, value.length, offset]
69
- : [ElementType.SubString, idxString, value.length];
70
- const idx = this.data.push(sub) - 1;
71
- this.cache.set(value, idx);
72
- return idx;
73
- }
74
- estimateSubStringCost(idxString, value, offset) {
75
- if (this.cache.has(value)) {
76
- return 0;
77
- }
78
- let cost = 5;
79
- cost += Math.ceil(Math.log10(idxString));
80
- cost += Math.ceil(Math.log10(value.length));
81
- if (offset) {
82
- cost += Math.ceil(Math.log10(offset)) + 1;
83
- }
84
- return cost;
85
- }
86
- addKnownString(idx, value) {
87
- if (value.length >= minSubStringLen) {
88
- const data = { idx, value };
89
- this.knownStrings.add(value.length > 256 ? [...value].slice(0, 256) : value, data);
90
- this.knownStringsRev.add(revStr(value, 256), data);
91
- }
92
- }
93
- addStringPrimitive(value) {
94
- const idx = this.data.push(value) - 1;
95
- this.addKnownString(idx, value);
96
- this.cache.set(value, idx);
97
- return idx;
98
- }
99
- duplicateIndex(idx) {
100
- const element = this.data[idx];
101
- const duplicate = this.data.push(element) - 1;
102
- return duplicate;
103
- }
104
- stringToIdx(value) {
105
- const found = this.cache.get(value);
106
- if (found !== undefined) {
107
- return found;
108
- }
109
- if (forceStringPrimitives || value.length < minSubStringLen || blockSplitRegex.test(value)) {
110
- return this.addStringPrimitive(value);
111
- }
112
- const foundPrefix = this.findPrefix(value);
113
- const foundSuffix = this.findSuffix(value);
114
- if (!foundPrefix && !foundSuffix) {
115
- return this.addStringPrimitive(value);
116
- }
117
- if (foundPrefix &&
118
- (!foundSuffix ||
119
- foundPrefix.subStr.length - foundPrefix.cost >= foundSuffix.subStr.length - foundSuffix.cost)) {
120
- const { idx: pfIdx, subStr, offset } = foundPrefix;
121
- const sIdx = this.addSubStringRef(pfIdx, subStr, offset);
122
- if (subStr === value)
123
- return sIdx;
124
- const v = [sIdx, this.stringToIdx(value.slice(subStr.length))];
125
- const idx = this.data.push([ElementType.String, ...v]) - 1;
126
- this.cache.set(value, idx);
127
- this.addKnownString(idx, value);
128
- return idx;
129
- }
130
- if (!foundSuffix) {
131
- return this.addStringPrimitive(value);
132
- }
133
- const { idx: pfIdx, subStr, offset } = foundSuffix;
134
- const sIdx = this.addSubStringRef(pfIdx, subStr, offset);
135
- const v = [this.stringToIdx(value.slice(0, -subStr.length)), sIdx];
136
- const idx = this.data.push([ElementType.String, ...v]) - 1;
137
- this.cache.set(value, idx);
138
- this.addKnownString(idx, value);
139
- return idx;
140
- }
141
- findPrefix(value) {
142
- const trieFound = this.knownStrings.find(value);
143
- if (!trieFound || !trieFound.data || trieFound.found.length < minSubStringLen) {
144
- return undefined;
145
- }
146
- const { data: tData, found: subStr } = trieFound;
147
- const cost = this.estimateSubStringCost(tData.idx, subStr, undefined);
148
- if (cost > subStr.length) {
149
- return undefined;
150
- }
151
- return { idx: tData.idx, subStr, offset: undefined, cost };
152
- }
153
- findSuffix(value) {
154
- const rev = revStr(value, 256);
155
- const trieFound = this.knownStringsRev.find(rev);
156
- if (!trieFound || !trieFound.data || trieFound.found.length < minSubStringLen) {
157
- return undefined;
158
- }
159
- const { data: tData, found: subStrRev } = trieFound;
160
- const offset = tData.value.length - subStrRev.length;
161
- const cost = this.estimateSubStringCost(tData.idx, subStrRev, offset);
162
- if (cost > subStrRev.length) {
163
- return undefined;
164
- }
165
- const srcStr = tData.value;
166
- const subStr = srcStr.slice(-subStrRev.length);
167
- return { idx: tData.idx, subStr, offset, cost };
168
- }
169
- objSetToIdx(value) {
170
- const found = this.cache.get(value);
171
- if (found !== undefined) {
172
- this.referenced.add(found);
173
- return found;
174
- }
175
- const idx = this.data.push(0) - 1;
176
- this.cache.set(value, idx);
177
- const keys = [...value];
178
- const k = this.createUniqueKeys(keys, false);
179
- const element = [ElementType.Set, k];
180
- return this.storeElement(value, idx, element);
181
- }
182
- createUniqueKeys(keys, cacheValue = true) {
183
- let k = this.arrToIdx(keys, cacheValue);
184
- const elementKeys = this.data[k];
185
- const uniqueKeys = new Set(elementKeys.slice(1));
186
- if (uniqueKeys.size !== keys.length) {
187
- // one or more of the keys got deduped. We need to duplicate it.
188
- uniqueKeys.clear();
189
- const indexes = elementKeys.slice(1).map((idx) => {
190
- if (uniqueKeys.has(idx)) {
191
- return this.duplicateIndex(idx);
192
- }
193
- uniqueKeys.add(idx);
194
- return idx;
195
- });
196
- k = this.createArrayElementFromIndexValues(this.data.length, indexes);
197
- }
198
- return k;
199
- }
200
- objMapToIdx(value) {
201
- const found = this.cache.get(value);
202
- if (found !== undefined) {
203
- this.referenced.add(found);
204
- return found;
205
- }
206
- const idx = this.data.push(0) - 1;
207
- this.cache.set(value, idx);
208
- const entries = [...value.entries()];
209
- const k = this.createUniqueKeys(entries.map(([key]) => key), false);
210
- const v = this.arrToIdx(entries.map(([, value]) => value), false);
211
- const element = [ElementType.Map, k, v];
212
- return this.storeElement(value, idx, element);
213
- }
214
- objRegExpToIdx(value) {
215
- const found = this.cache.get(value);
216
- if (found !== undefined) {
217
- return found;
218
- }
219
- const idx = this.data.push(0) - 1;
220
- this.cache.set(value, idx);
221
- const element = [
222
- ElementType.RegExp,
223
- this.stringToIdx(value.source),
224
- this.stringToIdx(value.flags),
225
- ];
226
- return this.storeElement(value, idx, element);
227
- }
228
- objDateToIdx(value) {
229
- const found = this.cache.get(value);
230
- if (found !== undefined) {
231
- return found;
232
- }
233
- const idx = this.data.push(0) - 1;
234
- this.cache.set(value, idx);
235
- const element = [ElementType.Date, value.getTime()];
236
- return this.storeElement(value, idx, element);
237
- }
238
- bigintToIdx(value) {
239
- const found = this.cache.get(value);
240
- if (found !== undefined) {
241
- return found;
242
- }
243
- const idx = this.data.push(0) - 1;
244
- this.cache.set(value, idx);
245
- const element = [
246
- ElementType.BigInt,
247
- this.primitiveToIdx(value <= Number.MAX_SAFE_INTEGER && value >= -Number.MAX_SAFE_INTEGER
248
- ? Number(value)
249
- : value.toString()),
250
- ];
251
- return this.storeElement(value, idx, element);
252
- }
253
- objToIdx(value) {
254
- const found = this.cache.get(value);
255
- if (found !== undefined) {
256
- this.referenced.add(found);
257
- return found;
258
- }
259
- if (isObjectWrapper(value)) {
260
- const idx = this.data.push({}) - 1;
261
- this.cache.set(value, idx);
262
- const element = [ElementType.Object, 0, this.valueToIdx(value.valueOf())];
263
- return this.storeElement(value, idx, element);
264
- }
265
- const entries = Object.entries(value);
266
- if (!entries.length) {
267
- if (this.emptyObjIdx) {
268
- return this.emptyObjIdx;
269
- }
270
- const idx = this.data.push({}) - 1;
271
- this.emptyObjIdx = idx;
272
- return idx;
273
- }
274
- const idx = this.data.push(0) - 1;
275
- this.cache.set(value, idx);
276
- if (this.sortKeys) {
277
- entries.sort(([a], [b]) => compare(a, b));
278
- }
279
- const k = this.arrToIdx(entries.map(([key]) => key));
280
- const v = this.arrToIdx(entries.map(([, value]) => value));
281
- const element = [ElementType.Object, k, v];
282
- return this.storeElement(value, idx, element);
283
- }
284
- storeElement(value, idx, element) {
285
- const useIdx = this.dedupe ? this.cacheElement(idx, element) : idx;
286
- if (useIdx !== idx && idx === this.data.length - 1) {
287
- this.data.length = idx;
288
- this.cache.set(value, useIdx);
289
- return useIdx;
290
- }
291
- this.data[idx] = element;
292
- return idx;
293
- }
294
- cacheElement(elemIdx, element) {
295
- let map = this.cachedElements;
296
- for (let i = 0; i < element.length - 1; i++) {
297
- const idx = element[i];
298
- let found = map.get(idx);
299
- if (!found) {
300
- found = new Map();
301
- map.set(idx, found);
302
- }
303
- assert(found instanceof Map);
304
- map = found;
305
- }
306
- const idx = element[element.length - 1];
307
- const foundIdx = map.get(idx);
308
- if (typeof foundIdx === 'number') {
309
- return this.referenced.has(elemIdx) ? elemIdx : foundIdx;
310
- }
311
- map.set(idx, elemIdx);
312
- return elemIdx;
313
- }
314
- stashArray(idx, element) {
315
- const indexHash = simpleHash(element);
316
- let found = this.cachedArrays.get(indexHash);
317
- if (!found) {
318
- found = [];
319
- this.cachedArrays.set(indexHash, found);
320
- }
321
- const foundIdx = found.find((entry) => isEqual(entry.v, element));
322
- if (foundIdx) {
323
- return this.referenced.has(idx) ? idx : foundIdx.idx;
324
- }
325
- found.push({ idx, v: element });
326
- return idx;
327
- }
328
- createArrayElementFromIndexValues(idx, indexValues) {
329
- const element = [ElementType.Array, ...indexValues];
330
- const useIdx = this.dedupe ? this.stashArray(idx, element) : idx;
331
- if (useIdx !== idx) {
332
- assert(this.data.length === idx + 1, `Expected ${idx + 1} but got ${this.data.length}`);
333
- this.data.length = idx;
334
- return useIdx;
335
- }
336
- this.data[idx] = element;
337
- return idx;
338
- }
339
- /**
340
- * Convert an array to an index.
341
- * @param value - The array to convert to an index.
342
- * @param cacheValue - Whether to cache the value.
343
- * @returns the index of the array.
344
- */
345
- arrToIdx(value, cacheValue = true) {
346
- const found = this.cache.get(value);
347
- if (found !== undefined) {
348
- this.referenced.add(found);
349
- return found;
350
- }
351
- const idx = this.data.push(0) - 1;
352
- this.cache.set(value, idx);
353
- const useIdx = this.createArrayElementFromIndexValues(idx, value.map((idx) => this.valueToIdx(idx)));
354
- if (cacheValue) {
355
- this.cache.set(value, useIdx);
356
- }
357
- return useIdx;
358
- }
359
- valueToIdx(value) {
360
- if (value === null) {
361
- return this.primitiveToIdx(value);
362
- }
363
- if (typeof value === 'object') {
364
- if (value instanceof Set) {
365
- return this.objSetToIdx(value);
366
- }
367
- if (value instanceof Map) {
368
- return this.objMapToIdx(value);
369
- }
370
- if (value instanceof RegExp) {
371
- return this.objRegExpToIdx(value);
372
- }
373
- if (Array.isArray(value)) {
374
- return this.arrToIdx(value);
375
- }
376
- if (value instanceof Date) {
377
- return this.objDateToIdx(value);
378
- }
379
- return this.objToIdx(value);
380
- }
381
- return this.primitiveToIdx(value);
382
- }
383
- /**
384
- * Reset things in a way that allows for reuse.
385
- */
386
- softReset() {
387
- this.cache.clear();
388
- this.cache.set(undefined, 0);
389
- }
390
- toJSON(json) {
391
- this.softReset();
392
- this.valueToIdx(json);
393
- return this.options?.optimize ? optimizeFlatpacked(this.data) : this.data;
394
- }
395
- }
396
- function* revStr(str, limit) {
397
- let i = str.length - 1;
398
- let n = 0;
399
- while (i >= 0 && n < limit) {
400
- // eslint-disable-next-line unicorn/prefer-code-point
401
- const code = str.charCodeAt(i);
402
- if (code & 0xfc00) {
403
- // surrogate pair
404
- i -= 1;
405
- yield str.slice(i, i + 2);
406
- }
407
- else {
408
- yield str[i];
409
- }
410
- i -= 1;
411
- n += 1;
412
- }
413
- }
414
- function isEqual(a, b) {
415
- if (a === b)
416
- return true;
417
- if (a.length !== b.length)
418
- return false;
419
- for (let i = 0; i < a.length; i++) {
420
- if (a[i] !== b[i])
421
- return false;
422
- }
423
- return true;
424
- }
425
- function simpleHash(values) {
426
- let hash = Math.sqrt(values.length);
427
- for (const value of values) {
428
- hash += value * value;
429
- }
430
- return hash;
431
- }
432
- function isObjectWrapper(value) {
433
- return (typeof value === 'object' &&
434
- value !== null &&
435
- typeof value.valueOf === 'function' &&
436
- value.valueOf() !== value);
437
- }
4
+ import { dataHeader, dataHeaderV1_0, dataHeaderV2_0 } from './types.mjs';
438
5
  export function toJSON(json, options) {
439
- return new CompactStorage(options).toJSON(json);
6
+ options = normalizeOptions(options);
7
+ return options.format === 'V1'
8
+ ? new CompactStorageV1(options).toJSON(json)
9
+ : new CompactStorageV2(options).toJSON(json);
440
10
  }
441
11
  export function stringify(data, pretty = true, options) {
442
- const json = toJSON(data, options);
443
- return pretty ? stringifyFlatpacked(json) : JSON.stringify(json);
12
+ const normalizedOptions = normalizeOptions(options);
13
+ const json = toJSON(data, normalizedOptions);
14
+ const stringifyOptions = normalizedOptions.format === 'V1' ? undefined : options || {};
15
+ return pretty ? stringifyFlatpacked(json, stringifyOptions) : JSON.stringify(json);
16
+ }
17
+ export function normalizeOptions(options) {
18
+ let header = dataHeader;
19
+ if (options?.format === 'V1') {
20
+ header = dataHeaderV1_0;
21
+ }
22
+ if (options?.format === 'V2') {
23
+ header = dataHeaderV2_0;
24
+ }
25
+ const dedupe = options?.dedupe ?? true;
26
+ const sortKeys = options?.sortKeys || dedupe;
27
+ const result = {
28
+ ...options,
29
+ format: header === dataHeaderV1_0 ? 'V1' : 'V2',
30
+ dedupe,
31
+ sortKeys,
32
+ optimize: options?.optimize ?? false,
33
+ meta: options?.meta,
34
+ };
35
+ return result;
444
36
  }
445
37
  //# sourceMappingURL=storage.mjs.map
@@ -0,0 +1,67 @@
1
+ import { CompactStorage } from './CompactStorage.mjs';
2
+ import type { Flatpacked, FlatpackOptions, Serializable, Unpacked } from './types.mjs';
3
+ export declare class CompactStorageV1 extends CompactStorage {
4
+ private data;
5
+ private dedupe;
6
+ private sortKeys;
7
+ private emptyObjIdx;
8
+ /**
9
+ * Cache of primitives and objects that have been added to the data.
10
+ */
11
+ private cache;
12
+ /**
13
+ * Set of indexes that have been referenced by other indexes.
14
+ */
15
+ private referenced;
16
+ /**
17
+ * Cache of arrays that have been deduped.
18
+ * The key is a hash of the array elements as a function of the index of the element.
19
+ */
20
+ private cachedArrays;
21
+ /**
22
+ * Cache of strings used for prefix matching.
23
+ */
24
+ private knownStrings;
25
+ /**
26
+ * Cache of reversed strings used for suffix matching.
27
+ */
28
+ private knownStringsRev;
29
+ private cachedElements;
30
+ constructor(options?: FlatpackOptions | undefined);
31
+ private primitiveToIdx;
32
+ private addSubStringRef;
33
+ private estimateSubStringCost;
34
+ private addKnownString;
35
+ private addStringPrimitive;
36
+ private duplicateIndex;
37
+ private stringToIdx;
38
+ private findPrefix;
39
+ private findSuffix;
40
+ private objSetToIdx;
41
+ private createUniqueKeys;
42
+ private objMapToIdx;
43
+ private objRegExpToIdx;
44
+ private objDateToIdx;
45
+ private bigintToIdx;
46
+ private objToIdx;
47
+ private storeElement;
48
+ private cacheElement;
49
+ private stashArray;
50
+ private createArrayElementFromIndexValues;
51
+ /**
52
+ * Convert an array to an index.
53
+ * @param value - The array to convert to an index.
54
+ * @param cacheValue - Whether to cache the value.
55
+ * @returns the index of the array.
56
+ */
57
+ private arrToIdx;
58
+ private valueToIdx;
59
+ /**
60
+ * Reset things in a way that allows for reuse.
61
+ */
62
+ private softReset;
63
+ toJSON<V extends Serializable>(json: V): Flatpacked;
64
+ }
65
+ export declare function toJSON<V extends Serializable>(json: V, options?: FlatpackOptions): Flatpacked;
66
+ export declare function stringify(data: Unpacked, pretty?: boolean, options?: FlatpackOptions): string;
67
+ //# sourceMappingURL=storageV1.d.mts.map