Skip to content

Commit 1bd64e9

Browse files
authored
refactor: works towards dynamic flatpack (#6264)
1 parent 5a09a97 commit 1bd64e9

File tree

5 files changed

+555
-10
lines changed

5 files changed

+555
-10
lines changed

packages/flatpack-json/src/Flatpack.mts

+76-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import assert from 'node:assert';
2+
13
import { FlatpackedWrapper } from './flatpackUtil.mjs';
4+
import { proxyDate, proxyObject, proxySet } from './proxy.mjs';
25
import {
36
ArrayRefElement,
47
BigIntRefElement,
@@ -8,6 +11,7 @@ import {
811
ObjectRefElement,
912
ObjectWrapperRefElement,
1013
PrimitiveRefElement,
14+
PrimitiveRefElementBase,
1115
RefElements,
1216
RegExpRefElement,
1317
SetRefElement,
@@ -76,6 +80,8 @@ export class FlatpackStore {
7680
private cachedSets = new Map<ArrayRefElement | undefined, SetRefElement>();
7781
private cachedMaps = new Map<ArrayRefElement | undefined, Map<ArrayRefElement | undefined, MapRefElement>>();
7882

83+
private cachedProxies = new WeakMap<RefElements, Serializable>();
84+
7985
/**
8086
* Cache of strings that have been deduped and stored in the data array.
8187
*/
@@ -275,7 +281,7 @@ export class FlatpackStore {
275281

276282
private dedupeSetRefs(value: Set<Serializable>, element: SetRefElement): SetRefElement {
277283
if (!this.dedupe) return element;
278-
const values = element.values();
284+
const values = element.valueRefs();
279285
const found = this.cachedSets.get(values);
280286
if (!found) {
281287
this.cachedSets.set(values, element);
@@ -287,6 +293,10 @@ export class FlatpackStore {
287293
return found;
288294
}
289295

296+
private proxySetRef(ref: SetRefElement): Set<Serializable> {
297+
return proxySet(new Set(this.#toValue(ref.valueRefs()) as Serializable[]), () => {});
298+
}
299+
290300
private createUniqueKeys(keys: Serializable[]): ArrayRefElement {
291301
const cacheValue = false;
292302
let k = this.arrToRef(keys, cacheValue);
@@ -320,8 +330,8 @@ export class FlatpackStore {
320330

321331
private dedupeMapRefs(value: Map<Serializable, Serializable>, element: MapRefElement): MapRefElement {
322332
if (!this.dedupe) return element;
323-
const keys = element.keys();
324-
const values = element.values();
333+
const keys = element.keyRefs();
334+
const values = element.valueRefs();
325335
let found = this.cachedMaps.get(keys);
326336
if (!found) {
327337
found = new Map();
@@ -340,6 +350,10 @@ export class FlatpackStore {
340350
return element;
341351
}
342352

353+
private proxyMapRef(_ref: MapRefElement): Map<Serializable, Serializable> {
354+
return new Map();
355+
}
356+
343357
private cvtRegExpToRef(value: RegExp): RegExpRefElement {
344358
const found = this.cache.get(value);
345359
if (found !== undefined) {
@@ -361,6 +375,10 @@ export class FlatpackStore {
361375
return this.addValueAndElement(value, new DateRefElement(value.getTime()));
362376
}
363377

378+
private proxyDateRef(ref: DateRefElement): Date {
379+
return proxyDate(ref.value, (date) => ref.setTime(date.getTime()));
380+
}
381+
364382
private cvtBigintToRef(value: bigint): BigIntRefElement {
365383
const found = this.cache.get(value);
366384
if (found !== undefined) {
@@ -415,8 +433,8 @@ export class FlatpackStore {
415433

416434
private dedupeObject(value: PrimitiveObject | ObjectWrapper, element: ObjectRefElement): ObjectRefElement {
417435
if (!this.dedupe) return element;
418-
const keys = element.keys();
419-
const values = element.values();
436+
const keys = element.keyRefs();
437+
const values = element.valueRefs();
420438
let found = this.cachedObjects.get(keys);
421439
if (!found) {
422440
found = new Map();
@@ -435,6 +453,18 @@ export class FlatpackStore {
435453
return element;
436454
}
437455

456+
private proxyObjectRef(ref: ObjectRefElement): PrimitiveObject {
457+
const keys = this.#toValue(ref.keyRefs()) as string[] | undefined;
458+
const values = this.#toValue(ref.valueRefs()) as Serializable[] | undefined;
459+
const obj = keys && values ? Object.fromEntries(keys.map((key, i) => [key, values[i]])) : {};
460+
return proxyObject(obj, (_value) => {});
461+
}
462+
463+
private proxyObjectWrapperRef(ref: ObjectWrapperRefElement): PrimitiveObject {
464+
const value = Object(this.#toValue(ref.valueRef())) as PrimitiveObject;
465+
return proxyObject(value, (_value) => {});
466+
}
467+
438468
/**
439469
*
440470
* @param value - The array converted to an ArrayRefElement.
@@ -482,6 +512,11 @@ export class FlatpackStore {
482512
return this.dedupeArray(value, element, cacheValue);
483513
}
484514

515+
private proxyArrayRef(ref: ArrayRefElement): PrimitiveArray {
516+
const arr = ref.valueRefs().map((v) => this.#toValue(v));
517+
return proxyObject(arr, (_value) => {});
518+
}
519+
485520
private valueToRef(value: Serializable): RefElements {
486521
if (value === null) {
487522
return this.primitiveToRef(value);
@@ -619,6 +654,26 @@ export class FlatpackStore {
619654
}
620655
}
621656

657+
#resolveToValueProxy(ref: RefElements | undefined): Unpacked {
658+
if (!ref) return undefined;
659+
if (ref instanceof ArrayRefElement) return this.proxyArrayRef(ref);
660+
if (ref instanceof ObjectRefElement) return this.proxyObjectRef(ref);
661+
if (ref instanceof PrimitiveRefElementBase) return ref.value;
662+
if (isStringRefElements(ref)) return ref.value;
663+
if (ref instanceof MapRefElement) return this.proxyMapRef(ref);
664+
if (ref instanceof SetRefElement) return this.proxySetRef(ref);
665+
if (ref instanceof BigIntRefElement) return ref.value;
666+
if (ref instanceof RegExpRefElement) return ref.value;
667+
if (ref instanceof DateRefElement) return this.proxyDateRef(ref);
668+
if (ref instanceof ObjectWrapperRefElement) return this.proxyObjectWrapperRef(ref);
669+
assert(false, 'Unknown ref type');
670+
}
671+
672+
#toValue(ref: RefElements | undefined): Unpacked {
673+
if (!ref) return undefined;
674+
return getOrResolve(this.cachedProxies, ref, (ref) => this.#resolveToValueProxy(ref));
675+
}
676+
622677
toJSON(): Flatpacked {
623678
const data = [dataHeader] as Flatpacked;
624679
const idxLookup = this.assignedElements;
@@ -654,6 +709,10 @@ export class FlatpackStore {
654709
toValue(): Unpacked {
655710
return fromJSON(this.toJSON());
656711
}
712+
713+
_toValueProxy(): Unpacked {
714+
return this.#toValue(this.root);
715+
}
657716
}
658717

659718
type TrieData = StringRefElements;
@@ -686,3 +745,15 @@ export function toJSON<V extends Serializable>(json: V, options?: FlatpackOption
686745
export function stringify(data: Unpacked, pretty = true): string {
687746
return pretty ? stringifyFlatpacked(toJSON(data)) : JSON.stringify(toJSON(data));
688747
}
748+
749+
type WeakOrNever<K, V> = K extends WeakKey ? WeakMap<K, V> : never;
750+
type SupportedMap<K, V> = Map<K, V> | WeakOrNever<K, V>;
751+
752+
function getOrResolve<K, V>(map: SupportedMap<K, V>, key: K, resolver: (key: K) => V): V {
753+
let value = map.get(key);
754+
if (value === undefined && !map.has(key)) {
755+
value = resolver(key);
756+
map.set(key, value);
757+
}
758+
return value as V;
759+
}

packages/flatpack-json/src/Flatpack.test.mts

+49
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { describe, expect, test } from 'vitest';
77
import { FlatpackStore, stringify, toJSON } from './Flatpack.mjs';
88
import { stringifyFlatpacked } from './stringify.mjs';
99
import { fromJSON } from './unpack.mjs';
10+
import { deepEqual } from './proxy.mts';
1011

1112
const urlFileList = new URL('../fixtures/fileList.txt', import.meta.url);
1213
const baseFilename = new URL(import.meta.url).pathname.split('/').slice(-1).join('').split('.').slice(0, -2).join('.');
@@ -189,6 +190,23 @@ describe('Flatpack', async () => {
189190
expect(fromJSON(fp.toJSON())).toEqual(data);
190191
expect(fp.toJSON()).not.toEqual(v);
191192
});
193+
194+
test.each`
195+
data
196+
${undefined}
197+
${'string'}
198+
${1}
199+
${1.1}
200+
${null}
201+
${true}
202+
${false}
203+
${new Date()}
204+
${/[a-z]+/}
205+
`('toValue $data', ({ data }) => {
206+
const fp = new FlatpackStore(data);
207+
expect(fp.toValue()).toEqual(data);
208+
expect(fp._toValueProxy()).toEqual(data);
209+
});
192210
});
193211

194212
async function sampleFileList() {
@@ -249,3 +267,34 @@ function sampleNestedData() {
249267
cValues,
250268
};
251269
}
270+
271+
describe('Flatpack value proxy', () => {
272+
test.each`
273+
value
274+
${undefined}
275+
${'string'}
276+
${1}
277+
${1.1}
278+
${null}
279+
${true}
280+
${false}
281+
${[]}
282+
${[1, 2]}
283+
${['a', 'b', 'a', 'b']}
284+
${{}}
285+
${{ a: 1 }}
286+
${{ a: { b: 1 } }}
287+
${{ a: { a: 'a', b: 42 } }}
288+
${{ a: [1] }}
289+
${new Set(['apple', 'banana', 'pineapple'])}
290+
${new Map([['apple', 1], ['banana', 2], ['pineapple', 3]])}
291+
${/[\p{L}\p{M}]+/gu}
292+
${new Date('2024-01-01')}
293+
`('identity $value', ({ value }) => {
294+
const fp = new FlatpackStore(value);
295+
const proxy = fp._toValueProxy();
296+
expect(deepEqual(proxy, value)).toBe(true);
297+
!(proxy instanceof Map || proxy instanceof Set) && expect(proxy).toEqual(value);
298+
expect(fp._toValueProxy()).toBe(proxy);
299+
});
300+
});

packages/flatpack-json/src/RefElements.mts

+25-5
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ export class ObjectRefElement extends BaseRefElement implements RefElement<Objec
124124
this.#v = values;
125125
}
126126

127-
keys(): ArrayRefElement | undefined {
127+
keyRefs(): ArrayRefElement | undefined {
128128
return this.#k;
129129
}
130130

131-
values(): ArrayRefElement | undefined {
131+
valueRefs(): ArrayRefElement | undefined {
132132
return this.#v;
133133
}
134134

@@ -174,6 +174,10 @@ export class ObjectWrapperRefElement extends BaseRefElement implements RefElemen
174174
this.#v = value;
175175
}
176176

177+
valueRef(): RefElements | undefined {
178+
return this.#v;
179+
}
180+
177181
toElement(lookup: FnIndexLookup): ObjectWrapperElement {
178182
return [ElementType.Object, 0, lookup(this.#v)];
179183
}
@@ -216,7 +220,7 @@ export class SetRefElement extends BaseRefElement implements RefElement<SetEleme
216220
return this.#v ? [this.#v] : undefined;
217221
}
218222

219-
values(): ArrayRefElement | undefined {
223+
valueRefs(): ArrayRefElement | undefined {
220224
return this.#v;
221225
}
222226

@@ -255,11 +259,11 @@ export class MapRefElement extends BaseRefElement implements RefElement<MapEleme
255259
return [this.#k, this.#v].filter((r) => !!r);
256260
}
257261

258-
keys(): ArrayRefElement | undefined {
262+
keyRefs(): ArrayRefElement | undefined {
259263
return this.#k;
260264
}
261265

262-
values(): ArrayRefElement | undefined {
266+
valueRefs(): ArrayRefElement | undefined {
263267
return this.#v;
264268
}
265269

@@ -286,6 +290,10 @@ export class RegExpRefElement extends BaseRefElement implements RefElement<RegEx
286290
return [ElementType.RegExp, lookup(this.#p), lookup(this.#f)];
287291
}
288292

293+
get value(): RegExp {
294+
return new RegExp(this.#p.value, this.#f.value);
295+
}
296+
289297
clone(): RegExpRefElement {
290298
return new RegExpRefElement(this.#p, this.#f);
291299
}
@@ -315,6 +323,14 @@ export class DateRefElement extends BaseRefElement implements RefElement<DateEle
315323
return [ElementType.Date, this.#v];
316324
}
317325

326+
get value(): Date {
327+
return new Date(this.#v);
328+
}
329+
330+
setTime(time: number): void {
331+
this.#v = time;
332+
}
333+
318334
clone(): DateRefElement {
319335
return new DateRefElement(this.#v);
320336
}
@@ -336,6 +352,10 @@ export class BigIntRefElement extends BaseRefElement implements RefElement<BigIn
336352
this.#v = value;
337353
}
338354

355+
get value(): bigint {
356+
return BigInt(this.#v.value);
357+
}
358+
339359
toElement(lookup: FnIndexLookup): BigIntElement {
340360
return [ElementType.BigInt, lookup(this.#v)];
341361
}

0 commit comments

Comments
 (0)