{"id":30702990,"url":"https://github.com/mylofi/local-data-lock","last_synced_at":"2025-09-02T16:03:38.132Z","repository":{"id":251453280,"uuid":"835010716","full_name":"mylofi/local-data-lock","owner":"mylofi","description":"Protect local-first app data with encryption/decryption key secured in Webauthn (biometric) passkey","archived":false,"fork":false,"pushed_at":"2025-04-05T18:58:46.000Z","size":115,"stargazers_count":149,"open_issues_count":3,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-08-29T16:03:05.123Z","etag":null,"topics":["encryption","local-first"],"latest_commit_sha":null,"homepage":"https://mylofi.github.io/local-data-lock/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mylofi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2024-07-29T01:11:10.000Z","updated_at":"2025-08-05T09:42:51.000Z","dependencies_parsed_at":"2024-08-19T02:26:35.423Z","dependency_job_id":"70236a46-7355-4a84-9a23-975ce8fb2079","html_url":"https://github.com/mylofi/local-data-lock","commit_stats":null,"previous_names":["mylofi/local-data-secure","mylofi/local-data-lock"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/mylofi/local-data-lock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mylofi%2Flocal-data-lock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mylofi%2Flocal-data-lock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mylofi%2Flocal-data-lock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mylofi%2Flocal-data-lock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mylofi","download_url":"https://codeload.github.com/mylofi/local-data-lock/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mylofi%2Flocal-data-lock/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273309572,"owners_count":25082545,"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","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["encryption","local-first"],"created_at":"2025-09-02T16:02:14.644Z","updated_at":"2025-09-02T16:03:38.119Z","avatar_url":"https://github.com/mylofi.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Local Data Lock\n\n[![npm Module](https://badge.fury.io/js/@lo-fi%2Flocal-data-lock.svg)](https://www.npmjs.org/package/@lo-fi/local-data-lock)\n[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)\n\n**Local Data Lock** provides a simple utility interface for encrypting and decrypting (and producing digital signatures for) local-first application data using a keypair stored and protected by [WebAuthn](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) (biometric passkeys), via the [**WebAuthn Local Client** library](https://github.com/mylofi/webauthn-local-client) -- no servers required!\n\n```js\nvar lockKey = await getLockKey({ .. });\n\nvar encData = await lockData({ hello: \"World!\" },lockKey);\n// \"aG4/z...\"\n\nawait unlockData(encData,lockKey);\n// { hello: \"World!\" }\n```\n\n----\n\n[Library Tests (Demo)](https://mylofi.github.io/local-data-lock/)\n\n----\n\n## Overview\n\nThis library can securely lock (encrypt) data in the local client, with no servers needed. The encrypted data *might also be* stored locally on the client device; for this purpose, please strongly consider using the [**Local Vault** library](https://github.com/mylofi/local-vault).\n\nHowever, the encrypted data (by default, represented as a base64 encoded string) might be transmitted and stored elsewhere, such as on an app's servers. The cryptographic keypair may also be used for digital signatures to verify secure data transmission, using the `signData()` and `verifySignature()` methods.\n\nThis cryptographic keypair is protected locally on the user's device in a biometric passkey; the user can easily unlock (decrypt) their data, or verify a received data transmission from their other device, by presenting a biometric factor to retrieve the keypair.\n\n### How does it work?\n\nOne direct dependency of this library is [**WebAuthn-Local-Client**](https://github.com/mylofi/webauthn-local-client), which utilizes the browser's [WebAuthn API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) for managing biometric passkeys entirely in the local client (zero servers).\n\nThe cryptographic keypair the library generates, is attached securely to a passkey, which is protected by the authenticator/device. The library also stores meta-data entries for these passkeys -- specifically, the public-key info for the passkey itself, which is necessary for **verifying** subsequent passkey authentication responses.\n\n**NOTE:** This public-key for a passkey is *NOT* in any way related to the crytographic keypair, which **Local Data Lock** does not persist anywhere on the device (only kept in memory). It's *only* used for authentication verification -- protecting against MitM attacks against the authenticator. Verification defaults *on*, but can be skipped by passing `verify: false` as an option to the `getLockKey()` method.\n\nThe client-side storage location (for passkey account metadata) that **Local Data Lock** uses, is [configurable (defaults to IndexedDB)](#configuring-client-side-storage).\n\n### Security vs Convenience\n\nYour application accesses the cryptographic keypair via `getLockKey()`, and may optionally decide if you want to persist it somewhere -- for more convenience/ease-of-use, as compared to asking the user to re-authenticate their passkey on each usage. But you are cautioned to be very careful in such decisions, striking an appropriate balance between security and convenience.\n\nIf the design is *too convenient* (e.g., once-forever logins), it's likely to be insecure (and the user may not realize it!). If the design is *too secure*, it's likely to have so much UX friction that users won't use it (or your app).\n\nTo assist in making these difficult tradeoffs, **Local Data Lock** internally caches the cryptographic keypair after a successful passkey authentication, and keeps it in memory (assuming no page refresh) for a period of time (by default, 30 minutes); in such a setup, the user won't need to re-authenticate their passkey more often than once per 30 minutes. This default time threshold can also be adjusted, from 0ms upward, [using `configure({ cacheLifetime: .. })`](#change-lock-key-cache-lifetime).\n\nYou are strongly encouraged **NOT** to persist the encryption/decryption key, and to instead rely on this time-based caching mechanism.\n\n## Deployment / Import\n\n```cmd\nnpm install @lo-fi/local-data-lock\n```\n\nThe [**@lo-fi/local-data-lock** npm package](https://npmjs.com/package/@lo-fi/local-data-lock) includes a `dist/` directory with all files you need to deploy **Local Data Lock** (and its dependencies) into your application/project.\n\n**Note:** If you obtain this library via git instead of npm, you'll need to [build `dist/` manually](#re-building-dist) before deployment.\n\n* **USING A WEB BUNDLER?** (Astro, Vite, Webpack, etc) Use the `dist/bundlers/*` files and see [Bundler Deployment](BUNDLERS.md) for instructions.\n\n* Otherwise, use the `dist/auto/*` files and see [Non-Bundler Deployment](NON-BUNDLERS.md) for instructions.\n\n## `WebAuthn` Supported?\n\nTo check if `WebAuthn` API and functionality is supported on the device, consult the `supportsWebAuthn` exported boolean.\n\nAdditionally, **Local Data Lock** requires the authenticator to support [\"user verification\"](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/isUserVerifyingPlatformAuthenticatorAvailable_static). Thus, a separate exported boolean called `supportsWAUserVerification` should also be checked:\n\n```js\nimport { supportsWebAuthn, supportsWAUserVerification } from \"..\";\n\nif (supportsWebAuthn \u0026\u0026 supportsWAUserVerification) {\n    // welcome to the future, without passwords!\n}\nelse {\n    // sigh, use fallback authentication, like\n    // icky passwords :(\n}\n```\n\n## Registering a local account (and lock-key keypair)\n\nA \"local account\" is merely a collection of one or more passkeys that are all holding the same encryption/decryption keypair. There's no limit on the number of \"local account\" passkey collections on a device (other than device storage limits).\n\nTo register a new local account:\n\n```js\nimport { getLockKey } from \"..\";\n\nvar key = await getLockKey({ addNewPasskey: true });\n```\n\nThe returned keypair result will also include a `localIdentity` property, with a unique ID (`string` value) for the local account. This local account ID should be stored by your application (in local-storage, cookie, etc):\n\n```js\nvar currentAccountID = key.localIdentity;\n```\n\n### Lock-Key Value Format\n\nOther than reading the `localIdentity` property, the lock-key object should be **treated opaquely**, meaning that you don't rely on its structure, don't make any changes to it, etc.\n\nIt contains binary data for the keypairs, in the form of various `Uint8Array` values. These types of data are not, as-is, particularly serialization safe (JSON, etc), for the purposes of storage or transmission. To serialize these binary-array values (and unserialize them later), you can use the `toBase64String()` / `fromBase64String()` utilities exported on the library's API.\n\nFor example, to serialize a lock-key for JSON-safe storage, or transmission:\n\n```js\nvar serializedKey = Object.fromEntries(\n    Object.entries(key)\n    .map(([ prop, value ]) =\u003e [\n        prop,\n        (\n            value instanceof Uint8Array \u0026\u0026\n            value.buffer instanceof ArrayBuffer\n        ) ?\n            toBase64String(value) :\n            value\n    ])\n);\n```\n\nAnd to deserialize:\n\n```js\nvar key = Object.fromEntries(\n    Object.entries(serializedKey)\n    .map(([ prop, value ]) =\u003e [\n        prop,\n        (\n            typeof value == \"string\" \u0026\u0026\n\n            // padded base64 encoding of Uint8Array(32)\n            // will be at least 44 characters long\n            value.length \u003e= 44\n        ) ?\n            fromBase64String(value) :\n            value\n    ])\n);\n```\n\n### Obtaining the keypair from existing account/passkey\n\nIf the `currentAccountID` (as shown above) is available, it should be used in subsequent calls to `getLockKey()` when re-obtaining the encryption/decryption keypair from the existing passkey:\n\n```js\nvar key = await getLockKey({ localIdentitity: currentAccountID });\n```\n\nIf you don't have (or the application loses) an account ID, call `listLocalIdentities()` (async) to receive an array of all registed local account IDs.\n\nAlternatively, `getLockKey()` can be called WITHOUT either `localIdentity` or `addNewPasskey` options, in which case the device will prompt the user to select a discoverable passkey (if supported). If the user chooses a passkey that matches one of the registered local accounts, the keypair (and its `localIdentity` account ID property) will be returned.\n\n### Adding alternate passkeys to an account\n\nUsers may prefer a more robust security setup (less chance of identity/data loss), by registering more than one passkey (for example, FaceID + TouchID) -- each holds a copy of the encryption/decryption keypair.\n\nTo prompt for adding a new passkey to an existing local account:\n\n```js\n/*var key =*/ await getLockKey({\n    localIdentitity: currentAccountID,\n    addNewPasskey: true\n});\n```\n\n### Change lock-key cache lifetime\n\nTo change the default (30 minutes) lifetime for caching the encryption/decryption keypair (extracted from passkey authentication):\n\n```js\nimport { configure } from \"..\";\n\n// change default lifetime to 5 minutes\nconfigure({ cacheLifetime: 5 * 60 * 1000 });\n```\n\n### Manually cache a lock-key\n\nTo manually cache a lock-key silently (without passkey prompt):\n\n```js\nimport { cacheLockKey } from \"..\";\n\ncacheLockKey(existingLockKey);\n```\n\n**WARNING:** This is generally not recommended; it's provided only for advanced use-cases, such as a lock-key being preserved (temporarily) to approximate a \"login session\" across multiple page loads. Avoid this approach unless you're certain you need it, as it can degrade some of the security assurances behind the design of this library.\n\n### Clear the lock-key cache\n\nTo clear a cache entry (effectively, \"logging out\"):\n\n```js\nimport { clearLockKeyCache } from \"..\";\n\nclearLockKeyCache(currentAccountID);\n```\n\nTo clear *all* cache entries, omit the local account ID:\n\n```js\nclearLockKeyCache();\n```\n\n### Removing a local account\n\nTo remove a local account (from device local storage), thereby discarding associated passkey public-key info (necessary for verifying passkey authentication responses):\n\n```js\nimport { removeLocalAccount } from \"..\";\n\nawait removeLocalAccount(currentAccountID);\n```\n\n### Configuring Passkeys\n\nThere are several options available to the `getLockKey()` method, to customize the information used when registering passkeys:\n\n```js\nvar key = await getLockKey({\n    addNewPasskey: true,  // or \"localIdentity: ..\" + \"resetLockKey: true\"\n\n    /* passkey configuration options: */\n    username: \"a-local-username\",\n    displayName: \"A Local Username\",\n    relyingPartyID: \"myappdomain.tld\",\n    relyingPartyName: \"My App\"\n});\n```\n\nAll of these passkey configuration options are string values, passed along to the `WebAuthn` API subsystem; they affect how the device saves the passkey once registered, and further verifies its usage later.\n\nThe `username` (default: `\"local-user\"`) and `displayName` (default: `\"Local User\"`) options are information the system uses in its modal dialogs to indicate to the user which passkey they are using in authentication operations; this library only preserves them for non-functional, metadata/debugging purposes. Ideally, your application should prompt the user for these values before initial passkey registration, or auto-generate values that will make sense to the user.\n\n**Note:** The values don't strictly need to be unique, but if a user registers multiple passkeys with the same username/display-name, it may be confusing to them in future authentications.\n\nThe `relyingPartyID` should be the canonical hostname of the web application, or matching an application's package ID (e.g., `com.app.my-favorite`) if it's an app-store installable application. Likewise, `relyingPartyName` (`My Favorite App`) should be a human-friendly name for your application that users will recognize; some devices will display this value in the passkey modal dialogs along with the `username` / `displayName` values.\n\nThree of the options (`username`, `displayName`, and `relyingPartName`) are *only* valid when creating a new passkey, in either `addNewPasskey: true` or `resetLockKey: true` modes; the `relyingPartyID` option can/should be used in all `getLockKey()` calls.\n\n### Canceling Pending Lock-Key Request\n\nIf a call to `getLockKey(..)` requires a passkey (re)authentication, there may be a substantial delay while the user is navigating the system prompts. Calling `getLockKey()` a subsequent time, while another `getLockKey()` is currently pending, will abort that previous call -- and should cancel any open system dialogs the user is interacting with.\n\nHowever, you may want to cancel a currently pending `getLockKey()` *without* having to call `getLockKey()` again, for example based on a timeout if authentication is taking too long. To be able to cancel this asynchronous operation, pass in an [`AbortController.signal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal) instance, as a `signal` option to `getLockKey()`:\n\n```js\nvar cancelToken = new AbortController();\nvar key = await getLockKey({\n    /* .. */,\n    signal: cancelToken.signal\n});\n\n// elsewhere:\ncancelToken.abort(\"Taking too long!\");\n```\n\nAborting a cancellation token while the `getLockKey()` is still pending (i.e., at an `await`), will by default throw an exception at that point. However, in some UX flows -- such as intending to call `getLockKey()` again with different options -- you may want to silently cancel that currently pending `getLockKey()` *without* throwing an exception.\n\nPass the `resetAbortReason` value to the `abort()` call:\n\n```js\nimport { resetAbortReason, getLockKey } from \"..\";\n\nvar key = await getLockKey({\n    /* .. */,\n    signal: cancelToken.signal\n});\n\n// elsewhere:\ncancelToken.abort(resetAbortReason);\n```\n\nThe current `getLockKey()` will now cleanly and silently cancel, **and its return value will be `undefined`**.\n\n## Encrypt some data\n\nOnce a keypair has been obtained, to encrypt application data:\n\n```js\nimport { lockData } from \"..\";\n\nvar encData = lockData(someData,key);\n```\n\nThe `lockData()` method will auto-detect the type of `someData`, so most any value (even a JSON-compatible object) is suitable to pass in.\n\n**Note:** If `someData` is already an array-buffer or typed-array, no transformation is necessary. If it's an object, a JSON string serialization is attempted. Otherwise, a string coercion is performed on the value. Regardless, the resulting string is then converted to a typed-array representation for encryption.\n\nThe default representation in the return value (`encData`) will be a base64 encoded string (suitable for storing in LocalStorage, transmitting in JSON, etc). If you prefer the `Uint8Array` binary representation:\n\n```js\nvar encDataBuffer = lockData(\n    someData,\n    key,\n    { outputFormat: \"raw\" }     // instead of \"base64\"\n);\n// Uint8Array[ .. ]\n```\n\n## Decrypt some data\n\nWith the keypair and a previously encrypted data value (from `lockData()`), decryption can be performed:\n\n```js\nimport { unlockData } from \"..\";\n\nvar data = unlockData(encData,key);\n```\n\nThe `unlockData()` method will auto-detect the type of `encData` (either the base64 string encoding, or the `Uint8Array` binary encoding).\n\nBy default, the decrypted data is assumed to be a utf-8 encoded string, with a JSON serialized value to be parsed. But if you are not encrypting/decrypting JSON-compatible data objects, set the `parseJSON: false` option:\n\n```js\nvar dataStr = unlockData(\n    encData,\n    key,\n    { parseJSON: false }\n);\n```\n\nIf you want the raw `Uint8Array` binary representation returned, instead of the utf-8 string:\n\n```js\nvar dataBuffer = unlockData(\n    encData,\n    key,\n    { outputFormat: \"raw\" }     // instead of \"utf8\" (or \"utf-8\")\n);\n```\n\n## Signing data and verifying signatures\n\nIn addition to encryption and decryption, lock-keys can be used for producing and verifying (detached) digital signatures.\n\nTo sign a piece of data:\n\n```js\nimport { signData } from \"..\";\n\nvar signature = signData(someData,lockKey);       // \"Zt83H..\"\n```\n\n`someData` can be any string or a JSON-serializable object; any other primitive value will be treated as a string. `lockKey` only strictly requires a `privateKey` property (from a full lock-key value).\n\nThe default representation in the return value will be a base64 encoded string (suitable for storing in LocalStorage, transmitting in JSON, etc). If you prefer the `Uint8Array` binary representation:\n\n```js\nvar signature = signData(\n    someData,\n    key,\n    { outputFormat: \"raw\" }     // instead of \"base64\"\n);\n// Uint8Array[ .. ]\n```\n\nTo verify a signature:\n\n```js\nimport { verifySignature } from \"..\";\n\nverifySignature(someData,lockKey,signature);    // true (or false!)\n```\n\nObviously, `someData` needs to hold the exact same data as was passed to `signData()` previously. `lockKey` only strictly requires a `publicKey` property (from a full lock-key value). `signature` may be a string (assumed as the base64 encoded representation) or the raw `Uint8Array` binary representation.\n\nThe function returns `true` / `false` for verification -- or throws an exception if the data, key, or signature are malformed/invalid.\n\n## Deriving an encryption/decryption key\n\nIf you want to manually derive the keypair information from a secure random seed value (`Uint8Array` with enough random entropy):\n\n```js\nimport { deriveLockKey } from \"..\";\n\nvar key = deriveLockKey(seedValue);\n```\n\nThis keypair is suitable to use with `lockData()` and `unlockData()` methods. However, the keypair returned WILL NOT be associated with (or protected by) a device passkey; it receives no entry in the device's local-storage and will not be returned from `getLockKey()`. The intent of this library is to rely on passkeys, so you are encouraged *not* to pursue this manual approach unless strictly necessary.\n\nFurther, to generate a suitable cryptograhpically random `seedValue`:\n\n```js\nimport { generateEntropy } from \"..\";\n\nvar seedValue = generateEntropy(32);\n```\n\n**Note:** The encryption/decryption keypairs this library uses (via underlying libsodium methods) require specifically 32 bytes (256 bits) of entropy for the seed value.\n\nThe returned `seedValue` will be a raw `Uint8Array` binary typed-array.\n\n## Importing an encryption/decryption key\n\nIf you have a lock-key keypair generated by **Local Vault** / **Local Data Lock**, either from manually calling [`deriveLockKey()`](#deriving-an-encryptiondecryption-key), or from a previous call to `getLockKey()` (even on another device!), you *can* choose to import it to a local account.\n\nWhen registering a new local-account:\n\n```js\nvar key = await getLockKey({\n    addNewPasskey: true,\n    useLockKey: existingLockKey\n});\nkey === existingLockKey;        // true\n```\n\nWhen resetting the key on an existing local-account:\n\n```js\nvar key = await getLockKey({\n    localIdentitity: currentAccountID,\n    resetLockKey: true,\n    useLockKey: existingLockKey\n});\nkey === existingLockKey;        // true\n```\n\n**Warning:** You should generally let **Local Data Lock** internally generate and manage the lock-keys on local-accounts, and should not store (or transmit) these lock-keys in a way that degrades the security promises of this library. Be very careful if you are using the library in a way that you need to use `useLockKey`, and make sure it's absolutely necessary.\n\n## Configuring client-side storage\n\nBy default, **Local Data Lock** will store its [passkey account metadata](#how-does-it-work) in [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), with the **Storage** library's `idb` storage adapter.\n\nHowever, you may wish to configure to use one of the other client storage mechanisms:\n\n```js\nimport { configure } from \"..\";\n\n// override default storage to Local-Storage\n// (instead of IndexedDB)\nconfigure({ accountStorage: \"local-storage\" });\n```\n\n**WARNING:** If you need to configure `accountStorage` as shown, make sure to do so just once (per page load), *before* any other calls to any other **Local Data Lock** methods, to prevent any confusion of where the passkey account metadata is held.\n\nThe corresponding (or default) **Storage** adapter will be loaded dynamically (i.e., from `\"@byojs/storage/*\"`), at the first need for **Local Data Lock** to access or update its passkey account metadata storage.\n\n### Manually specifying custom storage adapter\n\nIf you want to use a custom storage adapter -- one *not* [provided by **Storage**](https://github.com/byojs/storage?tab=readme-ov-file#client-side-storage-adapters) -- pass the storage adapter instance directly to `configure()`:\n\n```js\nimport { configure } from \"..\";\n\nconfigure({ accountStorage: customStorageAdapter });\n```\n\n**NOTE:** The adapter instance (`customStorageAdapter`) *must* conform to the [storage-adapter API as defined by **Storage**](https://github.com/byojs/storage?tab=readme-ov-file#storage-api).\n\n## WebAuthn-Local-Client Utilities\n\nThe following utilities are re-exported from the [`WebAuthn-Local-Client` dependency](https://github.com/mylofi/webauthn-local-client):\n\n* `toBase64String()` - from `Uint8Array` to string in base64 encoding\n* `fromBase64String()` - from base64 encoded string to `Uint8Array`\n* `toUTF8String()` - from `Uint8Array` to string in utf-8 string\n* `fromUTF8String()` - from utf-8 string to `Uint8Array`\n* `packPublicKeyJSON()` / `unpackPublicKeyJSON()` -- these are specifically for a passkey entry's `publicKey` property, when being stored/retrieved from `localStroage`\n\nThese utilities are helpful when dealing with converting values between various representations, especially for storing values (i.e., `localStorage`, etc).\n\n## Re-building `dist/*`\n\nIf you need to rebuild the `dist/*` files for any reason, run:\n\n```cmd\n# only needed one time\nnpm install\n\nnpm run build:all\n```\n\n## Tests\n\nSince the library involves non-automatable behaviors (requiring user intervention in browser), an automated unit-test suite is not included. Instead, a simple interactive browser test page is provided.\n\nVisit [`https://mylofi.github.io/local-data-lock/`](https://mylofi.github.io/local-data-lock/), and follow instructions in-page from there to perform the interactive tests.\n\n### Run Locally\n\nTo instead run the tests locally, first make sure you've [already run the build](#re-building-dist), then:\n\n```cmd\nnpm test\n```\n\nThis will start a static file webserver (no server logic), serving the interactive test page from `http://localhost:8080/`; visit this page in your browser to perform tests.\n\nBy default, the `test/test.js` file imports the code from the `src/*` directly. However, to test against the `dist/auto/*` files (as included in the npm package), you can modify `test/test.js`, updating the `/src` in its `import` statement to `/dist` (see the import-map in `test/index.html` for more details).\n\n## License\n\n[![License](https://img.shields.io/badge/license-MIT-a1356a)](LICENSE.txt)\n\nAll code and documentation are (c) 2024 Kyle Simpson and released under the [MIT License](http://getify.mit-license.org/). A copy of the MIT License [is also included](LICENSE.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmylofi%2Flocal-data-lock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmylofi%2Flocal-data-lock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmylofi%2Flocal-data-lock/lists"}