{"id":31753685,"url":"https://github.com/elemental-mind/associum","last_synced_at":"2026-01-20T17:00:04.666Z","repository":{"id":316788888,"uuid":"1049295592","full_name":"elemental-mind/associum","owner":"elemental-mind","description":"Lightweight multi-key maps with TypeScript support. Index values with a collection of keys efficiently.","archived":false,"fork":false,"pushed_at":"2025-10-12T16:28:50.000Z","size":147,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-18T15:08:42.829Z","etag":null,"topics":["index","indexing","map","multi-key","querying","structured","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elemental-mind.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-02T19:08:27.000Z","updated_at":"2025-09-28T20:52:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"23aac17f-8cc0-43f5-893d-bb99b711a9b0","html_url":"https://github.com/elemental-mind/associum","commit_stats":null,"previous_names":["elemental-mind/associum"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/elemental-mind/associum","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elemental-mind%2Fassocium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elemental-mind%2Fassocium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elemental-mind%2Fassocium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elemental-mind%2Fassocium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elemental-mind","download_url":"https://codeload.github.com/elemental-mind/associum/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elemental-mind%2Fassocium/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["index","indexing","map","multi-key","querying","structured","typescript"],"created_at":"2025-10-09T17:52:59.151Z","updated_at":"2026-01-20T17:00:04.659Z","avatar_url":"https://github.com/elemental-mind.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Associum\n\nAssocium provides multi-key maps built on top of `Map` with full TypeScript support.\n\nA vanilla map just compares its keys by reference, and does not reason about the structure of the given key. Associum's multi-key-maps solve this problem without falling back to serializing the whole key object, which would be memory-inefficient and make queries hard.\n\nIf you ever wanted to associate `[\"plant\", \"edible\", \"leafy\"] =\u003e \"spinach\"` or `{concept: \"mobility\", subconcept: \"car\", data: \"manufacturers\"} =\u003e [\"Mercedes\", \"BMW\", \"Toyota\", \"GM\", \"Ford\"]` this library provides the tools to make these associations and query them.\n\n## Main Features\n\n- **Multiple Indexing Options**: Support for unordered arrays, ordered arrays, or records (string-value pairs) as composite keys.\n- **Queryability**: Optional mixin enabling partial key queries for flexible lookups (adds ~1KB to bundle size).\n- **Map Compatibility**: Extends the built-in `Map`, preserving standard methods while enhancing key interpretation.\n- **Memory Efficient**: Uses unique keylet IDs for subkeys with per-instance management and reference-counted garbage collection, achieving memory usage comparable to a vanilla `Map`.\n- **Lightweight**: Tree-shakeable; base unordered variant ~200 bytes minified + gzipped (varies with build).\n- **Type Safe**: Comprehensive TypeScript generics for keys and values.\n- **Universal**: Compatible with browsers, Node.js, Deno, and other JS environments.\n- **Instance Isolated**: Self-contained keylet registry per map prevents interference across instances.\n- **Key Safety**: All indexing rejects `undefined` keys to ensure valid composites.\n\n## Installation \u0026 Use\n\nInstall via npm:\n\n```bash\nnpm install associum\n```\n\nImport and use like a standard `Map`, selecting the appropriate type:\n\n```typescript\nimport { UnorderedMultiKeyMap } from 'associum';\n\nconst multiKeyMap = new UnorderedMultiKeyMap\u003cstring[], any\u003e();\n\nmultiKeyMap.set([\"a\", \"c\", \"b\"], 123);\n\nif (multiKeyMap.has([\"a\", \"b\", \"c\"]))\n  console.log(multiKeyMap.get([\"b\", \"a\", \"c\"])); // 123\n```\n\n## Concepts\n\n### Keys\n\nAssocium supports three key types:\n\n#### Unordered Keys\nUnordered keys are arrays where the order of elements doesn't matter. When you set a value with `[\"A\", \"B\"]`, you can retrieve it with either `[\"A\", \"B\"]` or `[\"B\", \"A\"]`. This is useful when the combination of values is important, but not their sequence.\n\n```typescript\nconst map = new UnorderedMultiKeyMap\u003cstring[], number\u003e();\nmap.set([\"plant\", \"edible\", \"leafy\"], 1);\nconsole.log(map.get([\"leafy\", \"plant\", \"edible\"])); // 1\n```\n\n#### Ordered Keys\nOrdered keys are arrays where the order of elements matters. When you set a value with `[\"A\", \"B\"]`, you can only retrieve it with the exact same order `[\"A\", \"B\"]`. This is useful when sequence is important.\n\n```typescript\nconst map = new OrderedMultiKeyMap\u003cstring[], number\u003e();\nmap.set([\"concept\", \"mobility\", \"car\"], 1);\nconsole.log(map.get([\"concept\", \"mobility\", \"car\"])); // 1\nconsole.log(map.get([\"mobility\", \"concept\", \"car\"])); // undefined\n```\n\n#### Structured Keys\nStructured keys are objects where the first level of properties acts as the key. This provides a more semantic way to define keys with named properties.\n\n```typescript\nconst map = new StructuredMultiKeyMap\u003c{concept: string, subconcept: string, data: string}, string[]\u003e();\nmap.set({concept: \"mobility\", subconcept: \"car\", data: \"manufacturers\"}, [\"Mercedes\", \"BMW\", \"Toyota\"]);\nconsole.log(map.get({concept: \"mobility\", subconcept: \"car\", data: \"manufacturers\"})); // [\"Mercedes\", \"BMW\", \"Toyota\"]\n```\n\n**Note**: `undefined` values are not allowed for setting any type of key; attempts to set such keys throw an error.\n\n### Querying\n\nAssocium provides optional queryability that allows you to retrieve entries based on partial key matches. This feature is available through queryable variants of each map type.\n\n#### Querying Unordered Keys\nWith queryable unordered maps, you can find entries that contain specific key values regardless of their position in the array.\n\n```typescript\nconst map = new QueryableUnorderedMultiKeyMap\u003cstring[], number\u003e();\nmap.set([\"plant\", \"edible\", \"leafy\"], 1);\nmap.set([\"plant\", \"edible\", \"root\"], 2);\nmap.set([\"animal\", \"mammal\", \"domestic\"], 3);\n\n// Find entries with \"plant\" and \"edible\"\nconst results = map.queryIndexedWith([\"plant\", \"edible\"]);\n// [{ key: [\"plant\", \"edible\", \"leafy\"], value: 1 }, { key: [\"plant\", \"edible\", \"root\"], value: 2 }]\n```\n\n**Note**: Unordered maps do not support positional `query`; use `queryIndexedWith` for subset matches.\n\n#### Ordered Querying\nMatch specific positions with wildcards (`undefined`). Use `query`.\n\n```typescript\nconst map = new QueryableOrderedMultiKeyMap\u003cstring[], number\u003e();\nmap.set([\"concept\", \"mobility\", \"car\"], 1);\nmap.set([\"concept\", \"housing\", \"apartment\"], 2);\nmap.set([\"concept\", \"mobility\", \"bike\"], 3);\n\n// Match position 0=\"concept\", 1=\"mobility\"\nconst results = map.query([\"concept\", \"mobility\", undefined]);\n// [{ key: [\"concept\", \"mobility\", \"car\"], value: 1 }, { key: [\"concept\", \"mobility\", \"bike\"], value: 3 }]\n```\n\nSupports prefix/suffix matching via wildcards.\n\n#### Querying Structured Keys\nWith queryable structured maps, you can find entries that match specific property values, omitting properties you don't care about.\n\n```typescript\nconst map = new QueryableStructuredMultiKeyMap\u003c{user: string, role: string, department: string}, number\u003e();\nmap.set({user: \"u1\", role: \"admin\", department: \"IT\"}, 1);\nmap.set({user: \"u1\", role: \"editor\", department: \"Marketing\"}, 2);\nmap.set({user: \"u2\", role: \"admin\", department: \"IT\"}, 3);\n\n// Find all entries where user is \"u1\"\nconst results = map.query({user: \"u1\"});\n// [{ key: {user: \"u1\", role: \"admin\", department: \"IT\"}, value: 1 }, { key: {user: \"u1\", role: \"editor\", department: \"Marketing\"}, value: 2 }]\n```\n\nField order is fixed per instance based on first usage.\n\n## Examples\n\n### Basic Usage\n\n#### UnorderedMultiKeyMap\n```typescript\nimport { UnorderedMultiKeyMap } from 'associum';\n\n// Create a map where key order doesn't matter\nconst plantMap = new UnorderedMultiKeyMap\u003cstring[], string\u003e();\n\n// Set values\nplantMap.set([\"plant\", \"edible\", \"leafy\"], \"spinach\");\nplantMap.set([\"plant\", \"edible\", \"root\"], \"carrot\");\nplantMap.set([\"plant\", \"inedible\", \"decorative\"], \"poison ivy\");\n\n// Get values - order doesn't matter\nconsole.log(plantMap.get([\"edible\", \"plant\", \"leafy\"])); // \"spinach\"\nconsole.log(plantMap.get([\"plant\", \"root\", \"edible\"])); // \"carrot\"\n\n// Check existence\nconsole.log(plantMap.has([\"decorative\", \"plant\", \"inedible\"])); // true\n\n// Delete\nplantMap.delete([\"plant\", \"inedible\", \"decorative\"]);\nconsole.log(plantMap.has([\"plant\", \"inedible\", \"decorative\"])); // false\n```\n\n#### OrderedMultiKeyMap\n```typescript\nimport { OrderedMultiKeyMap } from 'associum';\n\n// Create a map where key order matters\nconst conceptMap = new OrderedMultiKeyMap\u003cstring[], string[]\u003e();\n\n// Set values\nconceptMap.set([\"concept\", \"mobility\", \"car\", \"manufacturers\"], [\"Mercedes\", \"BMW\", \"Toyota\", \"GM\", \"Ford\"]);\nconceptMap.set([\"concept\", \"mobility\", \"bike\", \"manufacturers\"], [\"Giant\", \"Trek\", \"Specialized\"]);\nconceptMap.set([\"concept\", \"housing\", \"apartment\", \"types\"], [\"studio\", \"1BR\", \"2BR\", \"penthouse\"]);\n\n// Get values - order matters\nconsole.log(conceptMap.get([\"concept\", \"mobility\", \"car\", \"manufacturers\"])); // [\"Mercedes\", \"BMW\", \"Toyota\", \"GM\", \"Ford\"]\nconsole.log(conceptMap.get([\"mobility\", \"concept\", \"car\", \"manufacturers\"])); // undefined\n\n// Check existence\nconsole.log(conceptMap.has([\"concept\", \"housing\", \"apartment\", \"types\"])); // true\n```\n\n#### StructuredMultiKeyMap\n```typescript\nimport { StructuredMultiKeyMap } from 'associum';\n\n// Create a map with object keys\nconst userMap = new StructuredMultiKeyMap\u003c{user: string, role: string, department: string}, number\u003e();\n\n// Set values\nuserMap.set({user: \"u1\", role: \"admin\", department: \"IT\"}, 1);\nuserMap.set({user: \"u1\", role: \"editor\", department: \"Marketing\"}, 2);\nuserMap.set({user: \"u2\", role: \"admin\", department: \"IT\"}, 3);\n\n// Get values\nconsole.log(userMap.get({user: \"u1\", role: \"admin\", department: \"IT\"})); // 1\nconsole.log(userMap.get({user: \"u1\", role: \"admin\"})); // undefined (requires full key)\n\n// Check existence\nconsole.log(userMap.has({user: \"u2\", role: \"admin\", department: \"IT\"})); // true\n```\n\n### Advanced Usage with Querying\n\n#### QueryableUnorderedMultiKeyMap\n```typescript\nimport { QueryableUnorderedMultiKeyMap } from 'associum';\n\nconst tagMap = new QueryableUnorderedMultiKeyMap\u003cstring[], string\u003e();\n\ntagMap.set([\"frontend\", \"react\", \"javascript\"], \"React\");\ntagMap.set([\"frontend\", \"vue\", \"javascript\"], \"Vue\");\ntagMap.set([\"frontend\", \"angular\", \"typescript\"], \"Angular\");\ntagMap.set([\"backend\", \"node\", \"javascript\"], \"Node.js\");\ntagMap.set([\"backend\", \"django\", \"python\"], \"Django\");\n\n// Query for frontend JavaScript frameworks\nconst jsFrameworks = tagMap.queryIndexedWith([\"frontend\", \"javascript\"]);\n// [{ key: [\"frontend\", \"react\", \"javascript\"], value: \"React\" }, { key: [\"frontend\", \"vue\", \"javascript\"], value: \"Vue\" }]\n\n// All frontend tech\nconst frontendTech = tagMap.queryIndexedWith([\"frontend\"]);\n// Includes React, Vue, Angular\n```\n\n#### QueryableOrderedMultiKeyMap\n```typescript\nimport { QueryableOrderedMultiKeyMap } from 'associum';\n\nconst pathMap = new QueryableOrderedMultiKeyMap\u003cstring[], string\u003e();\n\npathMap.set([\"api\", \"v1\", \"users\", \"GET\"], \"Get all users\");\npathMap.set([\"api\", \"v1\", \"users\", \"POST\"], \"Create user\");\npathMap.set([\"api\", \"v1\", \"users\", \":id\", \"GET\"], \"Get user by ID\");\npathMap.set([\"api\", \"v1\", \"posts\", \"GET\"], \"Get all posts\");\npathMap.set([\"api\", \"v1\", \"posts\", \"POST\"], \"Create post\");\n\n// All user endpoints (prefix match)\nconst userEndpoints = pathMap.query([\"api\", \"v1\", \"users\"]);\n// [{ key: [\"api\", \"v1\", \"users\", \"GET\"], value: \"Get all users\" }, ... , { key: [\"api\", \"v1\", \"users\", \":id\", \"GET\"], value: \"Get user by ID\" }]\n\n// All GET endpoints under v1 (positions 0,1,3)\nconst getEndpoints = pathMap.query([\"api\", \"v1\", undefined, \"GET\"]);\n// Matches [\"api\", \"v1\", \"users\", \"GET\"], [\"api\", \"v1\", \"posts\", \"GET\"] (note: :id example requires adjustment for length)\n```\n\n**Note**: For variable-length keys, wildcards work on specified positions; shorter keys may not match trailing wildcards.\n\n#### QueryableStructuredMultiKeyMap\n```typescript\nimport { QueryableStructuredMultiKeyMap } from 'associum';\n\nconst productMap = new QueryableStructuredMultiKeyMap\u003c{\n    category: string,\n    subcategory: string,\n    brand: string,\n    model: string\n}, number\u003e();\n\n// Set values\nproductMap.set({category: \"electronics\", subcategory: \"phones\", brand: \"Apple\", model: \"iPhone 13\"}, 999);\nproductMap.set({category: \"electronics\", subcategory: \"phones\", brand: \"Samsung\", model: \"Galaxy S21\"}, 899);\nproductMap.set({category: \"electronics\", subcategory: \"laptops\", brand: \"Apple\", model: \"MacBook Pro\"}, 1999);\nproductMap.set({category: \"clothing\", subcategory: \"shirts\", brand: \"Nike\", model: \"Sport Shirt\"}, 49);\n\n// Query for all Apple products\nconst appleProducts = productMap.query({brand: \"Apple\"});\n// Returns entries for iPhone 13 and MacBook Pro\n\n// Query for all electronic phones\nconst phones = productMap.query({category: \"electronics\", subcategory: \"phones\"});\n// Returns entries for iPhone 13 and Galaxy S21\n```\n## API\n\n### Map Classes\n\n#### UnorderedMultiKeyMap\u003cK extends any[], V\u003e\nA multi-key map where the order of elements in the key array doesn't matter.\n\n```typescript\nconst map = new UnorderedMultiKeyMap\u003cstring[], number\u003e();\n```\n\n#### OrderedMultiKeyMap\u003cK extends any[], V\u003e\nA multi-key map where the order of elements in the key array matters.\n\n```typescript\nconst map = new OrderedMultiKeyMap\u003cstring[], number\u003e();\n```\n\n#### StructuredMultiKeyMap\u003cK extends Record\u003cstring, any\u003e, V\u003e\nA multi-key map that uses objects as keys, where the first level of properties acts as the key.\n\n```typescript\nconst map = new StructuredMultiKeyMap\u003c{user: string, role: string}, number\u003e();\n```\n\n#### QueryableUnorderedMultiKeyMap\u003cK extends any[], V\u003e\nA queryable version of UnorderedMultiKeyMap that supports partial key lookups.\n\n```typescript\nconst map = new QueryableUnorderedMultiKeyMap\u003cstring[], number\u003e();\n```\n\n#### QueryableOrderedMultiKeyMap\u003cK extends any[], V\u003e\nA queryable version of OrderedMultiKeyMap that supports partial key lookups.\n\n```typescript\nconst map = new QueryableOrderedMultiKeyMap\u003cstring[], number\u003e();\n```\n\n#### QueryableStructuredMultiKeyMap\u003cK extends Record\u003cstring, any\u003e, V\u003e\nA queryable version of StructuredMultiKeyMap that supports partial key lookups.\n\n```typescript\nconst map = new QueryableStructuredMultiKeyMap\u003c{user: string, role: string}, number\u003e();\n```\n\n### Methods\n\nAll map classes extend the built-in JavaScript Map class and provide the following methods:\n\n#### set(key: K, value: V): this\nSets a value for the specified key.\n\n```typescript\nmap.set([\"A\", \"B\"], 1);\nmap.set({user: \"u1\", role: \"admin\"}, 1);\n```\n\n#### get(key: K): V | undefined\nReturns the value associated with the specified key, or undefined if the key doesn't exist.\n\n```typescript\nconst value = map.get([\"A\", \"B\"]);\nconst value = map.get({user: \"u1\", role: \"admin\"});\n```\n\n#### has(key: K): boolean\nReturns a boolean indicating whether an element with the specified key exists.\n\n```typescript\nconst exists = map.has([\"A\", \"B\"]);\nconst exists = map.has({user: \"u1\", role: \"admin\"});\n```\n\n#### delete(key: K): boolean\nRemoves the element with the specified key. Returns true if an element existed and has been removed, or false if the element does not exist.\n\n```typescript\nconst deleted = map.delete([\"A\", \"B\"]);\nconst deleted = map.delete({user: \"u1\", role: \"admin\"});\n```\n\n#### clear(): void\nRemoves all elements from the map.\n\n```typescript\nmap.clear();\n```\n\n#### keys(): MapIterator\u003cK\u003e\nReturns a new iterator object that contains the keys for each element in the map.\n\n```typescript\nfor (const key of map.keys()) {\n    console.log(key);\n}\n```\n\n#### entries(): MapIterator\u003c[K, V]\u003e\nReturns a new iterator object that contains [key, value] pairs for each element in the map.\n\n```typescript\nfor (const [key, value] of map.entries()) {\n    console.log(key, value);\n}\n```\n\n#### values(): MapIterator\u003cV\u003e\nReturns a new iterator object that contains the values for each element in the map.\n\n```typescript\nfor (const value of map.values()) {\n    console.log(value);\n}\n```\n\n### Query Methods (Queryable variants only)\n\n#### query(keyTemplate: Partial\u003cK\u003e): MultikeyMapQueryResult\u003cK, V\u003e[]\nReturns an array of entries that match the partial key template.\n\nFor OrderedMultiKeyMap:\n```typescript\nconst results = map.query([\"A\", undefined]); // Matches keys where first element is \"A\"\n```\n\nFor StructuredMultiKeyMap:\n```typescript\nconst results = map.query({user: \"u1\"}); // Matches keys where user property is \"u1\"\n```\n\n#### queryIndexedWith(keys: any[]): MultikeyMapQueryResult\u003cK, V\u003e[]\nReturns an array of entries that contain all of the specified key values, regardless of their position (UnorderedMultiKeyMap only).\n\n```typescript\nconst results = map.queryIndexedWith([\"A\", \"B\"]); // Matches keys containing both \"A\" and \"B\"\n```\n\n## License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felemental-mind%2Fassocium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felemental-mind%2Fassocium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felemental-mind%2Fassocium/lists"}