[
  {
    "path": ".commitlintrc.json",
    "content": "{ \"extends\": [\"@commitlint/config-conventional\"] }\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": \"@typicode\",\n  \"env\":{\n    \"browser\": true,\n    \"node\": true\n  }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.ts linguist-language=JavaScript\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: typicode"
  },
  {
    "path": ".github/workflows/node.js.yml",
    "content": "name: Node.js CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [18.x, 20.x]\n\n    steps:\n      - uses: actions/checkout@v3\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: npm ci\n      - run: npm run build --if-present\n      - run: npm test\n"
  },
  {
    "path": ".gitignore",
    "content": "lib\nnode_modules\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/commit-msg",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx --no-install commitlint --edit \"$1\"\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run lint\nnpm test\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "By contributing, you agree to release your modifications under the MIT\nlicense (see the file LICENSE-MIT).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 typicode\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# lowdb [![](http://img.shields.io/npm/dm/lowdb.svg?style=flat)](https://www.npmjs.org/package/lowdb) [![Node.js CI](https://github.com/typicode/lowdb/actions/workflows/node.js.yml/badge.svg)](https://github.com/typicode/lowdb/actions/workflows/node.js.yml)\n\n> Simple to use type-safe local JSON database 🦉\n> \n> If you know JavaScript, you know how to use lowdb.\n\nRead or create `db.json`\n\n```js\nconst db = await JSONFilePreset('db.json', { posts: [] })\n```\n\nUse plain JavaScript to change data\n\n```js\nconst post = { id: 1, title: 'lowdb is awesome', views: 100 }\n\n// In two steps\ndb.data.posts.push(post)\nawait db.write()\n\n// Or in one\nawait db.update(({ posts }) => posts.push(post))\n```\n\n```js\n// db.json\n{\n  \"posts\": [\n    { \"id\": 1, \"title\": \"lowdb is awesome\", \"views\": 100 }\n  ]\n}\n```\n\nIn the same spirit, query using native `Array` functions:\n\n```js\nconst { posts } = db.data\n\nposts.at(0) // First post\nposts.filter((post) => post.title.includes('lowdb')) // Filter by title\nposts.find((post) => post.id === 1) // Find by id\nposts.toSorted((a, b) => a.views - b.views) // Sort by views\n```\n\nIt's that simple. `db.data` is just a JavaScript object, no magic.\n\n## Sponsors\n\n<br>\n<br>\n\n<p align=\"center\">\n  <a href=\"https://mockend.com/\" target=\"_blank\">\n    <img src=\"https://jsonplaceholder.typicode.com/mockend.svg\" height=\"70px\">\n  </a>\n</p>\n\n<br>\n<br>\n\n[Become a sponsor and have your company logo here](https://github.com/sponsors/typicode) 👉 [GitHub Sponsors](https://github.com/sponsors/typicode)\n\n## Features\n\n- **Lightweight**\n- **Minimalist**\n- **TypeScript**\n- **Plain JavaScript**\n- Safe atomic writes\n- Hackable:\n  - Change storage, file format (JSON, YAML, ...) or add encryption via [adapters](#adapters)\n  - Extend it with lodash, ramda, ... for super powers!\n- Automatically switches to fast in-memory mode during tests\n\n## Install\n\n```sh\nnpm install lowdb\n```\n\n## Usage\n\n_Lowdb is a pure ESM package. If you're having trouble using it in your project, please [read this](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)._\n\n```js\nimport { JSONFilePreset } from 'lowdb/node'\n\n// Read or create db.json\nconst defaultData = { posts: [] }\nconst db = await JSONFilePreset('db.json', defaultData)\n\n// Update db.json\nawait db.update(({ posts }) => posts.push('hello world'))\n\n// Alternatively you can call db.write() explicitely later\n// to write to db.json\ndb.data.posts.push('hello world')\nawait db.write()\n```\n\n```js\n// db.json\n{\n  \"posts\": [ \"hello world\" ]\n}\n```\n\n### TypeScript\n\nYou can use TypeScript to check your data types.\n\n```ts\ntype Data = {\n  messages: string[]\n}\n\nconst defaultData: Data = { messages: [] }\nconst db = await JSONPreset<Data>('db.json', defaultData)\n\ndb.data.messages.push('foo') // ✅ Success\ndb.data.messages.push(1) // ❌ TypeScript error\n```\n\n### Lodash\n\nYou can extend lowdb with Lodash (or other libraries). To be able to extend it, we're not using `JSONPreset` here. Instead, we're using lower components.\n\n```ts\nimport { Low } from 'lowdb'\nimport { JSONFile } from 'lowdb/node'\nimport lodash from 'lodash'\n\ntype Post = {\n  id: number\n  title: string\n}\n\ntype Data = {\n  posts: Post[]\n}\n\n// Extend Low class with a new `chain` field\nclass LowWithLodash<T> extends Low<T> {\n  chain: lodash.ExpChain<this['data']> = lodash.chain(this).get('data')\n}\n\nconst defaultData: Data = {\n  posts: [],\n}\nconst adapter = new JSONFile<Data>('db.json')\n\nconst db = new LowWithLodash(adapter, defaultData)\nawait db.read()\n\n// Instead of db.data use db.chain to access lodash API\nconst post = db.chain.get('posts').find({ id: 1 }).value() // Important: value() must be called to execute chain\n```\n\n### CLI, Server, Browser and in tests usage\n\nSee [`src/examples/`](src/examples) directory.\n\n## API\n\n### Presets\n\nLowdb provides four presets for common cases.\n\n- `JSONFilePreset(filename, defaultData)`\n- `JSONFileSyncPreset(filename, defaultData)`\n- `LocalStoragePreset(name, defaultData)`\n- `SessionStoragePreset(name, defaultData)`\n\nSee [`src/examples/`](src/examples) directory for usage.\n\nLowdb is extremely flexible, if you need to extend it or modify its behavior, use the classes and adapters below instead of the presets.\n\n### Classes\n\nLowdb has two classes (for asynchronous and synchronous adapters).\n\n#### `new Low(adapter, defaultData)`\n\n```js\nimport { Low } from 'lowdb'\nimport { JSONFile } from 'lowdb/node'\n\nconst db = new Low(new JSONFile('file.json'), {})\nawait db.read()\nawait db.write()\n```\n\n#### `new LowSync(adapterSync, defaultData)`\n\n```js\nimport { LowSync } from 'lowdb'\nimport { JSONFileSync } from 'lowdb/node'\n\nconst db = new LowSync(new JSONFileSync('file.json'), {})\ndb.read()\ndb.write()\n```\n\n### Methods\n\n#### `db.read()`\n\nCalls `adapter.read()` and sets `db.data`.\n\n**Note:** `JSONFile` and `JSONFileSync` adapters will set `db.data` to `null` if file doesn't exist.\n\n```js\ndb.data // === null\ndb.read()\ndb.data // !== null\n```\n\n#### `db.write()`\n\nCalls `adapter.write(db.data)`.\n\n```js\ndb.data = { posts: [] }\ndb.write() // file.json will be { posts: [] }\ndb.data = {}\ndb.write() // file.json will be {}\n```\n\n#### `db.update(fn)`\n\nCalls `fn()` then `db.write()`.\n\n```js\ndb.update((data) => {\n  // make changes to data\n  // ...\n})\n// files.json will be updated\n```\n\n### Properties\n\n#### `db.data`\n\nHolds your db content. If you're using the adapters coming with lowdb, it can be any type supported by [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).\n\nFor example:\n\n```js\ndb.data = 'string'\ndb.data = [1, 2, 3]\ndb.data = { key: 'value' }\n```\n\n## Adapters\n\n### Lowdb adapters\n\n#### `JSONFile` `JSONFileSync`\n\nAdapters for reading and writing JSON files.\n\n```js\nimport { JSONFile, JSONFileSync } from 'lowdb/node'\n\nnew Low(new JSONFile(filename), {})\nnew LowSync(new JSONFileSync(filename), {})\n```\n\n#### `Memory` `MemorySync`\n\nIn-memory adapters. Useful for speeding up unit tests. See [`src/examples/`](src/examples) directory.\n\n```js\nimport { Memory, MemorySync } from 'lowdb'\n\nnew Low(new Memory(), {})\nnew LowSync(new MemorySync(), {})\n```\n\n#### `LocalStorage` `SessionStorage`\n\nSynchronous adapter for `window.localStorage` and `window.sessionStorage`.\n\n```js\nimport { LocalStorage, SessionStorage } from 'lowdb/browser'\nnew LowSync(new LocalStorage(name), {})\nnew LowSync(new SessionStorage(name), {})\n```\n\n### Utility adapters\n\n#### `TextFile` `TextFileSync`\n\nAdapters for reading and writing text. Useful for creating custom adapters.\n\n#### `DataFile` `DataFileSync`\n\nAdapters for easily supporting other data formats or adding behaviors (encrypt, compress...).\n\n```js\nimport { DataFile } from 'lowdb/node'\nnew DataFile(filename, {\n  parse: YAML.parse,\n  stringify: YAML.stringify\n})\nnew DataFile(filename, {\n  parse: (data) => { decypt(JSON.parse(data)) },\n  stringify: (str) => { encrypt(JSON.stringify(str)) }\n})\n```\n\n### Third-party adapters\n\nIf you've published an adapter for lowdb, feel free to create a PR to add it here.\n\n### Writing your own adapter\n\nYou may want to create an adapter to write `db.data` to YAML, XML, encrypt data, a remote storage, ...\n\nAn adapter is a simple class that just needs to expose two methods:\n\n```js\nclass AsyncAdapter {\n  read() {\n    /* ... */\n  } // should return Promise<data>\n  write(data) {\n    /* ... */\n  } // should return Promise<void>\n}\n\nclass SyncAdapter {\n  read() {\n    /* ... */\n  } // should return data\n  write(data) {\n    /* ... */\n  } // should return nothing\n}\n```\n\nFor example, let's say you have some async storage and want to create an adapter for it:\n\n```js\nimport { Low } from 'lowdb'\nimport { api } from './AsyncStorage'\n\nclass CustomAsyncAdapter {\n  // Optional: your adapter can take arguments\n  constructor(args) {\n    // ...\n  }\n\n  async read() {\n    const data = await api.read()\n    return data\n  }\n\n  async write(data) {\n    await api.write(data)\n  }\n}\n\nconst adapter = new CustomAsyncAdapter()\nconst db = new Low(adapter, {})\n```\n\nSee [`src/adapters/`](src/adapters) for more examples.\n\n#### Custom serialization\n\nTo create an adapter for another format than JSON, you can use `TextFile` or `TextFileSync`.\n\nFor example:\n\n```js\nimport { Adapter, Low } from 'lowdb'\nimport { TextFile } from 'lowdb/node'\nimport YAML from 'yaml'\n\nclass YAMLFile {\n  constructor(filename) {\n    this.adapter = new TextFile(filename)\n  }\n\n  async read() {\n    const data = await this.adapter.read()\n    if (data === null) {\n      return null\n    } else {\n      return YAML.parse(data)\n    }\n  }\n\n  write(obj) {\n    return this.adapter.write(YAML.stringify(obj))\n  }\n}\n\nconst adapter = new YAMLFile('file.yaml')\nconst db = new Low(adapter, {})\n```\n\n## Limits\n\nLowdb doesn't support Node's cluster module.\n\nIf you have large JavaScript objects (`~10-100MB`) you may hit some performance issues. This is because whenever you call `db.write`, the whole `db.data` is serialized using `JSON.stringify` and written to storage.\n\nDepending on your use case, this can be fine or not. It can be mitigated by doing batch operations and calling `db.write` only when you need it.\n\nIf you plan to scale, it's highly recommended to use databases like PostgreSQL or MongoDB instead.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"lowdb\",\n  \"version\": \"7.0.1\",\n  \"description\": \"Tiny local JSON database for Node, Electron and the browser\",\n  \"keywords\": [\n    \"database\",\n    \"db\",\n    \"electron\",\n    \"embed\",\n    \"embedded\",\n    \"flat\",\n    \"JSON\",\n    \"local\",\n    \"localStorage\",\n    \"sessionStorage\",\n    \"browser\",\n    \"esm\"\n  ],\n  \"homepage\": \"https://github.com/typicode/lowdb#readme\",\n  \"bugs\": {\n    \"url\": \"https://github.com/typicode/lowdb/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/typicode/lowdb.git\"\n  },\n  \"funding\": \"https://github.com/sponsors/typicode\",\n  \"license\": \"MIT\",\n  \"author\": \"Typicode <typicode@gmail.com>\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": \"./lib/index.js\",\n    \"./node\": \"./lib/node.js\",\n    \"./browser\": \"./lib/browser.js\"\n  },\n  \"types\": \"./lib\",\n  \"typesVersions\": {\n    \"*\": {\n      \"node\": [\n        \"lib/node.d.ts\"\n      ],\n      \"browser\": [\n        \"lib/browser.d.ts\"\n      ]\n    }\n  },\n  \"files\": [\n    \"lib\",\n    \"!lib/examples/**/*\",\n    \"!lib/**/*.test.*\"\n  ],\n  \"scripts\": {\n    \"test\": \"node --import tsx/esm --test src/**/*.test.ts src/**/**/*.test.ts\",\n    \"lint\": \"eslint src --ext .ts --ignore-path .gitignore\",\n    \"build\": \"del-cli lib && tsc\",\n    \"prepublishOnly\": \"npm run build\",\n    \"postversion\": \"git push --follow-tags && npm publish\",\n    \"prepare\": \"husky install\"\n  },\n  \"dependencies\": {\n    \"steno\": \"^4.0.2\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^18.4.3\",\n    \"@commitlint/config-conventional\": \"^18.4.3\",\n    \"@commitlint/prompt-cli\": \"^18.4.3\",\n    \"@sindresorhus/tsconfig\": \"^5.0.0\",\n    \"@types/express\": \"^4.17.21\",\n    \"@types/lodash\": \"^4.14.202\",\n    \"@types/node\": \"^20.10.5\",\n    \"@typicode/eslint-config\": \"^1.2.0\",\n    \"del-cli\": \"^5.1.0\",\n    \"eslint\": \"^8.56.0\",\n    \"express-async-handler\": \"^1.2.0\",\n    \"husky\": \"^8.0.3\",\n    \"lodash\": \"^4.17.21\",\n    \"tempy\": \"^3.1.0\",\n    \"ts-node\": \"^10.9.2\",\n    \"tsx\": \"^4.7.0\",\n    \"typescript\": \"^5.3.3\"\n  },\n  \"engines\": {\n    \"node\": \">=18\"\n  }\n}\n"
  },
  {
    "path": "src/adapters/Memory.test.ts",
    "content": "import { deepEqual, equal } from 'node:assert/strict'\nimport test from 'node:test'\n\nimport { Memory, MemorySync } from './Memory.js'\n\nawait test('Memory', async () => {\n  const obj = { a: 1 }\n\n  const memory = new Memory()\n\n  // Null by default\n  equal(await memory.read(), null)\n\n  // Write\n  equal(await memory.write(obj), undefined)\n\n  // Read\n  deepEqual(await memory.read(), obj)\n})\n\nawait test('MemorySync', () => {\n  const obj = { a: 1 }\n\n  const memory = new MemorySync()\n\n  // Null by default\n  equal(memory.read(), null)\n\n  // Write\n  equal(memory.write(obj), undefined)\n\n  // Read\n  deepEqual(memory.read(), obj)\n})\n"
  },
  {
    "path": "src/adapters/Memory.ts",
    "content": "import { Adapter, SyncAdapter } from '../core/Low.js'\n\nexport class Memory<T> implements Adapter<T> {\n  #data: T | null = null\n\n  read(): Promise<T | null> {\n    return Promise.resolve(this.#data)\n  }\n\n  write(obj: T): Promise<void> {\n    this.#data = obj\n    return Promise.resolve()\n  }\n}\n\nexport class MemorySync<T> implements SyncAdapter<T> {\n  #data: T | null = null\n\n  read(): T | null {\n    return this.#data || null\n  }\n\n  write(obj: T): void {\n    this.#data = obj\n  }\n}\n"
  },
  {
    "path": "src/adapters/browser/LocalStorage.ts",
    "content": "import { WebStorage } from './WebStorage.js'\n\nexport class LocalStorage<T> extends WebStorage<T> {\n  constructor(key: string) {\n    super(key, localStorage)\n  }\n}\n"
  },
  {
    "path": "src/adapters/browser/SessionStorage.ts",
    "content": "import { WebStorage } from './WebStorage.js'\n\nexport class SessionStorage<T> extends WebStorage<T> {\n  constructor(key: string) {\n    super(key, sessionStorage)\n  }\n}\n"
  },
  {
    "path": "src/adapters/browser/WebStorage.test.ts",
    "content": "import { deepEqual, equal } from 'node:assert/strict'\nimport test from 'node:test'\n\nimport { WebStorage } from './WebStorage.js'\n\nconst storage: { [key: string]: string } = {}\n\n// Mock localStorage\nconst mockStorage = () => ({\n  getItem: (key: string): string | null => storage[key] || null,\n  setItem: (key: string, data: string) => (storage[key] = data),\n  length: 1,\n  removeItem() {\n    return\n  },\n  clear() {\n    return\n  },\n  // eslint-disable-next-line @typescript-eslint/no-unused-vars\n  key(_: number): string {\n    return ''\n  },\n})\nglobal.localStorage = mockStorage()\nglobal.sessionStorage = mockStorage()\n\nawait test('localStorage', () => {\n  const obj = { a: 1 }\n  const storage = new WebStorage('key', localStorage)\n\n  // Write\n  equal(storage.write(obj), undefined)\n\n  // Read\n  deepEqual(storage.read(), obj)\n})\n\nawait test('sessionStorage', () => {\n  const obj = { a: 1 }\n  const storage = new WebStorage('key', sessionStorage)\n\n  // Write\n  equal(storage.write(obj), undefined)\n\n  // Read\n  deepEqual(storage.read(), obj)\n})\n"
  },
  {
    "path": "src/adapters/browser/WebStorage.ts",
    "content": "import { SyncAdapter } from '../../core/Low.js'\n\nexport class WebStorage<T> implements SyncAdapter<T> {\n  #key: string\n  #storage: Storage\n\n  constructor(key: string, storage: Storage) {\n    this.#key = key\n    this.#storage = storage\n  }\n\n  read(): T | null {\n    const value = this.#storage.getItem(this.#key)\n\n    if (value === null) {\n      return null\n    }\n\n    return JSON.parse(value) as T\n  }\n\n  write(obj: T): void {\n    this.#storage.setItem(this.#key, JSON.stringify(obj))\n  }\n}\n"
  },
  {
    "path": "src/adapters/node/DataFile.ts",
    "content": "import { PathLike } from 'fs'\n\nimport { Adapter, SyncAdapter } from '../../core/Low.js'\nimport { TextFile, TextFileSync } from './TextFile.js'\n\nexport class DataFile<T> implements Adapter<T> {\n  #adapter: TextFile\n  #parse: (str: string) => T\n  #stringify: (data: T) => string\n\n  constructor(\n    filename: PathLike,\n    {\n      parse,\n      stringify,\n    }: {\n      parse: (str: string) => T\n      stringify: (data: T) => string\n    },\n  ) {\n    this.#adapter = new TextFile(filename)\n    this.#parse = parse\n    this.#stringify = stringify\n  }\n\n  async read(): Promise<T | null> {\n    const data = await this.#adapter.read()\n    if (data === null) {\n      return null\n    } else {\n      return this.#parse(data)\n    }\n  }\n\n  write(obj: T): Promise<void> {\n    return this.#adapter.write(this.#stringify(obj))\n  }\n}\n\nexport class DataFileSync<T> implements SyncAdapter<T> {\n  #adapter: TextFileSync\n  #parse: (str: string) => T\n  #stringify: (data: T) => string\n\n  constructor(\n    filename: PathLike,\n    {\n      parse,\n      stringify,\n    }: {\n      parse: (str: string) => T\n      stringify: (data: T) => string\n    },\n  ) {\n    this.#adapter = new TextFileSync(filename)\n    this.#parse = parse\n    this.#stringify = stringify\n  }\n\n  read(): T | null {\n    const data = this.#adapter.read()\n    if (data === null) {\n      return null\n    } else {\n      return this.#parse(data)\n    }\n  }\n\n  write(obj: T): void {\n    this.#adapter.write(this.#stringify(obj))\n  }\n}\n"
  },
  {
    "path": "src/adapters/node/JSONFile.test.ts",
    "content": "import { deepEqual, equal } from 'node:assert/strict'\nimport test from 'node:test'\n\nimport { temporaryFile } from 'tempy'\n\nimport { JSONFile, JSONFileSync } from './JSONFile.js'\n\ntype Data = {\n  a: number\n}\n\nawait test('JSONFile', async () => {\n  const obj = { a: 1 }\n  const file = new JSONFile<Data>(temporaryFile())\n\n  // Null if file doesn't exist\n  equal(await file.read(), null)\n\n  // Write\n  equal(await file.write(obj), undefined)\n\n  // Read\n  deepEqual(await file.read(), obj)\n})\n\nawait test('JSONFileSync', () => {\n  const obj = { a: 1 }\n  const file = new JSONFileSync<Data>(temporaryFile())\n\n  // Null if file doesn't exist\n  equal(file.read(), null)\n\n  // Write\n  equal(file.write(obj), undefined)\n\n  // Read\n  deepEqual(file.read(), obj)\n})\n"
  },
  {
    "path": "src/adapters/node/JSONFile.ts",
    "content": "import { PathLike } from 'fs'\n\nimport { DataFile, DataFileSync } from './DataFile.js'\n\nexport class JSONFile<T> extends DataFile<T> {\n  constructor(filename: PathLike) {\n    super(filename, {\n      parse: JSON.parse,\n      stringify: (data: T) => JSON.stringify(data, null, 2),\n    })\n  }\n}\n\nexport class JSONFileSync<T> extends DataFileSync<T> {\n  constructor(filename: PathLike) {\n    super(filename, {\n      parse: JSON.parse,\n      stringify: (data: T) => JSON.stringify(data, null, 2),\n    })\n  }\n}\n"
  },
  {
    "path": "src/adapters/node/TextFile.test.ts",
    "content": "import { deepEqual, equal } from 'node:assert/strict'\nimport test from 'node:test'\n\nimport { temporaryFile } from 'tempy'\n\nimport { TextFile, TextFileSync } from './TextFile.js'\n\nawait test('TextFile', async () => {\n  const str = 'foo'\n  const file = new TextFile(temporaryFile())\n\n  // Null if file doesn't exist\n  equal(await file.read(), null)\n\n  // Write\n  equal(await file.write(str), undefined)\n\n  // Read\n  deepEqual(await file.read(), str)\n})\n\nawait test('TextFileSync', () => {\n  const str = 'foo'\n  const file = new TextFileSync(temporaryFile())\n\n  // Null if file doesn't exist\n  equal(file.read(), null)\n\n  // Write\n  equal(file.write(str), undefined)\n\n  // Read\n  deepEqual(file.read(), str)\n})\n\nawait test('RaceCondition', async () => {\n  const file = new TextFile(temporaryFile())\n  const promises: Promise<void>[] = []\n\n  let i = 0\n  for (; i <= 100; i++) {\n    promises.push(file.write(String(i)))\n  }\n\n  await Promise.all(promises)\n\n  equal(await file.read(), String(i - 1))\n})\n"
  },
  {
    "path": "src/adapters/node/TextFile.ts",
    "content": "import { PathLike, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { readFile } from 'node:fs/promises'\nimport path from 'node:path'\n\nimport { Writer } from 'steno'\n\nimport { Adapter, SyncAdapter } from '../../core/Low.js'\n\nexport class TextFile implements Adapter<string> {\n  #filename: PathLike\n  #writer: Writer\n\n  constructor(filename: PathLike) {\n    this.#filename = filename\n    this.#writer = new Writer(filename)\n  }\n\n  async read(): Promise<string | null> {\n    let data\n\n    try {\n      data = await readFile(this.#filename, 'utf-8')\n    } catch (e) {\n      if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n        return null\n      }\n      throw e\n    }\n\n    return data\n  }\n\n  write(str: string): Promise<void> {\n    return this.#writer.write(str)\n  }\n}\n\nexport class TextFileSync implements SyncAdapter<string> {\n  #tempFilename: PathLike\n  #filename: PathLike\n\n  constructor(filename: PathLike) {\n    this.#filename = filename\n    const f = filename.toString()\n    this.#tempFilename = path.join(path.dirname(f), `.${path.basename(f)}.tmp`)\n  }\n\n  read(): string | null {\n    let data\n\n    try {\n      data = readFileSync(this.#filename, 'utf-8')\n    } catch (e) {\n      if ((e as NodeJS.ErrnoException).code === 'ENOENT') {\n        return null\n      }\n      throw e\n    }\n\n    return data\n  }\n\n  write(str: string): void {\n    writeFileSync(this.#tempFilename, str)\n    renameSync(this.#tempFilename, this.#filename)\n  }\n}\n"
  },
  {
    "path": "src/browser.ts",
    "content": "export * from './adapters/browser/LocalStorage.js'\nexport * from './adapters/browser/SessionStorage.js'\nexport * from './presets/browser.js'\n"
  },
  {
    "path": "src/core/Low.test.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { deepEqual, equal, throws } from 'node:assert/strict'\nimport fs from 'node:fs'\nimport test from 'node:test'\n\nimport lodash from 'lodash'\nimport { temporaryFile } from 'tempy'\n\nimport { Memory } from '../adapters/Memory.js'\nimport { JSONFile, JSONFileSync } from '../adapters/node/JSONFile.js'\nimport { Low, LowSync } from './Low.js'\n\ntype Data = {\n  a?: number\n  b?: number\n}\n\nfunction createJSONFile(obj: unknown): string {\n  const file = temporaryFile()\n  fs.writeFileSync(file, JSON.stringify(obj))\n  return file\n}\n\nfunction readJSONFile(file: string): unknown {\n  return JSON.parse(fs.readFileSync(file).toString())\n}\n\nawait test('CheckArgs', () => {\n  const adapter = new Memory()\n  // Ignoring TypeScript error and pass incorrect argument\n  // @ts-ignore\n  throws(() => new Low())\n  // @ts-ignore\n  throws(() => new LowSync())\n  // @ts-ignore\n  throws(() => new Low(adapter))\n  // @ts-ignore\n  throws(() => new LowSync(adapter))\n})\n\nawait test('Low', async () => {\n  // Create JSON file\n  const obj = { a: 1 }\n  const file = createJSONFile(obj)\n\n  // Init\n  const defaultData: Data = {}\n  const adapter = new JSONFile<Data>(file)\n  const low = new Low(adapter, defaultData)\n  await low.read()\n\n  // Data should equal file content\n  deepEqual(low.data, obj)\n\n  // Write new data\n  const newObj = { b: 2 }\n  low.data = newObj\n  await low.write()\n\n  // File content should equal new data\n  deepEqual(readJSONFile(file), newObj)\n\n  // Write using update()\n  await low.update((data) => {\n    data.b = 3\n  })\n  deepEqual(readJSONFile(file), { b: 3 })\n})\n\nawait test('LowSync', () => {\n  // Create JSON file\n  const obj = { a: 1 }\n  const file = createJSONFile(obj)\n\n  // Init\n  const defaultData: Data = {}\n  const adapter = new JSONFileSync<Data>(file)\n  const low = new LowSync(adapter, defaultData)\n  low.read()\n\n  // Data should equal file content\n  deepEqual(low.data, obj)\n\n  // Write new data\n  const newObj = { b: 2 }\n  low.data = newObj\n  low.write()\n\n  // File content should equal new data\n  deepEqual(readJSONFile(file), newObj)\n\n  // Write using update()\n  low.update((data) => {\n    data.b = 3\n  })\n  deepEqual(readJSONFile(file), { b: 3 })\n})\n\nawait test('Lodash', async () => {\n  // Extend with lodash\n  class LowWithLodash<T> extends Low<T> {\n    chain: lodash.ExpChain<this['data']> = lodash.chain(this).get('data')\n  }\n\n  // Create JSON file\n  const obj = { todos: ['foo', 'bar'] }\n  const file = createJSONFile(obj)\n\n  // Init\n  const defaultData = { todos: [] }\n  const adapter = new JSONFile<typeof obj>(file)\n  const low = new LowWithLodash(adapter, defaultData)\n  await low.read()\n\n  // Use lodash\n  const firstTodo = low.chain.get('todos').first().value()\n\n  equal(firstTodo, 'foo')\n})\n"
  },
  {
    "path": "src/core/Low.ts",
    "content": "export interface Adapter<T> {\n  read: () => Promise<T | null>\n  write: (data: T) => Promise<void>\n}\n\nexport interface SyncAdapter<T> {\n  read: () => T | null\n  write: (data: T) => void\n}\n\nfunction checkArgs(adapter: unknown, defaultData: unknown) {\n  if (adapter === undefined) throw new Error('lowdb: missing adapter')\n  if (defaultData === undefined) throw new Error('lowdb: missing default data')\n}\n\nexport class Low<T = unknown> {\n  adapter: Adapter<T>\n  data: T\n\n  constructor(adapter: Adapter<T>, defaultData: T) {\n    checkArgs(adapter, defaultData)\n    this.adapter = adapter\n    this.data = defaultData\n  }\n\n  async read(): Promise<void> {\n    const data = await this.adapter.read()\n    if (data) this.data = data\n  }\n\n  async write(): Promise<void> {\n    if (this.data) await this.adapter.write(this.data)\n  }\n\n  async update(fn: (data: T) => unknown): Promise<void> {\n    fn(this.data)\n    await this.write()\n  }\n}\n\nexport class LowSync<T = unknown> {\n  adapter: SyncAdapter<T>\n  data: T\n\n  constructor(adapter: SyncAdapter<T>, defaultData: T) {\n    checkArgs(adapter, defaultData)\n    this.adapter = adapter\n    this.data = defaultData\n  }\n\n  read(): void {\n    const data = this.adapter.read()\n    if (data) this.data = data\n  }\n\n  write(): void {\n    if (this.data) this.adapter.write(this.data)\n  }\n\n  update(fn: (data: T) => unknown): void {\n    fn(this.data)\n    this.write()\n  }\n}\n"
  },
  {
    "path": "src/examples/README.md",
    "content": "# Examples\n\n- [cli.ts](./cli.ts) - Simple CLI using JSONFileSync adapter\n- [server.ts](./server.ts) - Express example using JSONFile adapter\n- [browser.ts](./browser.ts) - LocalStorage adapter example\n- [in-memory.ts](./in-memory.ts) - Example showing how to use in-memory adapter to write fast tests\n"
  },
  {
    "path": "src/examples/browser.ts",
    "content": "import { LocalStoragePreset } from '../presets/browser.js'\n\ntype Data = {\n  messages: string[]\n}\n\nconst defaultData: Data = { messages: [] }\nconst db = LocalStoragePreset<Data>('db', defaultData)\n\ndb.update(({ messages }) => messages.push('foo'))\n"
  },
  {
    "path": "src/examples/cli.ts",
    "content": "import { JSONFileSyncPreset } from '../presets/node.js'\n\ntype Data = {\n  messages: string[]\n}\n\nconst message = process.argv[2] || ''\n\nconst defaultData: Data = { messages: [] }\nconst db = JSONFileSyncPreset<Data>('file.json', defaultData)\n\ndb.update(({ messages }) => messages.push(message))\n"
  },
  {
    "path": "src/examples/in-memory.ts",
    "content": "// With this adapter, calling `db.write()` will do nothing.\n// One use case for this adapter can be for tests.\nimport { LowSync, MemorySync, SyncAdapter } from '../index.js'\nimport { JSONFileSync } from '../node.js'\n\ndeclare global {\n  // eslint-disable-next-line @typescript-eslint/no-namespace\n  namespace NodeJS {\n    interface ProcessEnv {\n      NODE_ENV: 'test' | 'dev' | 'prod'\n    }\n  }\n}\n\ntype Data = Record<string, unknown>\nconst defaultData: Data = {}\nconst adapter: SyncAdapter<Data> =\n  process.env.NODE_ENV === 'test'\n    ? new MemorySync<Data>()\n    : new JSONFileSync<Data>('db.json')\n\nconst db = new LowSync<Data>(adapter, defaultData)\ndb.read()\n// Rest of your code...\n"
  },
  {
    "path": "src/examples/server.ts",
    "content": "// Note: if you're developing a local server and don't expect to get concurrent requests,\n// it can be easier to use `JSONFileSync` adapter.\n// But if you need to avoid blocking requests, you can do so by using `JSONFile` adapter.\nimport express from 'express'\nimport asyncHandler from 'express-async-handler'\n\nimport { JSONFilePreset } from '../presets/node.js'\n\nconst app = express()\napp.use(express.json())\n\ntype Post = {\n  id: string\n  body: string\n}\n\ntype Data = {\n  posts: Post[]\n}\n\nconst defaultData: Data = { posts: [] }\nconst db = await JSONFilePreset<Data>('db.json', defaultData)\n\n// db.data can be destructured to avoid typing `db.data` everywhere\nconst { posts } = db.data\n\napp.get('/posts/:id', (req, res) => {\n  const post = posts.find((p) => p.id === req.params.id)\n  res.send(post)\n})\n\napp.post(\n  '/posts',\n  asyncHandler(async (req, res) => {\n    const post = req.body as Post\n    post.id = String(posts.length + 1)\n    await db.update(({ posts }) => posts.push(post))\n    res.send(post)\n  }),\n)\n\napp.listen(3000, () => {\n  console.log('listening on port 3000')\n})\n"
  },
  {
    "path": "src/index.ts",
    "content": "export * from './adapters/Memory.js'\nexport * from './core/Low.js'\n"
  },
  {
    "path": "src/node.ts",
    "content": "export * from './adapters/node/DataFile.js'\nexport * from './adapters/node/JSONFile.js'\nexport * from './adapters/node/TextFile.js'\nexport * from './presets/node.js'\n"
  },
  {
    "path": "src/presets/browser.ts",
    "content": "import { LocalStorage } from '../adapters/browser/LocalStorage.js'\nimport { SessionStorage } from '../adapters/browser/SessionStorage.js'\nimport { LowSync } from '../index.js'\n\nexport function LocalStoragePreset<Data>(\n  key: string,\n  defaultData: Data,\n): LowSync<Data> {\n  const adapter = new LocalStorage<Data>(key)\n  const db = new LowSync<Data>(adapter, defaultData)\n  db.read()\n  return db\n}\n\nexport function SessionStoragePreset<Data>(\n  key: string,\n  defaultData: Data,\n): LowSync<Data> {\n  const adapter = new SessionStorage<Data>(key)\n  const db = new LowSync<Data>(adapter, defaultData)\n  db.read()\n  return db\n}\n"
  },
  {
    "path": "src/presets/node.ts",
    "content": "import { PathLike } from 'node:fs'\n\nimport { Memory, MemorySync } from '../adapters/Memory.js'\nimport { JSONFile, JSONFileSync } from '../adapters/node/JSONFile.js'\nimport { Low, LowSync } from '../core/Low.js'\n\nexport async function JSONFilePreset<Data>(\n  filename: PathLike,\n  defaultData: Data,\n): Promise<Low<Data>> {\n  const adapter =\n    process.env.NODE_ENV === 'test'\n      ? new Memory<Data>()\n      : new JSONFile<Data>(filename)\n  const db = new Low<Data>(adapter, defaultData)\n  await db.read()\n  return db\n}\n\nexport function JSONFileSyncPreset<Data>(\n  filename: PathLike,\n  defaultData: Data,\n): LowSync<Data> {\n  const adapter =\n    process.env.NODE_ENV === 'test'\n      ? new MemorySync<Data>()\n      : new JSONFileSync<Data>(filename)\n  const db = new LowSync<Data>(adapter, defaultData)\n  db.read()\n  return db\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"@sindresorhus/tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"./lib\"\n  }\n}\n"
  }
]