@atlaspack/build-cache 2.13.3-canary.56 → 2.13.3-canary.560
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/CHANGELOG.md +99 -0
- package/dist/LargeMap.js +59 -0
- package/dist/buildCache.js +15 -0
- package/dist/index.js +19 -0
- package/dist/serializer.js +222 -0
- package/dist/serializerCore.js +9 -0
- package/lib/LargeMap.js +63 -0
- package/lib/serializer.js +16 -2
- package/lib/serializerCore.js +1 -1
- package/lib/types/LargeMap.d.ts +19 -0
- package/lib/types/buildCache.d.ts +2 -0
- package/{src/index.js → lib/types/index.d.ts} +0 -2
- package/lib/types/serializer.d.ts +11 -0
- package/lib/types/serializerCore.d.ts +2 -0
- package/package.json +12 -5
- package/src/LargeMap.ts +67 -0
- package/src/{buildCache.js → buildCache.ts} +0 -2
- package/src/index.ts +3 -0
- package/src/{serializer.js → serializer.ts} +27 -9
- package/src/serializerCore.ts +4 -0
- package/test/LargeMap.test.ts +151 -0
- package/test/{serializer.test.js → serializer.test.ts} +6 -8
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/src/serializerCore.js +0 -5
package/src/LargeMap.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Map implementation that can exceed Node 24's Map size limit.
|
|
3
|
+
*
|
|
4
|
+
* LargeMap works around the Maximum maps size limit by using multiple internal Maps,
|
|
5
|
+
* creating a new one when the current Map reaches the size limit.
|
|
6
|
+
*
|
|
7
|
+
* This is a minimal implementation supporting only has/get/set - intended as a
|
|
8
|
+
* temporary solution until we no longer need large JS serialization
|
|
9
|
+
*/
|
|
10
|
+
export class LargeMap<K, V> {
|
|
11
|
+
maps: Map<K, V>[];
|
|
12
|
+
maxSize: number;
|
|
13
|
+
singleMap: boolean;
|
|
14
|
+
lastMap: Map<K, V>;
|
|
15
|
+
|
|
16
|
+
constructor(maxSize: number = Math.pow(2, 23)) {
|
|
17
|
+
this.lastMap = new Map();
|
|
18
|
+
this.maps = [this.lastMap];
|
|
19
|
+
this.singleMap = true;
|
|
20
|
+
this.maxSize = maxSize;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(key: K, value: V): this {
|
|
24
|
+
// Update existing key if found
|
|
25
|
+
if (!this.singleMap) {
|
|
26
|
+
for (let map of this.maps) {
|
|
27
|
+
if (map.has(key)) {
|
|
28
|
+
map.set(key, value);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Otherwise, add to last map
|
|
35
|
+
if (this.lastMap.size >= this.maxSize) {
|
|
36
|
+
this.lastMap = new Map();
|
|
37
|
+
this.maps.push(this.lastMap);
|
|
38
|
+
this.singleMap = false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.lastMap.set(key, value);
|
|
42
|
+
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get(key: K): V | undefined {
|
|
47
|
+
if (this.singleMap) {
|
|
48
|
+
return this.lastMap.get(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (let map of this.maps) {
|
|
52
|
+
if (map.has(key)) {
|
|
53
|
+
return map.get(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
has(key: K): boolean {
|
|
60
|
+
for (let map of this.maps) {
|
|
61
|
+
if (map.has(key)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/index.ts
ADDED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import {getFeatureFlag} from '@atlaspack/feature-flags';
|
|
2
|
+
|
|
2
3
|
import {createBuildCache} from './buildCache';
|
|
4
|
+
import {LargeMap} from './LargeMap';
|
|
3
5
|
import {serializeRaw, deserializeRaw} from './serializerCore';
|
|
4
6
|
|
|
5
7
|
export {serializeRaw, deserializeRaw} from './serializerCore';
|
|
6
8
|
|
|
9
|
+
// flow-to-ts helpers
|
|
10
|
+
export type Class<T> = new (...args: any[]) => T;
|
|
11
|
+
// /flow-to-ts helpers
|
|
12
|
+
|
|
7
13
|
const nameToCtor: Map<string, Class<any>> = new Map();
|
|
8
14
|
const ctorToName: Map<Class<any>, string> = new Map();
|
|
9
15
|
|
|
@@ -49,7 +55,7 @@ function shallowCopy(object: any) {
|
|
|
49
55
|
return object;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
function isBuffer(object) {
|
|
58
|
+
function isBuffer(object: any) {
|
|
53
59
|
return (
|
|
54
60
|
object.buffer instanceof ArrayBuffer ||
|
|
55
61
|
(typeof SharedArrayBuffer !== 'undefined' &&
|
|
@@ -57,13 +63,25 @@ function isBuffer(object) {
|
|
|
57
63
|
);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
function shouldContinueMapping(value) {
|
|
66
|
+
function shouldContinueMapping(value: any) {
|
|
61
67
|
return value && typeof value === 'object' && value.$$raw !== true;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
interface MapLike<K, V> {
|
|
71
|
+
get(key: K): V | undefined;
|
|
72
|
+
set(key: K, value: V): unknown;
|
|
73
|
+
has(key: K): boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function mapObject(object: any, fn: (val?: any) => any, preOrder = false): any {
|
|
77
|
+
// Use LargeMap to work around Node 24's Map size limit
|
|
78
|
+
// when the feature flag is enabled
|
|
79
|
+
let cache: MapLike<any, any> = getFeatureFlag('useLargeMapInBuildCache')
|
|
80
|
+
? new LargeMap<any, any>()
|
|
81
|
+
: new Map<any, any>();
|
|
82
|
+
let memo: MapLike<any, any> = getFeatureFlag('useLargeMapInBuildCache')
|
|
83
|
+
? new LargeMap<any, any>()
|
|
84
|
+
: new Map<any, any>();
|
|
67
85
|
|
|
68
86
|
// Memoize the passed function to ensure it always returns the exact same
|
|
69
87
|
// output by reference for the same input. This is important to maintain
|
|
@@ -211,8 +229,8 @@ export function restoreDeserializedObject(object: any): any {
|
|
|
211
229
|
);
|
|
212
230
|
}
|
|
213
231
|
|
|
214
|
-
if (typeof ctor.deserialize === 'function') {
|
|
215
|
-
return ctor.deserialize(value.value);
|
|
232
|
+
if (typeof (ctor as any).deserialize === 'function') {
|
|
233
|
+
return (ctor as any).deserialize(value.value);
|
|
216
234
|
}
|
|
217
235
|
|
|
218
236
|
value = value.value;
|
|
@@ -223,7 +241,7 @@ export function restoreDeserializedObject(object: any): any {
|
|
|
223
241
|
});
|
|
224
242
|
}
|
|
225
243
|
|
|
226
|
-
const serializeCache = createBuildCache();
|
|
244
|
+
const serializeCache = createBuildCache<any, Buffer>();
|
|
227
245
|
|
|
228
246
|
export function serialize(object: any): Buffer {
|
|
229
247
|
let cached = serializeCache.get(object);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import {LargeMap} from '../src/LargeMap';
|
|
3
|
+
|
|
4
|
+
describe('LargeMap', () => {
|
|
5
|
+
describe('basic operations', () => {
|
|
6
|
+
it('should set and get values', () => {
|
|
7
|
+
const map = new LargeMap<string, number>();
|
|
8
|
+
map.set('a', 1);
|
|
9
|
+
map.set('b', 2);
|
|
10
|
+
|
|
11
|
+
assert.equal(map.get('a'), 1);
|
|
12
|
+
assert.equal(map.get('b'), 2);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should return undefined for missing keys', () => {
|
|
16
|
+
const map = new LargeMap<string, number>();
|
|
17
|
+
assert.equal(map.get('missing'), undefined);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should check if key exists with has()', () => {
|
|
21
|
+
const map = new LargeMap<string, number>();
|
|
22
|
+
map.set('a', 1);
|
|
23
|
+
|
|
24
|
+
assert.equal(map.has('a'), true);
|
|
25
|
+
assert.equal(map.has('b'), false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should update existing key in single map', () => {
|
|
29
|
+
const map = new LargeMap<string, number>();
|
|
30
|
+
map.set('a', 1);
|
|
31
|
+
map.set('a', 2);
|
|
32
|
+
|
|
33
|
+
assert.equal(map.get('a'), 2);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return this from set() for chaining', () => {
|
|
37
|
+
const map = new LargeMap<string, number>();
|
|
38
|
+
const result = map.set('a', 1).set('b', 2).set('c', 3);
|
|
39
|
+
|
|
40
|
+
assert.equal(result, map);
|
|
41
|
+
assert.equal(map.get('a'), 1);
|
|
42
|
+
assert.equal(map.get('b'), 2);
|
|
43
|
+
assert.equal(map.get('c'), 3);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('multiple internal maps', () => {
|
|
48
|
+
it('should create new internal map when maxSize is reached', () => {
|
|
49
|
+
const map = new LargeMap<number, number>(3); // Small maxSize for testing
|
|
50
|
+
|
|
51
|
+
map.set(1, 1);
|
|
52
|
+
map.set(2, 2);
|
|
53
|
+
map.set(3, 3);
|
|
54
|
+
assert.equal(map.maps.length, 1);
|
|
55
|
+
|
|
56
|
+
map.set(4, 4); // Should trigger new internal map
|
|
57
|
+
assert.equal(map.maps.length, 2);
|
|
58
|
+
|
|
59
|
+
map.set(5, 5);
|
|
60
|
+
map.set(6, 6);
|
|
61
|
+
assert.equal(map.maps.length, 2);
|
|
62
|
+
|
|
63
|
+
map.set(7, 7); // Should trigger another new internal map
|
|
64
|
+
assert.equal(map.maps.length, 3);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should find values across multiple internal maps', () => {
|
|
68
|
+
const map = new LargeMap<number, string>(2);
|
|
69
|
+
|
|
70
|
+
map.set(1, 'one');
|
|
71
|
+
map.set(2, 'two');
|
|
72
|
+
map.set(3, 'three');
|
|
73
|
+
map.set(4, 'four');
|
|
74
|
+
map.set(5, 'five');
|
|
75
|
+
|
|
76
|
+
assert.equal(map.get(1), 'one');
|
|
77
|
+
assert.equal(map.get(2), 'two');
|
|
78
|
+
assert.equal(map.get(3), 'three');
|
|
79
|
+
assert.equal(map.get(4), 'four');
|
|
80
|
+
assert.equal(map.get(5), 'five');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should check has() across multiple internal maps', () => {
|
|
84
|
+
const map = new LargeMap<number, string>(2);
|
|
85
|
+
|
|
86
|
+
map.set(1, 'one');
|
|
87
|
+
map.set(2, 'two');
|
|
88
|
+
map.set(3, 'three');
|
|
89
|
+
|
|
90
|
+
assert.equal(map.has(1), true);
|
|
91
|
+
assert.equal(map.has(2), true);
|
|
92
|
+
assert.equal(map.has(3), true);
|
|
93
|
+
assert.equal(map.has(4), false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should update existing key in earlier internal map', () => {
|
|
97
|
+
const map = new LargeMap<number, string>(2);
|
|
98
|
+
|
|
99
|
+
map.set(1, 'one');
|
|
100
|
+
map.set(2, 'two');
|
|
101
|
+
map.set(3, 'three'); // Goes to second map
|
|
102
|
+
|
|
103
|
+
// Update key in first map
|
|
104
|
+
map.set(1, 'ONE');
|
|
105
|
+
|
|
106
|
+
assert.equal(map.get(1), 'ONE');
|
|
107
|
+
assert.equal(map.maps.length, 2); // Should not create new map
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('type handling', () => {
|
|
112
|
+
it('should work with object keys', () => {
|
|
113
|
+
const map = new LargeMap<object, string>();
|
|
114
|
+
const key1 = {id: 1};
|
|
115
|
+
const key2 = {id: 2};
|
|
116
|
+
|
|
117
|
+
map.set(key1, 'first');
|
|
118
|
+
map.set(key2, 'second');
|
|
119
|
+
|
|
120
|
+
assert.equal(map.get(key1), 'first');
|
|
121
|
+
assert.equal(map.get(key2), 'second');
|
|
122
|
+
assert.equal(map.get({id: 1}), undefined); // Different reference
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should work with null and undefined values', () => {
|
|
126
|
+
const map = new LargeMap<string, any>();
|
|
127
|
+
|
|
128
|
+
map.set('null', null);
|
|
129
|
+
map.set('undefined', undefined);
|
|
130
|
+
|
|
131
|
+
assert.equal(map.get('null'), null);
|
|
132
|
+
assert.equal(map.get('undefined'), undefined);
|
|
133
|
+
assert.equal(map.has('null'), true);
|
|
134
|
+
assert.equal(map.has('undefined'), true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should work with various key types', () => {
|
|
138
|
+
const map = new LargeMap<any, string>();
|
|
139
|
+
|
|
140
|
+
map.set(1, 'number');
|
|
141
|
+
map.set('str', 'string');
|
|
142
|
+
map.set(true, 'boolean');
|
|
143
|
+
map.set(null, 'null');
|
|
144
|
+
|
|
145
|
+
assert.equal(map.get(1), 'number');
|
|
146
|
+
assert.equal(map.get('str'), 'string');
|
|
147
|
+
assert.equal(map.get(true), 'boolean');
|
|
148
|
+
assert.equal(map.get(null), 'null');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
|
|
3
1
|
import assert from 'assert';
|
|
4
2
|
import sinon from 'sinon';
|
|
5
3
|
|
|
@@ -151,8 +149,8 @@ describe('serializer', () => {
|
|
|
151
149
|
|
|
152
150
|
it('should serialize a cyclic class', () => {
|
|
153
151
|
class Foo {
|
|
154
|
-
x:
|
|
155
|
-
constructor(x
|
|
152
|
+
x: Foo | null | undefined;
|
|
153
|
+
constructor(x?: Foo | null) {
|
|
156
154
|
this.x = x;
|
|
157
155
|
}
|
|
158
156
|
}
|
|
@@ -193,8 +191,8 @@ describe('serializer', () => {
|
|
|
193
191
|
|
|
194
192
|
it('should serialize a cyclic class and copy on write', () => {
|
|
195
193
|
class Foo {
|
|
196
|
-
x:
|
|
197
|
-
constructor(x
|
|
194
|
+
x: Foo | null | undefined;
|
|
195
|
+
constructor(x?: Foo | null) {
|
|
198
196
|
this.x = x;
|
|
199
197
|
}
|
|
200
198
|
}
|
|
@@ -257,7 +255,7 @@ describe('serializer', () => {
|
|
|
257
255
|
class Outer {
|
|
258
256
|
inner: Inner;
|
|
259
257
|
|
|
260
|
-
constructor(inner) {
|
|
258
|
+
constructor(inner: any) {
|
|
261
259
|
this.inner = inner;
|
|
262
260
|
}
|
|
263
261
|
|
|
@@ -272,7 +270,7 @@ describe('serializer', () => {
|
|
|
272
270
|
class Inner {
|
|
273
271
|
x: number;
|
|
274
272
|
|
|
275
|
-
constructor(x) {
|
|
273
|
+
constructor(x: any) {
|
|
276
274
|
this.x = x;
|
|
277
275
|
}
|
|
278
276
|
|