flatpack-json 8.19.3 → 9.0.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/dist/flatpackUtil.mjs +2 -2
- package/dist/optimizeFlatpacked.d.mts +3 -0
- package/dist/optimizeFlatpacked.mjs +147 -0
- package/dist/storage.d.mts +9 -2
- package/dist/storage.mjs +93 -12
- package/dist/types.d.mts +16 -1
- package/dist/types.mjs +12 -1
- package/dist/unpack.mjs +2 -2
- package/package.json +7 -6
package/dist/flatpackUtil.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { ArrayRefElement, BigIntRefElement, DateRefElement, MapRefElement, NumberRefElement, ObjectRefElement, ObjectWrapperRefElement, PrimitiveRefElement, RegExpRefElement, SetRefElement, StringConcatRefElement, StringPrimitiveRefElement, SubStringRefElement, } from './RefElements.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { ElementType, supportedHeaders, } from './types.mjs';
|
|
4
4
|
export function fromElement(elem, resolve) {
|
|
5
5
|
function handleArrayElement(element) {
|
|
6
6
|
switch (element[0]) {
|
|
@@ -90,6 +90,6 @@ export class FlatpackedWrapper {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
export function isFlatpacked(value) {
|
|
93
|
-
return Array.isArray(value) && value[0] ===
|
|
93
|
+
return Array.isArray(value) && typeof value[0] === 'string' && supportedHeaders.has(value[0]);
|
|
94
94
|
}
|
|
95
95
|
//# sourceMappingURL=flatpackUtil.mjs.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { ElementType, supportedHeaders } from './types.mjs';
|
|
3
|
+
export function optimizeFlatpacked(data) {
|
|
4
|
+
const [header] = data;
|
|
5
|
+
if (!supportedHeaders.has(header)) {
|
|
6
|
+
throw new Error('Invalid header');
|
|
7
|
+
}
|
|
8
|
+
const elementRefs = data.map((element, index) => ({ origIndex: index, refCount: 0, index: 0, element }));
|
|
9
|
+
const indexToRefElement = new Map(elementRefs.entries());
|
|
10
|
+
for (const refElement of elementRefs) {
|
|
11
|
+
if (refElement.origIndex === 0) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const indexes = getRefIndexes(refElement.element);
|
|
15
|
+
for (const index of indexes) {
|
|
16
|
+
const ref = indexToRefElement.get(index);
|
|
17
|
+
assert(ref, `Invalid reference index: ${index}`);
|
|
18
|
+
ref.refCount++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const sortedRefElements = elementRefs.slice(2).sort((a, b) => b.refCount - a.refCount || a.origIndex - b.origIndex);
|
|
22
|
+
sortedRefElements.forEach((refElement, index) => {
|
|
23
|
+
refElement.index = index + 2;
|
|
24
|
+
});
|
|
25
|
+
const indexMap = new Map([
|
|
26
|
+
[0, 0],
|
|
27
|
+
[1, 1],
|
|
28
|
+
]);
|
|
29
|
+
sortedRefElements.forEach((refElement) => {
|
|
30
|
+
indexMap.set(refElement.origIndex, refElement.index);
|
|
31
|
+
});
|
|
32
|
+
const optimizedElements = [
|
|
33
|
+
data[1],
|
|
34
|
+
...sortedRefElements.map((refElement) => refElement.element),
|
|
35
|
+
].map((element) => patchIndexes(element, indexMap));
|
|
36
|
+
return [header, ...optimizedElements];
|
|
37
|
+
}
|
|
38
|
+
function patchIndexes(elem, indexMap) {
|
|
39
|
+
function mapIndex(index) {
|
|
40
|
+
const v = indexMap.get(index);
|
|
41
|
+
assert(v !== undefined, `Invalid index: ${index}`);
|
|
42
|
+
return v;
|
|
43
|
+
}
|
|
44
|
+
function mapIndexes(indexes) {
|
|
45
|
+
return indexes.map(mapIndex);
|
|
46
|
+
}
|
|
47
|
+
function handleArrayElement(element) {
|
|
48
|
+
switch (element[0]) {
|
|
49
|
+
case ElementType.Array: {
|
|
50
|
+
return [element[0], ...mapIndexes(element.slice(1))];
|
|
51
|
+
}
|
|
52
|
+
case ElementType.Object: {
|
|
53
|
+
return [element[0], mapIndex(element[1]), mapIndex(element[2])];
|
|
54
|
+
}
|
|
55
|
+
case ElementType.String: {
|
|
56
|
+
return [element[0], ...mapIndexes(element.slice(1))];
|
|
57
|
+
}
|
|
58
|
+
case ElementType.SubString: {
|
|
59
|
+
return element[3] !== undefined
|
|
60
|
+
? [element[0], mapIndex(element[1]), element[2], element[3]]
|
|
61
|
+
: [element[0], mapIndex(element[1]), element[2]];
|
|
62
|
+
}
|
|
63
|
+
case ElementType.Set: {
|
|
64
|
+
return [element[0], mapIndex(element[1])];
|
|
65
|
+
}
|
|
66
|
+
case ElementType.Map: {
|
|
67
|
+
return [element[0], mapIndex(element[1]), mapIndex(element[2])];
|
|
68
|
+
}
|
|
69
|
+
case ElementType.RegExp: {
|
|
70
|
+
return [element[0], mapIndex(element[1]), mapIndex(element[2])];
|
|
71
|
+
}
|
|
72
|
+
case ElementType.Date: {
|
|
73
|
+
return element;
|
|
74
|
+
}
|
|
75
|
+
case ElementType.BigInt: {
|
|
76
|
+
return [element[0], mapIndex(element[1])];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
assert(false, 'Invalid element type');
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(elem)) {
|
|
82
|
+
return handleArrayElement(elem);
|
|
83
|
+
}
|
|
84
|
+
if (typeof elem === 'string') {
|
|
85
|
+
return elem;
|
|
86
|
+
}
|
|
87
|
+
if (typeof elem === 'number') {
|
|
88
|
+
return elem;
|
|
89
|
+
}
|
|
90
|
+
if (typeof elem === 'object') {
|
|
91
|
+
return elem;
|
|
92
|
+
}
|
|
93
|
+
assert(typeof elem === 'boolean');
|
|
94
|
+
return elem;
|
|
95
|
+
}
|
|
96
|
+
function getRefIndexes(elem) {
|
|
97
|
+
function handleArrayElement(element) {
|
|
98
|
+
switch (element[0]) {
|
|
99
|
+
case ElementType.Array: {
|
|
100
|
+
return element.slice(1);
|
|
101
|
+
}
|
|
102
|
+
case ElementType.Object: {
|
|
103
|
+
return element.slice(1).filter((v) => !!v);
|
|
104
|
+
}
|
|
105
|
+
case ElementType.String: {
|
|
106
|
+
return element.slice(1);
|
|
107
|
+
}
|
|
108
|
+
case ElementType.SubString: {
|
|
109
|
+
return [element[1]];
|
|
110
|
+
}
|
|
111
|
+
case ElementType.Set: {
|
|
112
|
+
return element.slice(1);
|
|
113
|
+
}
|
|
114
|
+
case ElementType.Map: {
|
|
115
|
+
return element.slice(1);
|
|
116
|
+
}
|
|
117
|
+
case ElementType.RegExp: {
|
|
118
|
+
return element.slice(1);
|
|
119
|
+
}
|
|
120
|
+
case ElementType.Date: {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
case ElementType.BigInt: {
|
|
124
|
+
return [element[1]];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
assert(false, 'Invalid element type');
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(elem)) {
|
|
130
|
+
return handleArrayElement(elem);
|
|
131
|
+
}
|
|
132
|
+
if (typeof elem === 'string') {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
if (typeof elem === 'number') {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
if (typeof elem === 'object') {
|
|
139
|
+
if (elem === null) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
assert(typeof elem === 'boolean');
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=optimizeFlatpacked.mjs.map
|
package/dist/storage.d.mts
CHANGED
|
@@ -19,17 +19,24 @@ export declare class CompactStorage {
|
|
|
19
19
|
*/
|
|
20
20
|
private cachedArrays;
|
|
21
21
|
/**
|
|
22
|
-
* Cache of strings
|
|
22
|
+
* Cache of strings used for prefix matching.
|
|
23
23
|
*/
|
|
24
24
|
private knownStrings;
|
|
25
|
+
/**
|
|
26
|
+
* Cache of reversed strings used for suffix matching.
|
|
27
|
+
*/
|
|
28
|
+
private knownStringsRev;
|
|
25
29
|
private cachedElements;
|
|
26
30
|
constructor(options?: FlatpackOptions | undefined);
|
|
27
31
|
private primitiveToIdx;
|
|
28
32
|
private addSubStringRef;
|
|
33
|
+
private estimateSubStringCost;
|
|
29
34
|
private addKnownString;
|
|
30
35
|
private addStringPrimitive;
|
|
31
36
|
private duplicateIndex;
|
|
32
37
|
private stringToIdx;
|
|
38
|
+
private findPrefix;
|
|
39
|
+
private findSuffix;
|
|
33
40
|
private objSetToIdx;
|
|
34
41
|
private createUniqueKeys;
|
|
35
42
|
private objMapToIdx;
|
|
@@ -56,5 +63,5 @@ export declare class CompactStorage {
|
|
|
56
63
|
toJSON<V extends Serializable>(json: V): Flatpacked;
|
|
57
64
|
}
|
|
58
65
|
export declare function toJSON<V extends Serializable>(json: V, options?: FlatpackOptions): Flatpacked;
|
|
59
|
-
export declare function stringify(data: Unpacked, pretty?: boolean): string;
|
|
66
|
+
export declare function stringify(data: Unpacked, pretty?: boolean, options?: FlatpackOptions): string;
|
|
60
67
|
//# sourceMappingURL=storage.d.mts.map
|
package/dist/storage.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
+
import { optimizeFlatpacked } from './optimizeFlatpacked.mjs';
|
|
2
3
|
import { stringifyFlatpacked } from './stringify.mjs';
|
|
3
4
|
import { Trie } from './Trie.mjs';
|
|
4
5
|
import { blockSplitRegex, dataHeader, ElementType } from './types.mjs';
|
|
@@ -32,9 +33,13 @@ export class CompactStorage {
|
|
|
32
33
|
*/
|
|
33
34
|
cachedArrays = new Map();
|
|
34
35
|
/**
|
|
35
|
-
* Cache of strings
|
|
36
|
+
* Cache of strings used for prefix matching.
|
|
36
37
|
*/
|
|
37
38
|
knownStrings = new Trie();
|
|
39
|
+
/**
|
|
40
|
+
* Cache of reversed strings used for suffix matching.
|
|
41
|
+
*/
|
|
42
|
+
knownStringsRev = new Trie();
|
|
38
43
|
cachedElements = new Map();
|
|
39
44
|
constructor(options) {
|
|
40
45
|
this.options = options;
|
|
@@ -66,9 +71,23 @@ export class CompactStorage {
|
|
|
66
71
|
this.cache.set(value, idx);
|
|
67
72
|
return idx;
|
|
68
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
|
+
}
|
|
69
86
|
addKnownString(idx, value) {
|
|
70
87
|
if (value.length >= minSubStringLen) {
|
|
71
|
-
|
|
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);
|
|
72
91
|
}
|
|
73
92
|
}
|
|
74
93
|
addStringPrimitive(value) {
|
|
@@ -90,20 +109,63 @@ export class CompactStorage {
|
|
|
90
109
|
if (forceStringPrimitives || value.length < minSubStringLen || blockSplitRegex.test(value)) {
|
|
91
110
|
return this.addStringPrimitive(value);
|
|
92
111
|
}
|
|
93
|
-
const
|
|
94
|
-
|
|
112
|
+
const foundPrefix = this.findPrefix(value);
|
|
113
|
+
const foundSuffix = this.findSuffix(value);
|
|
114
|
+
if (!foundPrefix && !foundSuffix) {
|
|
95
115
|
return this.addStringPrimitive(value);
|
|
96
116
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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];
|
|
102
136
|
const idx = this.data.push([ElementType.String, ...v]) - 1;
|
|
103
137
|
this.cache.set(value, idx);
|
|
104
138
|
this.addKnownString(idx, value);
|
|
105
139
|
return idx;
|
|
106
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
|
+
}
|
|
107
169
|
objSetToIdx(value) {
|
|
108
170
|
const found = this.cache.get(value);
|
|
109
171
|
if (found !== undefined) {
|
|
@@ -328,7 +390,25 @@ export class CompactStorage {
|
|
|
328
390
|
toJSON(json) {
|
|
329
391
|
this.softReset();
|
|
330
392
|
this.valueToIdx(json);
|
|
331
|
-
return this.data;
|
|
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;
|
|
332
412
|
}
|
|
333
413
|
}
|
|
334
414
|
function isEqual(a, b) {
|
|
@@ -358,7 +438,8 @@ function isObjectWrapper(value) {
|
|
|
358
438
|
export function toJSON(json, options) {
|
|
359
439
|
return new CompactStorage(options).toJSON(json);
|
|
360
440
|
}
|
|
361
|
-
export function stringify(data, pretty = true) {
|
|
362
|
-
|
|
441
|
+
export function stringify(data, pretty = true, options) {
|
|
442
|
+
const json = toJSON(data, options);
|
|
443
|
+
return pretty ? stringifyFlatpacked(json) : JSON.stringify(json);
|
|
363
444
|
}
|
|
364
445
|
//# sourceMappingURL=storage.mjs.map
|
package/dist/types.d.mts
CHANGED
|
@@ -93,7 +93,22 @@ export interface FlatpackOptions {
|
|
|
93
93
|
* @default true
|
|
94
94
|
*/
|
|
95
95
|
dedupe?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Try to optimize the size of the output.
|
|
98
|
+
*/
|
|
99
|
+
optimize?: boolean;
|
|
96
100
|
}
|
|
97
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Legacy header for Flatpack JSON.
|
|
103
|
+
*/
|
|
104
|
+
export declare const dataHeaderV0_1: "Dehydrated JSON v1";
|
|
105
|
+
/**
|
|
106
|
+
* The current header for Flatpack JSON.
|
|
107
|
+
*/
|
|
108
|
+
export declare const dataHeader: "Flatpack JSON v1";
|
|
109
|
+
/**
|
|
110
|
+
* The set of supported headers for Flatpack JSON.
|
|
111
|
+
*/
|
|
112
|
+
export declare const supportedHeaders: Set<string>;
|
|
98
113
|
export {};
|
|
99
114
|
//# sourceMappingURL=types.d.mts.map
|
package/dist/types.mjs
CHANGED
|
@@ -11,5 +11,16 @@ export var ElementType;
|
|
|
11
11
|
ElementType[ElementType["BigInt"] = 8] = "BigInt";
|
|
12
12
|
})(ElementType || (ElementType = {}));
|
|
13
13
|
export const blockSplitRegex = /^sha\d/;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Legacy header for Flatpack JSON.
|
|
16
|
+
*/
|
|
17
|
+
export const dataHeaderV0_1 = 'Dehydrated JSON v1';
|
|
18
|
+
/**
|
|
19
|
+
* The current header for Flatpack JSON.
|
|
20
|
+
*/
|
|
21
|
+
export const dataHeader = 'Flatpack JSON v1';
|
|
22
|
+
/**
|
|
23
|
+
* The set of supported headers for Flatpack JSON.
|
|
24
|
+
*/
|
|
25
|
+
export const supportedHeaders = new Set([dataHeaderV0_1, dataHeader]);
|
|
15
26
|
//# sourceMappingURL=types.mjs.map
|
package/dist/unpack.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
-
import {
|
|
2
|
+
import { ElementType, supportedHeaders, } from './types.mjs';
|
|
3
3
|
export function fromJSON(data) {
|
|
4
4
|
const [header] = data;
|
|
5
|
-
if (header
|
|
5
|
+
if (!supportedHeaders.has(header)) {
|
|
6
6
|
throw new Error('Invalid header');
|
|
7
7
|
}
|
|
8
8
|
const cache = new Map([[0, undefined]]);
|
package/package.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"provenance": true
|
|
6
6
|
},
|
|
7
|
-
"version": "
|
|
8
|
-
"description": "A library to normalize JSON objects to reduce the size.",
|
|
7
|
+
"version": "9.0.0",
|
|
8
|
+
"description": "A library to normalize / flatten JSON objects to reduce the size.",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"cspell",
|
|
11
11
|
"json",
|
|
@@ -50,11 +50,12 @@
|
|
|
50
50
|
"url": "https://github.com/streetsidesoftware/cspell/labels/filetype"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
|
-
"node": ">=
|
|
53
|
+
"node": ">=20"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@cspell/filetypes": "
|
|
57
|
-
"diff": "^7.0.0"
|
|
56
|
+
"@cspell/filetypes": "9.0.0",
|
|
57
|
+
"diff": "^7.0.0",
|
|
58
|
+
"flatted": "^3.3.3"
|
|
58
59
|
},
|
|
59
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "f7c4be398734894d817d9b60214731a516cff7d2"
|
|
60
61
|
}
|