Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: works towards dynamic flatpack #6264

Merged
merged 1 commit into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions packages/flatpack-json/src/Flatpack.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import assert from 'node:assert';

import { FlatpackedWrapper } from './flatpackUtil.mjs';
import { proxyDate, proxyObject, proxySet } from './proxy.mjs';
import {
ArrayRefElement,
BigIntRefElement,
Expand All @@ -8,6 +11,7 @@ import {
ObjectRefElement,
ObjectWrapperRefElement,
PrimitiveRefElement,
PrimitiveRefElementBase,
RefElements,
RegExpRefElement,
SetRefElement,
Expand Down Expand Up @@ -76,6 +80,8 @@ export class FlatpackStore {
private cachedSets = new Map<ArrayRefElement | undefined, SetRefElement>();
private cachedMaps = new Map<ArrayRefElement | undefined, Map<ArrayRefElement | undefined, MapRefElement>>();

private cachedProxies = new WeakMap<RefElements, Serializable>();

/**
* Cache of strings that have been deduped and stored in the data array.
*/
Expand Down Expand Up @@ -275,7 +281,7 @@ export class FlatpackStore {

private dedupeSetRefs(value: Set<Serializable>, element: SetRefElement): SetRefElement {
if (!this.dedupe) return element;
const values = element.values();
const values = element.valueRefs();
const found = this.cachedSets.get(values);
if (!found) {
this.cachedSets.set(values, element);
Expand All @@ -287,6 +293,10 @@ export class FlatpackStore {
return found;
}

private proxySetRef(ref: SetRefElement): Set<Serializable> {
return proxySet(new Set(this.#toValue(ref.valueRefs()) as Serializable[]), () => {});
}

private createUniqueKeys(keys: Serializable[]): ArrayRefElement {
const cacheValue = false;
let k = this.arrToRef(keys, cacheValue);
Expand Down Expand Up @@ -320,8 +330,8 @@ export class FlatpackStore {

private dedupeMapRefs(value: Map<Serializable, Serializable>, element: MapRefElement): MapRefElement {
if (!this.dedupe) return element;
const keys = element.keys();
const values = element.values();
const keys = element.keyRefs();
const values = element.valueRefs();
let found = this.cachedMaps.get(keys);
if (!found) {
found = new Map();
Expand All @@ -340,6 +350,10 @@ export class FlatpackStore {
return element;
}

private proxyMapRef(_ref: MapRefElement): Map<Serializable, Serializable> {
return new Map();
}

private cvtRegExpToRef(value: RegExp): RegExpRefElement {
const found = this.cache.get(value);
if (found !== undefined) {
Expand All @@ -361,6 +375,10 @@ export class FlatpackStore {
return this.addValueAndElement(value, new DateRefElement(value.getTime()));
}

private proxyDateRef(ref: DateRefElement): Date {
return proxyDate(ref.value, (date) => ref.setTime(date.getTime()));
}

private cvtBigintToRef(value: bigint): BigIntRefElement {
const found = this.cache.get(value);
if (found !== undefined) {
Expand Down Expand Up @@ -415,8 +433,8 @@ export class FlatpackStore {

private dedupeObject(value: PrimitiveObject | ObjectWrapper, element: ObjectRefElement): ObjectRefElement {
if (!this.dedupe) return element;
const keys = element.keys();
const values = element.values();
const keys = element.keyRefs();
const values = element.valueRefs();
let found = this.cachedObjects.get(keys);
if (!found) {
found = new Map();
Expand All @@ -435,6 +453,18 @@ export class FlatpackStore {
return element;
}

private proxyObjectRef(ref: ObjectRefElement): PrimitiveObject {
const keys = this.#toValue(ref.keyRefs()) as string[] | undefined;
const values = this.#toValue(ref.valueRefs()) as Serializable[] | undefined;
const obj = keys && values ? Object.fromEntries(keys.map((key, i) => [key, values[i]])) : {};
return proxyObject(obj, (_value) => {});
}

private proxyObjectWrapperRef(ref: ObjectWrapperRefElement): PrimitiveObject {
const value = Object(this.#toValue(ref.valueRef())) as PrimitiveObject;
return proxyObject(value, (_value) => {});
}

/**
*
* @param value - The array converted to an ArrayRefElement.
Expand Down Expand Up @@ -482,6 +512,11 @@ export class FlatpackStore {
return this.dedupeArray(value, element, cacheValue);
}

private proxyArrayRef(ref: ArrayRefElement): PrimitiveArray {
const arr = ref.valueRefs().map((v) => this.#toValue(v));
return proxyObject(arr, (_value) => {});
}

private valueToRef(value: Serializable): RefElements {
if (value === null) {
return this.primitiveToRef(value);
Expand Down Expand Up @@ -619,6 +654,26 @@ export class FlatpackStore {
}
}

#resolveToValueProxy(ref: RefElements | undefined): Unpacked {
if (!ref) return undefined;
if (ref instanceof ArrayRefElement) return this.proxyArrayRef(ref);
if (ref instanceof ObjectRefElement) return this.proxyObjectRef(ref);
if (ref instanceof PrimitiveRefElementBase) return ref.value;
if (isStringRefElements(ref)) return ref.value;
if (ref instanceof MapRefElement) return this.proxyMapRef(ref);
if (ref instanceof SetRefElement) return this.proxySetRef(ref);
if (ref instanceof BigIntRefElement) return ref.value;
if (ref instanceof RegExpRefElement) return ref.value;
if (ref instanceof DateRefElement) return this.proxyDateRef(ref);
if (ref instanceof ObjectWrapperRefElement) return this.proxyObjectWrapperRef(ref);
assert(false, 'Unknown ref type');
}

#toValue(ref: RefElements | undefined): Unpacked {
if (!ref) return undefined;
return getOrResolve(this.cachedProxies, ref, (ref) => this.#resolveToValueProxy(ref));
}

toJSON(): Flatpacked {
const data = [dataHeader] as Flatpacked;
const idxLookup = this.assignedElements;
Expand Down Expand Up @@ -654,6 +709,10 @@ export class FlatpackStore {
toValue(): Unpacked {
return fromJSON(this.toJSON());
}

_toValueProxy(): Unpacked {
return this.#toValue(this.root);
}
}

type TrieData = StringRefElements;
Expand Down Expand Up @@ -686,3 +745,15 @@ export function toJSON<V extends Serializable>(json: V, options?: FlatpackOption
export function stringify(data: Unpacked, pretty = true): string {
return pretty ? stringifyFlatpacked(toJSON(data)) : JSON.stringify(toJSON(data));
}

type WeakOrNever<K, V> = K extends WeakKey ? WeakMap<K, V> : never;
type SupportedMap<K, V> = Map<K, V> | WeakOrNever<K, V>;

function getOrResolve<K, V>(map: SupportedMap<K, V>, key: K, resolver: (key: K) => V): V {
let value = map.get(key);
if (value === undefined && !map.has(key)) {
value = resolver(key);
map.set(key, value);
}
return value as V;
}
49 changes: 49 additions & 0 deletions packages/flatpack-json/src/Flatpack.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { describe, expect, test } from 'vitest';
import { FlatpackStore, stringify, toJSON } from './Flatpack.mjs';
import { stringifyFlatpacked } from './stringify.mjs';
import { fromJSON } from './unpack.mjs';
import { deepEqual } from './proxy.mts';

const urlFileList = new URL('../fixtures/fileList.txt', import.meta.url);
const baseFilename = new URL(import.meta.url).pathname.split('/').slice(-1).join('').split('.').slice(0, -2).join('.');
Expand Down Expand Up @@ -189,6 +190,23 @@ describe('Flatpack', async () => {
expect(fromJSON(fp.toJSON())).toEqual(data);
expect(fp.toJSON()).not.toEqual(v);
});

test.each`
data
${undefined}
${'string'}
${1}
${1.1}
${null}
${true}
${false}
${new Date()}
${/[a-z]+/}
`('toValue $data', ({ data }) => {
const fp = new FlatpackStore(data);
expect(fp.toValue()).toEqual(data);
expect(fp._toValueProxy()).toEqual(data);
});
});

async function sampleFileList() {
Expand Down Expand Up @@ -249,3 +267,34 @@ function sampleNestedData() {
cValues,
};
}

describe('Flatpack value proxy', () => {
test.each`
value
${undefined}
${'string'}
${1}
${1.1}
${null}
${true}
${false}
${[]}
${[1, 2]}
${['a', 'b', 'a', 'b']}
${{}}
${{ a: 1 }}
${{ a: { b: 1 } }}
${{ a: { a: 'a', b: 42 } }}
${{ a: [1] }}
${new Set(['apple', 'banana', 'pineapple'])}
${new Map([['apple', 1], ['banana', 2], ['pineapple', 3]])}
${/[\p{L}\p{M}]+/gu}
${new Date('2024-01-01')}
`('identity $value', ({ value }) => {
const fp = new FlatpackStore(value);
const proxy = fp._toValueProxy();
expect(deepEqual(proxy, value)).toBe(true);
!(proxy instanceof Map || proxy instanceof Set) && expect(proxy).toEqual(value);
expect(fp._toValueProxy()).toBe(proxy);
});
});
30 changes: 25 additions & 5 deletions packages/flatpack-json/src/RefElements.mts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ export class ObjectRefElement extends BaseRefElement implements RefElement<Objec
this.#v = values;
}

keys(): ArrayRefElement | undefined {
keyRefs(): ArrayRefElement | undefined {
return this.#k;
}

values(): ArrayRefElement | undefined {
valueRefs(): ArrayRefElement | undefined {
return this.#v;
}

Expand Down Expand Up @@ -174,6 +174,10 @@ export class ObjectWrapperRefElement extends BaseRefElement implements RefElemen
this.#v = value;
}

valueRef(): RefElements | undefined {
return this.#v;
}

toElement(lookup: FnIndexLookup): ObjectWrapperElement {
return [ElementType.Object, 0, lookup(this.#v)];
}
Expand Down Expand Up @@ -216,7 +220,7 @@ export class SetRefElement extends BaseRefElement implements RefElement<SetEleme
return this.#v ? [this.#v] : undefined;
}

values(): ArrayRefElement | undefined {
valueRefs(): ArrayRefElement | undefined {
return this.#v;
}

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

keys(): ArrayRefElement | undefined {
keyRefs(): ArrayRefElement | undefined {
return this.#k;
}

values(): ArrayRefElement | undefined {
valueRefs(): ArrayRefElement | undefined {
return this.#v;
}

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

get value(): RegExp {
return new RegExp(this.#p.value, this.#f.value);
}

clone(): RegExpRefElement {
return new RegExpRefElement(this.#p, this.#f);
}
Expand Down Expand Up @@ -315,6 +323,14 @@ export class DateRefElement extends BaseRefElement implements RefElement<DateEle
return [ElementType.Date, this.#v];
}

get value(): Date {
return new Date(this.#v);
}

setTime(time: number): void {
this.#v = time;
}

clone(): DateRefElement {
return new DateRefElement(this.#v);
}
Expand All @@ -336,6 +352,10 @@ export class BigIntRefElement extends BaseRefElement implements RefElement<BigIn
this.#v = value;
}

get value(): bigint {
return BigInt(this.#v.value);
}

toElement(lookup: FnIndexLookup): BigIntElement {
return [ElementType.BigInt, lookup(this.#v)];
}
Expand Down
Loading