{"id":15592036,"url":"https://github.com/derhuerst/synchronous-autocomplete","last_synced_at":"2025-04-15T05:02:51.955Z","repository":{"id":66219604,"uuid":"116533276","full_name":"derhuerst/synchronous-autocomplete","owner":"derhuerst","description":"Fast, simple autocompletion.","archived":false,"fork":false,"pushed_at":"2022-09-23T13:32:23.000Z","size":256,"stargazers_count":13,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-15T05:02:31.428Z","etag":null,"topics":["autocomplete","autocompletion","fuzzy","search"],"latest_commit_sha":null,"homepage":"https://github.com/derhuerst/synchronous-autocomplete#synchronous-autocomplete","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/derhuerst.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","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}},"created_at":"2018-01-07T03:11:37.000Z","updated_at":"2024-06-03T21:21:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"187ba33a-8ba7-48bd-bbed-0b41a209b862","html_url":"https://github.com/derhuerst/synchronous-autocomplete","commit_stats":{"total_commits":48,"total_committers":3,"mean_commits":16.0,"dds":0.0625,"last_synced_commit":"4b7d8e141f77a15299c6f80ea22ed358090a1f38"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fsynchronous-autocomplete","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fsynchronous-autocomplete/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fsynchronous-autocomplete/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/derhuerst%2Fsynchronous-autocomplete/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/derhuerst","download_url":"https://codeload.github.com/derhuerst/synchronous-autocomplete/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249010199,"owners_count":21197797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["autocomplete","autocompletion","fuzzy","search"],"created_at":"2024-10-02T23:53:23.931Z","updated_at":"2025-04-15T05:02:51.931Z","avatar_url":"https://github.com/derhuerst.png","language":"JavaScript","funding_links":["https://github.com/sponsors/derhuerst"],"categories":[],"sub_categories":[],"readme":"# synchronous-autocomplete\n\n**Fast, simple [autocompletion](https://en.wikipedia.org/wiki/Autocomplete).** Also supports [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance)-based fuzzy search. Uses precomputed indexes to be fast.\n\n[![npm version](https://img.shields.io/npm/v/synchronous-autocomplete.svg)](https://www.npmjs.com/package/synchronous-autocomplete)\n![ISC-licensed](https://img.shields.io/github/license/derhuerst/synchronous-autocomplete.svg)\n[![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst)\n[![chat with me on Twitter](https://img.shields.io/badge/chat%20with%20me-on%20Twitter-1da1f2.svg)](https://twitter.com/derhuerst)\n\n\n## Installing\n\n```shell\nnpm install synchronous-autocomplete\n```\n\n\n## Usage\n\nLet's build a simple search for our fruit stand. We assign a `weight` property to each of them because some are bought more often and we want to push their ranking in the search results.\n\n```js\nconst items = [ {\n\tid: 'apple',\n\tname: 'Juicy sour Apple.',\n\tweight: 3\n}, {\n\tid: 'banana',\n\tname: 'Sweet juicy Banana!',\n\tweight: 2\n}, {\n\tid: 'pome',\n\tname: 'Sour Pomegranate',\n\tweight: 5\n} ]\n```\n\nLet's understand the terminology used by this tool:\n\n- *item*: A thing to search for. In our example, apple, banana and pomegranate each are an *item*.\n- *weight*: How important an *item* is.\n- *token*: A word from the fully normalized item name. For example, to find an item named `Hey There!`, you may process its name into the *tokens* `hey` \u0026 `there`.\n- *fragment*: A word from the normalized search query, which may partially match a *token*. E.g. the *fragment* `ther` (from the search query `Hey Ther`) partially matches the *token* `there`.\n- *relevance*: How well an item fits to the search query.\n- *score*: A combination of an item's *weight* and *relevance*. Used to rank search results.\n\nIn order to be as fast and disk-space-efficient as possible, `synchronous-autocomplete` requires five indexes to be prebuilt from the list of items. Check [the example code](example.js) for more details on how to build them. For our example, they would look like this:\n\n```js\nconst tokens = { // internal item IDs, by token\n\tjuicy: [0, 1],\n\tsour: [0, 3],\n\tapple: [0],\n\tsweet: [1],\n\tbanana: [1],\n\tpomegranate: [3]\n}\nconst weights = [ // item weights, by internal item ID\n\t3, // apple\n\t2, // banana\n\t5 // pome\n]\nconst nrOfTokens = [ // nr of tokens, by internal item ID\n\t3, // apple\n\t3, // banana\n\t2 // pome\n]\nconst scores = { // \"uniqueness\" of each token, by token\n\tjuicy: 2 / 3, // 2 out of 3 items have the token \"juicy\"\n\tsour: 2 / 3,\n\tapple: 1 / 3,\n\tsweet: 1 / 3,\n\tbanana: 1 / 3,\n\tpomegranate: 1 / 3\n}\n// In order to create smaller search indexes, we use numerical item IDs\n// internally and maintain a mapping to their \"real\"/original IDs.\nconst originalIds = [\n\t'apple',\n\t'banana',\n\t'pome'\n]\n```\n\nNext, we must define a function that normalizes search input into a list of *fragments*. Consider using this simple function:\n\n```js\nimport normalize from 'normalize-for-search'\n\nconst tokenize = (str) =\u003e {\n\treturn normalize(str).replace(/[^\\w\\s]/g, '').split(/\\s+/g)\n}\n```\n\nOf course, you don't have to calculate the tokens \u0026 scores! Instead, use `buildIndex` to generate the data:\n\n```js\nimport {buildIndex} from 'synchronous-autocomplete/build.js'\n\nconst index = buildIndex(tokenize, items)\n```\n\nNow, we can query our index:\n\n```js\nimport {createAutocomplete} from 'synchronous-autocomplete'\n\nconst autocomplete = createAutocomplete(index, tokenize)\n\nautocomplete('bana')\n// [ {\n// \trelevance: 0.6666665555555555,\n// \tscore: 0.8399472266053544,\n// \tweight: 2,\n// } ]\n\nautocomplete('sour')\n// [ {\n// \tid: 'pome',\n// \trelevance: 1.8333335,\n// \tscore: 3.134956187236602,\n// \tweight: 5,\n// }, {\n// \tid: 'apple',\n// \trelevance: 1.2222223333333333,\n// \tscore: 1.762749635070118,\n// \tweight: 3,\n// } ]\n\nautocomplete('aplle', 3, true) // note the typo\n// [ {\n// \tid: 'apple',\n// \trelevance: 0.22222216666666667,\n// \tscore: 0.3204998243877813,\n// \tweight: 3,\n// } ]\n```\n\n\n## API\n\n```js\nconst index = buildIndex(tokenize, items)\nconst {tokens, scores, weights, nrOfTokens, originalIds} = index\n```\n\n- `tokenize` must be a function that, given a search query, returns an array of *fragments*.\n- `items` must be an array of objects, each with `id`, `name` \u0026 `weight`.\n\n```js\nconst autocomplete = createAutocomplete(index, tokenize)\nautocomplete(query, limit = 6, fuzzy = false, completion = true)\n```\n\n- `tokens` must be an object with an array of internal *item* IDs per *token*.\n- `scores` must be an object with a *token* score per *token*.\n- `weights` must be an array with an *item* weight per internal *item* ID.\n- `nrOfTokens` must be an array with the number of *tokens* per internal *item* ID.\n- `originalIds` must be an array with the (real) *item* ID per internal *item* ID.\n- `tokenize` is the same as with `buildIndex()`.\n\n\n## Storing the index as protocol buffer\n\n[Protocol buffers](https://developers.google.com/protocol-buffers/) (a.k. *protobuf*s) are a compact binary format for structured data serialization.\n\n```js\nimport {encodeIndex} from 'synchronous-autocomplete/encode.js'\nimport {writeFileSync, readFileSync} from 'node:fs'\n\n// encode \u0026 write the index\nconst encoded = encodeIndex(index)\nwriteFileSync('index.pbf', encoded)\n\n// read \u0026 decode the index\nconst decoded = decode(readFileSync('index.pbf'))\n```\n\n\n## Contributing\n\nIf you have a question or have difficulties using `synchronous-autocomplete`, please double-check your code and setup first. If you think you have found a bug or want to propose a feature, refer to [the issues page](https://github.com/derhuerst/synchronous-autocomplete/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderhuerst%2Fsynchronous-autocomplete","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fderhuerst%2Fsynchronous-autocomplete","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fderhuerst%2Fsynchronous-autocomplete/lists"}