genome 0.1.0 → 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Valtio Universe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,136 +1,286 @@
1
+ ![NPM Version](https://img.shields.io/npm/v/genome?style=flat-square&color=%23e8b339)
2
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/overthemike/genome/test.yml?style=flat-square&color=%23e8b339)
3
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/genome?style=flat-square&color=%23e8b339)
4
+ ![NPM License](https://img.shields.io/npm/l/genome?style=flat-square&color=%23e8b339)
5
+
6
+
1
7
  # genome
2
8
 
3
- Simple build system using ES6 classes, generators, and promises
9
+ A lightweight, robust library for generating unique identifiers for JavaScript/TypeScript objects based on their structure rather than requiring explicit string keys.
10
+
11
+ ## Purpose
12
+
13
+ This library provides a solution for scenarios where you need to:
14
+
15
+ - Persist and rehydrate state without requiring explicit string keys
16
+ - Identify structurally identical objects across different instances
17
+ - Match objects by their shape rather than by identity or manual keys
18
+ - Detect circular references safely
4
19
 
5
20
  ## Installation
6
- genome requires io.js or node.js with harmony flags.
7
21
 
22
+ ```bash
23
+ pnpm add genome
8
24
  ```
9
- npm i -g genome
10
- npm i -save-dev genome
11
- ```
12
25
 
13
- ## Usage
14
- Create a `genomefile.js` in your project's root directory.
26
+ ## Basic Usage
27
+ ```typescript
28
+ import { generateStructureId, getCompactId } from 'genome';
29
+
30
+ // Example object
31
+ const user = {
32
+ name: 'John',
33
+ age: 30,
34
+ preferences: {
35
+ theme: 'dark',
36
+ notifications: true
37
+ }
38
+ }
39
+
40
+ // Generate a unique ID based on the object structure and it's properties' types
41
+ const id = generateStructureId(user) // L0:3713-L1:5761-L2:13827
15
42
 
16
- ```javascript
17
- 'use strict'; // Required to use classes
43
+ // Generate a unique ID and then hash that value
44
+ const hashed = getCompactId(user) // cd76ea96
18
45
 
19
- var genome = require('genome');
46
+ // Get info on what a structure would be without generating
47
+ const {
48
+ id, // L0:541598767187353870402585606-L1:1547425049106725343623905933-L2:10
49
+ levels, // 3
50
+ collisions // 0 ID collisions - same object structure already ran through generator
51
+ } = getStructureInfo(user)
20
52
 
21
- module.exports = class {
22
- // Tasks go here
23
- }
53
+ // Get info for a compact id
54
+ const {
55
+ id, // cd76ea96
56
+ levels, // 3
57
+ collisions // 0 ID collisions - same object structure already ran through generator
58
+ } = getCompactInfo(user)
59
+
60
+ // get all of the data being stored about the state - debugging info
61
+ const allStructureStateData = exportStructureState()
24
62
  ```
25
63
 
26
- ### Create tasks
27
- Tasks in genome are generator functions. Task names may include colons (:) and/or hyphens (-).
64
+ ## API Reference
28
65
 
29
- ```javascript
30
- *sayhi () {
31
- console.log('Hello, world');
32
- }
66
+ ### `generateStructureId(obj: Record<string, any>, config?: StructureIdConfig): string`
33
67
 
34
- *'say-something-else' () {
35
- console.log('Something else');
36
- }
68
+ Generates a unique ID string based on the structure of the provided object.
69
+
70
+ - **Parameters**:
71
+ - `obj`: The object to generate an ID for.
72
+ - `config` (optional): Configuration options for ID generation.
73
+ - **Returns**: A string representing the structure ID.
74
+
75
+ ### `getStructureInfo(obj: Record<string, any>, config?: StructureIdConfig): { id: string; levels: number; collisionCount: number; }`
76
+
77
+ Provides additional information about the object's structure.
78
+
79
+ - **Parameters**:
80
+ - `obj`: The object to analyze.
81
+ - `config` (optional): Configuration options for ID generation.
82
+ - **Returns**: An object containing:
83
+ - `id`: The structure ID.
84
+ - `levels`: The number of nesting levels in the object.
85
+ - `collisionCount`: The number of times this structure has been encountered.
86
+
87
+ ### `setStructureIdConfig(config: StructureIdConfig): void`
88
+
89
+ Sets global configuration options for structure ID generation.
90
+
91
+ - **Parameters**:
92
+ - `config`: The configuration object.
93
+
94
+ ### `getStructureIdConfig(): StructureIdConfig`
95
+
96
+ Gets the current global configuration.
97
+
98
+ - **Returns**: A copy of the current global configuration object.
99
+
100
+ ### `resetState(): void`
101
+
102
+ Resets the internal state of the library, clearing all cached property mappings.
103
+
104
+ **Note**: You typically don't need to call this unless you want to start fresh with property-to-bit mappings.
105
+
106
+ ### Configuration Options
107
+
108
+ The `StructureIdConfig` object supports the following options:
37
109
 
38
- *'say:goodbye' () {
39
- console.log('See ya later');
110
+ ```typescript
111
+ interface StructureIdConfig {
112
+ newIdOnCollision?: boolean;
40
113
  }
41
114
  ```
42
115
 
43
- ### Run tasks
44
- In your command line, run:
116
+ #### `newIdOnCollision` (default: `false`)
45
117
 
46
- ```
47
- genome sayhi
48
- ```
118
+ When set to `true`, each object with the same structure will receive a unique ID. This is useful when you need to distinguish between different object instances that share the same structure.
119
+
120
+ ```typescript
121
+ // Generate a unique ID for each object, even with the same structure
122
+ const config = { newIdOnCollision: true };
123
+
124
+ const obj1 = { name: "John", age: 30 };
125
+ const obj2 = { name: "Alice", age: 25 };
49
126
 
50
- genome commands may accept multiple tasks to run asyncronously:
127
+ const id1 = generateStructureId(obj1, config);
128
+ const id2 = generateStructureId(obj2, config);
51
129
 
130
+ console.log(id1); // "L0:0-L1:5"
131
+ console.log(id2); // "L0:1-L1:5"
132
+ console.log(id1 === id2); // false (even though structure is identical)
52
133
  ```
53
- genome sayhi say-something-else say:goodbye
134
+
135
+ You can set this option globally or per call:
136
+
137
+ ```typescript
138
+ // Set globally
139
+ setStructureIdConfig({ newIdOnCollision: true });
140
+
141
+ // Will use global config
142
+ const id1 = generateStructureId(obj1);
143
+ const id2 = generateStructureId(obj2);
144
+
145
+ // Override for a specific call
146
+ const id3 = generateStructureId(obj3, { newIdOnCollision: false });
54
147
  ```
55
148
 
56
- Run a task from within another task using `genome.do()`.
149
+ ## When to use `genome`
57
150
 
58
- ```javascript
59
- *speak () {
60
- genome.do('sayhi');
61
- }
151
+ The `genome` library shines in scenarios where you need to identify and match objects based on their structure rather than explicit keys or instance identity. Here are some ideal use cases:
152
+
153
+ ### State Management Without Explicit Keys
154
+
155
+ When persisting and rehydrating application state, you often need a way to match stored state with the corresponding objects in your application. Instead of manually maintaining string keys for every object, `genome` automatically generates consistent identifiers based on object structure.
156
+
157
+ ```ts
158
+ // Instead of this:
159
+ const componentKey = "user-preferences-panel";
160
+ storeState(componentKey, preferences);
161
+ // Later:
162
+ const savedState = getState(componentKey);
163
+
164
+ // You can do this:
165
+ const structureId = generateStructureId(preferences);
166
+ storeState(structureId, preferences);
167
+ // Later:
168
+ const savedState = getState(generateStructureId(preferences));
62
169
  ```
63
170
 
64
- `genome.do()` accepts strings and arrays. Arrays of tasks will be run asyncronously.
171
+ ### Memoization and Caching
65
172
 
66
- ```javascript
67
- *speak1 () {
68
- genome.do(['sayhi', 'say-something-else', 'say:goodbye']);
173
+ When implementing memoization patterns, you can use structure-based IDs to cache results based on input structure rather than identity:
174
+
175
+ ```ts
176
+ const memoizedResults = new Map<string, any>();
177
+
178
+ function expensiveCalculation(data: SomeComplexObject) {
179
+ const structureId = generateStructureId(data);
180
+
181
+ if (memoizedResults.has(structureId)) {
182
+ return memoizedResults.get(structureId);
183
+ }
184
+
185
+ const result = /* complex calculation */;
186
+ memoizedResults.set(structureId, result);
187
+ return result;
69
188
  }
189
+ ```
190
+
191
+ ### Normalizing Data for Storage
70
192
 
71
- // Is the same as:
193
+ When storing objects in databases or state management systems, you can use structural IDs to create consistent references:
72
194
 
73
- *speak2 () {
74
- genome.do('sayhi');
75
- genome.do('say-something-else');
76
- genome.do('say:goodbye');
195
+ ```ts
196
+ function normalizeForStorage(entities: Record<string, unknown>[]) {
197
+ const normalizedEntities: Record<string, any> = {};
198
+
199
+ for (const entity of entities) {
200
+ const id = generateStructureId(entity);
201
+ normalizedEntities[id] = entity;
202
+ }
203
+
204
+ return normalizedEntities;
77
205
  }
78
206
  ```
79
207
 
80
- If you need tasks to run in a certain order, add the yield statement before calling `genome.do()`.
208
+ ### Change detection
81
209
 
82
- ```javascript
83
- *speak2 () {
84
- yield genome.do('sayhi');
85
- yield genome.do('say-something-else');
86
- genome.do('say:goodbye');
210
+ Detect changes in object structure without relying on reference equality:
211
+
212
+ ```ts
213
+ function hasStructuralChanges(oldObj: object, newObj: object): boolean {
214
+ return generateStructureId(oldObj) !== generateStructureId(newObj);
87
215
  }
88
216
  ```
89
217
 
90
- ### Read/write files
91
- Genomen adds a `.contents` property to strings to make reading and writing files as easy as:
218
+ ### Object Deduplication
219
+
220
+ Efficiently identify and remove duplicate objects with identical structures:
221
+
222
+ ```ts
223
+ function deduplicateByStructure<T>(objects: T[]): T[] {
224
+ const uniqueStructures = new Map<string, T>();
225
+
226
+ for (const obj of objects) {
227
+ const id = generateStructureId(obj as Record<string, unknown>);
228
+ if (!uniqueStructures.has(id)) {
229
+ uniqueStructures.set(id, obj);
230
+ }
231
+ }
232
+
233
+ return Array.from(uniqueStructures.values());
234
+ }
235
+ ```
92
236
 
93
- ```javascript
94
- 'dist/html.index'.contents = yield 'app/html.index'.contents;
237
+ ### Unique Per-Instance IDs
238
+
239
+ When you need to uniquely identify each object instance, even if they share the same structure, you can use the `newIdOnCollision` option:
240
+
241
+ ```ts
242
+ function assignUniqueIds<T>(objects: T[]): Map<T, string> {
243
+ const idMap = new Map<T, string>();
244
+ const config = { newIdOnCollision: true };
245
+
246
+ for (const obj of objects) {
247
+ const id = generateStructureId(obj as Record<string, unknown>, config);
248
+ idMap.set(obj, id);
249
+ }
250
+
251
+ return idMap;
252
+ }
95
253
  ```
254
+ ### Benefits Over Manual Key Management
96
255
 
97
- Genome does not require plugins like gulp or grunt. Simply install standard node packages and use their
98
- build-in api.
256
+ - Automatic: No need to manually specify and maintain string keys
257
+ - Consistent: Same structure always generates the same ID
258
+ - Structural: Changes to object structure are automatically reflected in the ID
259
+ - Safe: Handles circular references without issues
260
+ - Deterministic: Property order doesn't affect the generated ID
99
261
 
100
- ```javascript
101
- *html () {
102
- // Process one file and output it
103
- var slm = require('slm');
262
+ ## How It Works
104
263
 
105
- yield 'dist/index.html'.contents = slm.render(yield 'app/index.slm'.contents);
106
- browserSync.reload(paths.html.dest);
107
- }
264
+ The library uses a bit-wise approach to generate structure IDs:
108
265
 
109
- *scripts () {
110
- // Output stream to file
111
- var browserify = require('browserify');
266
+ 1. Each JavaScript type gets a unique bit value (`number`, `string`, `object`, etc.)
267
+ 2. Each property name gets a unique bit value the first time it's encountered
268
+ 3. These bit values are consistently used for the same types and property names
269
+ 4. The object is traversed, and hash values are calculated for each level of nesting
270
+ 5. The final ID is formed by combining these level hashes
112
271
 
113
- yield 'dist/scripts/app.js'.contents = browserify('app/scripts/app.js', { transform: 'babelify' }).bundle();
114
- browserSync.reload(paths.scripts.dest);
115
- }
272
+ This approach ensures:
273
+ - Identical structures get identical IDs
274
+ - Different structures get different IDs
275
+ - The algorithm works correctly with circular references
276
+ - Property order doesn't affect the generated ID
116
277
 
117
- *styles () {
118
- // Output multiple files to directory with the String.prototype.use method
119
- var stylus = require('stylus');
278
+ ## Performance Considerations
120
279
 
121
- yield 'dist/styles/'.contents = yield 'app/styles/*.styl'.use(stylus.render, '.css');
122
- browserSync.reload(paths.styles.dest);
123
- }
124
- ```
280
+ - The library maintains a global mapping of property names to bit values, which grows as more unique property names are encountered
281
+ - For very large or complex objects, the bit values might become quite large (using BigInt internally)
282
+ - Circular references are handled efficiently without stack overflows
125
283
 
126
- ### Watch files
127
- Watch files for changes with String.prototype.onChange, passing in a function or a task name or array of task names.
284
+ ## License
128
285
 
129
- ```javascript
130
- *watch () {
131
- 'app/**/*.slm'.onChange('html');
132
- 'app/scripts/**/*.js'.onChange('scripts');
133
- 'app/styles/**/*.styl'.onChange('styles');
134
- 'dist/**/*'.onChange(browserSync.reload);
135
- }
136
- ```
286
+ MIT
package/dist/hash.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @fileoverview Fast, non-cryptographic hash functions for structure ID generation
3
+ * @module @synpatico/genome/hash
4
+ */
5
+ /**
6
+ * Available hash function implementations
7
+ */
8
+ export declare const hashFunction: {
9
+ readonly MURMUR: typeof murmurHash3;
10
+ readonly XXHASH: typeof xxHash32;
11
+ };
12
+ /**
13
+ * Type representing available hash functions
14
+ */
15
+ export type HashFunction = (typeof hashFunction)[keyof typeof hashFunction];
16
+ /**
17
+ * Options for hash function selection
18
+ */
19
+ type HashFunctionProps = {
20
+ /** Built-in hash function to use */
21
+ type?: HashFunction;
22
+ /** Custom hash function implementation */
23
+ custom?: (str: string) => string;
24
+ };
25
+ /**
26
+ * Generates a hash from a string using the specified algorithm.
27
+ * Defaults to xxHash32 for optimal performance.
28
+ *
29
+ * @param str - The string to hash
30
+ * @param options - Optional hash function configuration
31
+ * @returns A hexadecimal hash string
32
+ *
33
+ * @example
34
+ * ```javascript
35
+ * // Using default xxHash32
36
+ * const h1 = hash("hello"); // "884863d4"
37
+ *
38
+ * // Using MurmurHash3
39
+ * const h2 = hash("hello", { type: hashFunction.MURMUR }); // "613cae7d"
40
+ *
41
+ * // Using custom function
42
+ * const h3 = hash("hello", { custom: (s) => s.length.toString() }); // "5"
43
+ * ```
44
+ */
45
+ export declare const hash: (str: string, options?: HashFunctionProps) => string;
46
+ /**
47
+ * MurmurHash3 implementation - fast non-cryptographic hash function.
48
+ * Provides good distribution and collision resistance for hash tables.
49
+ *
50
+ * @param str - The string to hash
51
+ * @returns A hexadecimal hash string
52
+ *
53
+ * @example
54
+ * ```javascript
55
+ * const hash = murmurHash3("hello world");
56
+ * console.log(hash); // "5e928f0f"
57
+ * ```
58
+ */
59
+ export declare function murmurHash3(str: string): string;
60
+ /**
61
+ *
62
+ * @param input - byte array or string
63
+ * @param seed - optional seed (32-bit unsigned);
64
+ */
65
+ export declare function xxHash32(input: Uint8Array | string, seed?: number): string;
66
+ export {};
67
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY;;;CAGf,CAAA;AAEV;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAA;AAE3E;;GAEG;AACH,KAAK,iBAAiB,GAAG;IACxB,oCAAoC;IACpC,IAAI,CAAC,EAAE,YAAY,CAAA;IACnB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;CAChC,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,UAAU,iBAAiB,KAAG,MAU/D,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAyD/C;AAsBD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,IAAI,SAAI,GAAG,MAAM,CA2KrE"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Order-preserving structure ID generator that avoids GLOBAL_KEY_MAP growth
3
+ * from array indices and lengths by hashing numeric values directly.
4
+ *
5
+ * This mirrors the current generateStructureId algorithm with one change:
6
+ * - For arrays, we compute index/length contributions via numeric hashing
7
+ * (fixed seeds) instead of storing strings like "[i]" and "length:n"
8
+ * in GLOBAL_KEY_MAP. Property names still use GLOBAL_KEY_MAP.
9
+ */
10
+ import { type StructureIdConfig } from "./index";
11
+ /**
12
+ * Generate a structure ID while avoiding GLOBAL_KEY_MAP growth from array indices/lengths.
13
+ * Property names still use GLOBAL_KEY_MAP like the current implementation.
14
+ */
15
+ export declare const generateStructureIdIndexHash: (obj: unknown, config?: StructureIdConfig) => string;
16
+ export declare function getStructureInfoIndexHash(obj: unknown, config?: StructureIdConfig): {
17
+ id: string;
18
+ levels: number;
19
+ collisionCount: number;
20
+ };
21
+ //# sourceMappingURL=id-indexhash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-indexhash.d.ts","sourceRoot":"","sources":["../src/id-indexhash.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,SAAS,CAAA;AA8DhB;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACvC,KAAK,OAAO,EACZ,SAAS,iBAAiB,KACzB,MAuHF,CAAA;AAED,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,OAAO,EACZ,MAAM,CAAC,EAAE,iBAAiB,GACzB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAUxD"}
@@ -0,0 +1,6 @@
1
+ export declare function generateStructureIdMultiset(obj: unknown): string;
2
+ export declare function getStructureInfoMultiset(obj: unknown): {
3
+ id: string;
4
+ levels: number;
5
+ };
6
+ //# sourceMappingURL=id-multiset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-multiset.d.ts","sourceRoot":"","sources":["../src/id-multiset.ts"],"names":[],"mappings":"AAgCA,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAyEhE;AAGD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAGrF"}
@@ -0,0 +1,2 @@
1
+ export declare function generateStructureIdMultiset32(obj: unknown): string;
2
+ //# sourceMappingURL=id-multiset32.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id-multiset32.d.ts","sourceRoot":"","sources":["../src/id-multiset32.ts"],"names":[],"mappings":"AAwCA,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAiElE"}