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.
@@ -0,0 +1,17 @@
1
+ export declare class RefCounter<T> {
2
+ #private;
3
+ constructor(values?: Iterable<T>);
4
+ /**
5
+ * Increment the reference count for a value. If the value does not exist in the map, it will be added with a count of 1.
6
+ */
7
+ add(value: T): void;
8
+ set(value: T, count: number): void;
9
+ has(value: T): boolean;
10
+ get(value: T): number;
11
+ isReferenced(value: T): boolean;
12
+ clear(): void;
13
+ delete(value: T): boolean;
14
+ [Symbol.iterator](): IterableIterator<[T, number]>;
15
+ toJSON(): Map<T, number>;
16
+ }
17
+ //# sourceMappingURL=RefCounter.d.mts.map
@@ -0,0 +1,42 @@
1
+ export class RefCounter {
2
+ #refCounts = new Map();
3
+ constructor(values) {
4
+ if (values) {
5
+ for (const value of values) {
6
+ this.add(value);
7
+ }
8
+ }
9
+ }
10
+ /**
11
+ * Increment the reference count for a value. If the value does not exist in the map, it will be added with a count of 1.
12
+ */
13
+ add(value) {
14
+ const count = this.#refCounts.get(value) ?? 0;
15
+ this.#refCounts.set(value, count + 1);
16
+ }
17
+ set(value, count) {
18
+ this.#refCounts.set(value, count);
19
+ }
20
+ has(value) {
21
+ return this.#refCounts.has(value);
22
+ }
23
+ get(value) {
24
+ return this.#refCounts.get(value) ?? 0;
25
+ }
26
+ isReferenced(value) {
27
+ return !!this.get(value);
28
+ }
29
+ clear() {
30
+ this.#refCounts.clear();
31
+ }
32
+ delete(value) {
33
+ return this.#refCounts.delete(value);
34
+ }
35
+ [Symbol.iterator]() {
36
+ return this.#refCounts.entries();
37
+ }
38
+ toJSON() {
39
+ return this.#refCounts;
40
+ }
41
+ }
42
+ //# sourceMappingURL=RefCounter.mjs.map
@@ -1,9 +1,9 @@
1
- import type { ArrayElement, BigIntElement, DateElement, Index, MapElement, ObjectElement, ObjectWrapperElement, RegExpElement, SetElement, SimplePrimitive, StringElement, SubStringElement } from './types.mjs';
1
+ import type { ArrayElement, BigIntElement, DateElement, FlatpackIndex, MapElement, ObjectElement, ObjectWrapperElement, RegExpElement, SetElement, SimplePrimitive, StringElement, SubStringElement } from './types.mjs';
2
2
  export interface BaseRef {
3
3
  /** Unique id of the reference */
4
4
  id: number;
5
5
  }
6
- export type FnIndexLookup = (ref: RefElements | undefined) => Index;
6
+ export type FnIndexLookup = (ref: RefElements | undefined) => FlatpackIndex;
7
7
  export interface RefElement<T> extends BaseRef {
8
8
  toElement(lookupFn: FnIndexLookup): T;
9
9
  clone(): RefElement<T>;
package/dist/Trie.d.mts CHANGED
@@ -1,18 +1,48 @@
1
- export declare class Trie<T> {
2
- root: RootNode<T>;
3
- add(key: string | Iterable<string>, data: T): void;
1
+ /**
2
+ * @deprecated This class is a bit wonky and will be removed in the future. Use {@link Trie} instead.
3
+ */
4
+ export declare class TrieOfStrings<T> {
5
+ root: RootNode<string, T>;
6
+ /**
7
+ * Add a string to the Trie.
8
+ * The behavior is a bit wonky and will get deprecated in the future.
9
+ * Wonky behavior:
10
+ * - It adds the data element to every new node created.
11
+ */
12
+ add(key: string | Iterable<string>, data: T): TrieNode<string, T>;
4
13
  find(key: string | Iterable<string>): {
5
14
  data: T | undefined;
6
15
  found: string;
7
16
  } | undefined;
17
+ findNode(key: string | Iterable<string>): FoundNode<string, T> | undefined;
8
18
  }
9
- type ChildMap<T> = Map<string, TrieNode<T>>;
10
- interface TrieNode<T> {
19
+ export declare class Trie<K, T> {
20
+ root: TrieNode<K, T>;
21
+ size: number;
22
+ set(key: Iterable<K>, data: T): TrieNode<K, T>;
23
+ get(key: Iterable<K>): T | undefined;
24
+ has(key: Iterable<K>): boolean;
25
+ delete(key: Iterable<K>): boolean;
26
+ clear(): void;
27
+ /**
28
+ * Look for the longest prefix of the key that exists in the trie and return the node and the found prefix.
29
+ * @param key - The key to search for.
30
+ * @returns The node and path found. If the full key is not found, `found` will be false.
31
+ */
32
+ findNode(key: Iterable<K>): FoundNode<K, T> | undefined;
33
+ }
34
+ type ChildMap<K, T> = Map<K, TrieNode<K, T>>;
35
+ interface TrieNode<K, T> {
11
36
  d?: T | undefined;
12
- c?: ChildMap<T>;
37
+ c?: ChildMap<K, T>;
13
38
  }
14
- interface RootNode<T> extends TrieNode<T> {
39
+ interface RootNode<K, T> extends TrieNode<K, T> {
15
40
  d: undefined;
16
41
  }
42
+ interface FoundNode<K, T> {
43
+ node: TrieNode<K, T>;
44
+ found: boolean;
45
+ path: K[];
46
+ }
17
47
  export {};
18
48
  //# sourceMappingURL=Trie.d.mts.map
package/dist/Trie.mjs CHANGED
@@ -1,5 +1,14 @@
1
- export class Trie {
1
+ /**
2
+ * @deprecated This class is a bit wonky and will be removed in the future. Use {@link Trie} instead.
3
+ */
4
+ export class TrieOfStrings {
2
5
  root = { d: undefined, c: new Map() };
6
+ /**
7
+ * Add a string to the Trie.
8
+ * The behavior is a bit wonky and will get deprecated in the future.
9
+ * Wonky behavior:
10
+ * - It adds the data element to every new node created.
11
+ */
3
12
  add(key, data) {
4
13
  let node = this.root;
5
14
  for (const k of key) {
@@ -13,23 +22,105 @@ export class Trie {
13
22
  }
14
23
  node = n;
15
24
  }
25
+ return node;
16
26
  }
17
27
  find(key) {
28
+ const foundNode = this.findNode(key);
29
+ if (!foundNode) {
30
+ return undefined;
31
+ }
32
+ const { node, path: found } = foundNode;
33
+ return { data: node.d, found: found.join('') };
34
+ }
35
+ findNode(key) {
36
+ let node = this.root;
37
+ const path = [];
38
+ let found = true;
39
+ for (const k of key) {
40
+ const c = node.c;
41
+ if (!c) {
42
+ found = false;
43
+ break;
44
+ }
45
+ const n = c.get(k);
46
+ if (!n) {
47
+ found = false;
48
+ break;
49
+ }
50
+ path.push(k);
51
+ node = n;
52
+ }
53
+ return { node, path, found };
54
+ }
55
+ }
56
+ export class Trie {
57
+ root = {};
58
+ size = 0;
59
+ set(key, data) {
60
+ let node = this.root;
61
+ for (const k of key) {
62
+ let c = node.c;
63
+ if (!c) {
64
+ node.c = c = new Map();
65
+ }
66
+ let n = c.get(k);
67
+ if (!n) {
68
+ c.set(k, (n = {}));
69
+ }
70
+ node = n;
71
+ }
72
+ const wasSet = Object.hasOwn(node, 'd');
73
+ node.d = data;
74
+ if (!wasSet) {
75
+ ++this.size;
76
+ }
77
+ return node;
78
+ }
79
+ get(key) {
80
+ const f = this.findNode(key);
81
+ return f?.found ? f.node.d : undefined;
82
+ }
83
+ has(key) {
84
+ const f = this.findNode(key);
85
+ return !!f?.found && Object.hasOwn(f.node, 'd');
86
+ }
87
+ delete(key) {
88
+ const f = this.findNode(key);
89
+ if (!f?.found || !Object.hasOwn(f.node, 'd')) {
90
+ return false;
91
+ }
92
+ delete f.node.d;
93
+ --this.size;
94
+ return true;
95
+ }
96
+ clear() {
97
+ this.root = {};
98
+ this.size = 0;
99
+ }
100
+ /**
101
+ * Look for the longest prefix of the key that exists in the trie and return the node and the found prefix.
102
+ * @param key - The key to search for.
103
+ * @returns The node and path found. If the full key is not found, `found` will be false.
104
+ */
105
+ findNode(key) {
18
106
  let node = this.root;
19
- let found = '';
107
+ const path = [];
108
+ let found = true;
20
109
  for (const k of key) {
21
110
  const c = node.c;
22
111
  if (!c) {
112
+ found = false;
23
113
  break;
24
114
  }
25
115
  const n = c.get(k);
26
116
  if (!n) {
117
+ found = false;
27
118
  break;
28
119
  }
29
- found += k;
120
+ path.push(k);
30
121
  node = n;
31
122
  }
32
- return { data: node.d, found };
123
+ return { node, path, found };
33
124
  }
34
125
  }
35
126
  //# sourceMappingURL=Trie.mjs.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A cache that can store values for both primitive and object keys. Primitive keys are stored in a Map,
3
+ * while object keys are stored in a WeakMap. This allows the cache to automatically clean up entries for
4
+ * objects that are no longer referenced elsewhere in the program, while still allowing primitive keys to
5
+ * be stored without issue.
6
+ */
7
+ export declare class WeakCache<V> {
8
+ #private;
9
+ constructor(entries?: Iterable<readonly [unknown, V]>);
10
+ get(key: unknown): V | undefined;
11
+ set(key: unknown, value: V): void;
12
+ clear(): void;
13
+ has(key: unknown): boolean;
14
+ }
15
+ //# sourceMappingURL=WeakCache.d.mts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A cache that can store values for both primitive and object keys. Primitive keys are stored in a Map,
3
+ * while object keys are stored in a WeakMap. This allows the cache to automatically clean up entries for
4
+ * objects that are no longer referenced elsewhere in the program, while still allowing primitive keys to
5
+ * be stored without issue.
6
+ */
7
+ export class WeakCache {
8
+ #primitiveCache = new Map();
9
+ #objectCache = new WeakMap();
10
+ constructor(entries) {
11
+ if (entries) {
12
+ for (const [key, value] of entries) {
13
+ this.set(key, value);
14
+ }
15
+ }
16
+ }
17
+ get(key) {
18
+ if (typeof key === 'object' && key !== null) {
19
+ return this.#objectCache.get(key);
20
+ }
21
+ return this.#primitiveCache.get(key);
22
+ }
23
+ set(key, value) {
24
+ if (typeof key === 'object' && key !== null) {
25
+ this.#objectCache.set(key, value);
26
+ }
27
+ else {
28
+ this.#primitiveCache.set(key, value);
29
+ }
30
+ }
31
+ clear() {
32
+ this.#primitiveCache.clear();
33
+ this.#objectCache = new WeakMap();
34
+ }
35
+ has(key) {
36
+ // Note: WeakMap.has will always return false for primitive keys,
37
+ // so we can check both caches without first checking if it is an object.
38
+ return this.#primitiveCache.has(key) || this.#objectCache.has(key);
39
+ }
40
+ }
41
+ //# sourceMappingURL=WeakCache.mjs.map
@@ -1,6 +1,5 @@
1
1
  import type { RefElements } from './RefElements.mjs';
2
- import type { Flatpacked } from './types.mjs';
3
- import { type FlattenedElement } from './types.mjs';
2
+ import type { Flatpacked, FlattenedElement } from './types.mjs';
4
3
  export declare function fromElement(elem: FlattenedElement, resolve: (index: number) => RefElements): RefElements;
5
4
  export declare function fromFlatpacked(flat: Flatpacked): RefElements[];
6
5
  export declare class FlatpackedWrapper {
@@ -0,0 +1,14 @@
1
+ import type { Flatpacked, FlatpackIndex, FlattenedElement, ObjectElement, StringTableElement, UnpackMetaData } from './types.mjs';
2
+ export declare function getIndexesReferencedByElement(elem: FlattenedElement): FlatpackIndex[];
3
+ export declare function extractObjectKeyAndValueIndexes(elem: FlattenedElement): FlatpackIndex[];
4
+ type KeyValueIndexes = [key: FlatpackIndex, value: FlatpackIndex];
5
+ export declare function extractObjectKeyAndValueIndexesFrom(elem: ObjectElement): KeyValueIndexes;
6
+ export declare function extractObjectKeyAndValueIndexesFrom(elem: undefined): undefined;
7
+ export declare function extractObjectKeyAndValueIndexesFrom(elem: FlattenedElement | undefined): KeyValueIndexes | undefined;
8
+ export declare function getFlatpackedRootIdx(flatpack: Flatpacked): FlatpackIndex;
9
+ export declare function getFlatpackedRoot(flatpack: Flatpacked): FlattenedElement;
10
+ export declare function generateUnpackMetaData(flatpack: Flatpacked): UnpackMetaData;
11
+ export declare function isStringTableElement(elem: FlattenedElement | undefined): elem is StringTableElement;
12
+ export declare function isObjectElement(elem: FlattenedElement | undefined): elem is ObjectElement;
13
+ export {};
14
+ //# sourceMappingURL=flatpacked.d.mts.map
@@ -0,0 +1,129 @@
1
+ import assert from 'node:assert';
2
+ import { RefCounter } from './RefCounter.mjs';
3
+ import { ElementType } from './types.mjs';
4
+ export function getIndexesReferencedByElement(elem) {
5
+ if (!elem) {
6
+ return [];
7
+ }
8
+ if (Array.isArray(elem)) {
9
+ return extractElementIndexes(elem);
10
+ }
11
+ if (typeof elem === 'string') {
12
+ return [];
13
+ }
14
+ if (typeof elem === 'number') {
15
+ return [];
16
+ }
17
+ if (typeof elem === 'object') {
18
+ return [];
19
+ }
20
+ assert(typeof elem === 'boolean', `Expected boolean, got ${typeof elem}`);
21
+ return [];
22
+ }
23
+ function extractElementIndexes(element) {
24
+ switch (element[0]) {
25
+ case ElementType.Array: {
26
+ return element.slice(1);
27
+ }
28
+ case ElementType.Object: {
29
+ return element.slice(1).filter((v) => !!v);
30
+ }
31
+ case ElementType.String: {
32
+ return element.slice(1);
33
+ }
34
+ case ElementType.SubString: {
35
+ return [element[1]];
36
+ }
37
+ case ElementType.Set: {
38
+ return element.slice(1, 2);
39
+ }
40
+ case ElementType.Map: {
41
+ return element.slice(1, 3);
42
+ }
43
+ case ElementType.RegExp: {
44
+ return element.slice(1, 3);
45
+ }
46
+ case ElementType.Date: {
47
+ return [];
48
+ }
49
+ case ElementType.BigInt: {
50
+ return [element[1]];
51
+ }
52
+ }
53
+ if (!element.length)
54
+ return [];
55
+ assert(false, 'Invalid element type.');
56
+ }
57
+ export function extractObjectKeyAndValueIndexes(elem) {
58
+ if (!elem || typeof elem !== 'object' || !Array.isArray(elem))
59
+ return [];
60
+ const element = elem;
61
+ switch (element[0]) {
62
+ case ElementType.Object: {
63
+ return element.slice(1).filter((v) => !!v);
64
+ }
65
+ case ElementType.Set: {
66
+ return []; // element.slice(1, 2);
67
+ }
68
+ case ElementType.Map: {
69
+ return []; // element.slice(1, 3);
70
+ }
71
+ }
72
+ return [];
73
+ }
74
+ export function extractObjectKeyAndValueIndexesFrom(elem) {
75
+ if (!isObjectElement(elem))
76
+ return undefined;
77
+ return [elem[1], elem[2]];
78
+ }
79
+ export function getFlatpackedRootIdx(flatpack) {
80
+ let idx = 1;
81
+ if (isStringTableElement(flatpack[idx])) {
82
+ idx++;
83
+ }
84
+ assert(!isStringTableElement(flatpack[idx]), 'String table element should be at index 1 if it exists');
85
+ return idx;
86
+ }
87
+ export function getFlatpackedRoot(flatpack) {
88
+ return flatpack[getFlatpackedRootIdx(flatpack)];
89
+ }
90
+ export function generateUnpackMetaData(flatpack) {
91
+ const referenced = new RefCounter();
92
+ const rootIndex = getFlatpackedRootIdx(flatpack);
93
+ calcReferenced(rootIndex);
94
+ // Make sure the string table is always included.
95
+ if (rootIndex > 1) {
96
+ referenced.add(1);
97
+ }
98
+ const metaData = {
99
+ flatpack,
100
+ referenced,
101
+ rootIndex,
102
+ };
103
+ return metaData;
104
+ function calcReferenced(idx) {
105
+ if (!idx)
106
+ return;
107
+ if (referenced.isReferenced(idx)) {
108
+ referenced.add(idx);
109
+ return;
110
+ }
111
+ referenced.add(idx);
112
+ for (const ref of getIndexesReferencedByElement(flatpack[idx])) {
113
+ calcReferenced(ref);
114
+ }
115
+ }
116
+ }
117
+ export function isStringTableElement(elem) {
118
+ if (!Array.isArray(elem)) {
119
+ return false;
120
+ }
121
+ return elem[0] === ElementType.StringTable;
122
+ }
123
+ export function isObjectElement(elem) {
124
+ if (!Array.isArray(elem)) {
125
+ return false;
126
+ }
127
+ return elem[0] === ElementType.Object;
128
+ }
129
+ //# sourceMappingURL=flatpacked.mjs.map
@@ -1,43 +1,75 @@
1
1
  import assert from 'node:assert';
2
+ import { getIndexesReferencedByElement } from './flatpacked.mjs';
3
+ import { StringTableBuilder } from './stringTable.mjs';
2
4
  import { ElementType, supportedHeaders } from './types.mjs';
3
5
  export function optimizeFlatpacked(data) {
4
- const [header] = data;
6
+ const [header, maybeStringTable] = data;
7
+ if (data[1] === undefined || data[2] === undefined) {
8
+ return data;
9
+ }
5
10
  if (!supportedHeaders.has(header)) {
6
11
  throw new Error('Invalid header');
7
12
  }
8
- const elementRefs = data.map((element, index) => ({ origIndex: index, refCount: 0, index: 0, element }));
9
- const indexToRefElement = new Map(elementRefs.entries());
13
+ const stringTable = maybeStringTable && Array.isArray(maybeStringTable) && maybeStringTable[0] === ElementType.StringTable
14
+ ? maybeStringTable
15
+ : undefined;
16
+ const startIndex = stringTable ? 2 : 1;
17
+ const elements = data.slice(startIndex);
18
+ const elementRefs = elements.map((element, index) => ({
19
+ origIndex: index + startIndex,
20
+ refCount: 0,
21
+ index: 0,
22
+ element,
23
+ }));
24
+ const indexToRefElement = new Map(elementRefs.map((refElement) => [refElement.origIndex, refElement]));
25
+ const stringTableBuilder = new StringTableBuilder(stringTable);
10
26
  for (const refElement of elementRefs) {
11
- if (refElement.origIndex === 0) {
12
- continue;
13
- }
14
- const indexes = getRefIndexes(refElement.element);
27
+ const indexes = getIndexesReferencedByElement(refElement.element);
15
28
  for (const index of indexes) {
29
+ if (index < 0) {
30
+ stringTableBuilder.addRef(-index);
31
+ continue;
32
+ }
33
+ if (!index) {
34
+ continue;
35
+ }
16
36
  const ref = indexToRefElement.get(index);
17
37
  assert(ref, `Invalid reference index: ${index}`);
18
38
  ref.refCount++;
19
39
  }
20
40
  }
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
- });
41
+ const sortedRefElements = elementRefs.sort((a, b) => b.refCount - a.refCount || a.origIndex - b.origIndex);
25
42
  const indexMap = new Map([
26
43
  [0, 0],
27
44
  [1, 1],
28
45
  ]);
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];
46
+ if (stringTable) {
47
+ indexMap.set(2, 2);
48
+ }
49
+ for (const refElement of sortedRefElements) {
50
+ const idx = indexMap.get(refElement.origIndex) ?? indexMap.size;
51
+ refElement.index = idx;
52
+ indexMap.set(refElement.origIndex, idx);
53
+ }
54
+ for (const [oldStrIndex, newStrIndex] of stringTableBuilder.sortEntriesByRefCount()) {
55
+ if (!oldStrIndex || !newStrIndex) {
56
+ continue;
57
+ }
58
+ indexMap.set(-oldStrIndex, -newStrIndex);
59
+ }
60
+ const stringTableElements = stringTable ? [stringTableBuilder.build()] : [];
61
+ const result = [header, ...stringTableElements];
62
+ for (const refElement of sortedRefElements) {
63
+ const element = patchIndexes(refElement.element, indexMap);
64
+ result[refElement.index] = element;
65
+ }
66
+ return result;
37
67
  }
38
68
  function patchIndexes(elem, indexMap) {
39
69
  function mapIndex(index) {
40
70
  const v = indexMap.get(index);
71
+ if (v === undefined && index < 0)
72
+ return index;
41
73
  assert(v !== undefined, `Invalid index: ${index}`);
42
74
  return v;
43
75
  }
@@ -76,6 +108,8 @@ function patchIndexes(elem, indexMap) {
76
108
  return [element[0], mapIndex(element[1])];
77
109
  }
78
110
  }
111
+ if (!element.length)
112
+ return [];
79
113
  assert(false, 'Invalid element type');
80
114
  }
81
115
  if (Array.isArray(elem)) {
@@ -90,58 +124,7 @@ function patchIndexes(elem, indexMap) {
90
124
  if (typeof elem === 'object') {
91
125
  return elem;
92
126
  }
93
- assert(typeof elem === 'boolean');
127
+ assert(typeof elem === 'boolean', `Expected boolean, got ${typeof elem}`);
94
128
  return elem;
95
129
  }
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
130
  //# sourceMappingURL=optimizeFlatpacked.mjs.map
package/dist/proxy.mjs CHANGED
@@ -134,7 +134,6 @@ export function proxyMap(values, onUpdate) {
134
134
  }
135
135
  set(key, value) {
136
136
  const r = super.set(key, value);
137
- console.log('set', key, value);
138
137
  onUpdate?.(this, key, value);
139
138
  return r;
140
139
  }