{"id":28809736,"url":"https://github.com/num2k/stosh","last_synced_at":"2026-02-28T07:07:00.507Z","repository":{"id":288566794,"uuid":"968526990","full_name":"num2k/stosh","owner":"num2k","description":"Middleware-based browser storage wrapper","archived":false,"fork":false,"pushed_at":"2025-05-13T20:43:05.000Z","size":577,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-15T09:52:19.685Z","etag":null,"topics":["cookie","indexeddb","javascript","library","localstorage","npmjs","sessionstorage","storage","stosh","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/stosh","language":"TypeScript","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/num2k.png","metadata":{"files":{"readme":"README.ko.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2025-04-18T08:37:30.000Z","updated_at":"2025-05-14T12:59:25.000Z","dependencies_parsed_at":"2025-05-13T21:34:29.795Z","dependency_job_id":"d878ba80-76cd-4fd7-a1a2-1e401d2351c4","html_url":"https://github.com/num2k/stosh","commit_stats":null,"previous_names":["num2k/stosh"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/num2k/stosh","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/num2k%2Fstosh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/num2k%2Fstosh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/num2k%2Fstosh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/num2k%2Fstosh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/num2k","download_url":"https://codeload.github.com/num2k/stosh/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/num2k%2Fstosh/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260558777,"owners_count":23027718,"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":["cookie","indexeddb","javascript","library","localstorage","npmjs","sessionstorage","storage","stosh","typescript"],"created_at":"2025-06-18T13:03:20.785Z","updated_at":"2026-02-28T07:07:00.501Z","avatar_url":"https://github.com/num2k.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# stosh\n\n미들웨어 기반 브라우저 스토리지 래퍼\n\nstosh는 IndexedDB, localStorage, sessionStorage, cookie를 브라우저에서 안전하고 확장성 있게 사용할 수 있도록 공통 인터페이스를 제공하는 TypeScript 라이브러리입니다.\n\n---\n\n## 주요 특징\n\n- IndexedDB/localStorage/sessionStorage/cookie 공통 인터페이스 제공\n- 네임스페이스(접두사)로 데이터 격리 및 관리\n- set 시 만료(expire) 옵션 지원, 만료 데이터 자동 삭제\n- 미들웨어 패턴 지원: set/get/remove 동작에 자유롭게 기능 추가 가능\n- 타입 안전성(TypeScript 제네릭)\n- 스토리지 이벤트 구독(onChange) 지원: 스토리지 값 변경 시 콜백 실행\n- 커스텀 직렬화/역직렬화 지원(암호화, 압축 등 다양한 포맷 사용 가능)\n- 일괄 처리(Batch) API 지원: 여러 키/값을 한 번에 저장·조회·삭제\n- 외부 의존성 없음, 경량화 번들사이즈 4kB(gzip) 이하\n- 크로스 브라우저(Chrome, Edge, Firefox, Safari 등) 지원\n- 모든 JS/TS 프로젝트(React, Vue, Next, 순수 HTML+JS 등)에서 호환\n\n---\n\n## 설치\n\n**npm**:\n\n```bash\nnpm install stosh\n```\n\n**yarn**:\n\n```bash\nyarn add stosh\n```\n\n**pnpm**:\n\n```bash\npnpm add stosh\n```\n\n**순수 HTML에서 CDN 사용:**\n아래와 같이 `script` 태그를 추가하면, stosh를 전역 함수(`window.stosh`)로 바로 사용할 수 있습니다.\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/num2k/stosh@latest/standalone/stosh.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const storage = stosh({ namespace: \"demo\" });\n  storage.setSync(\"foo\", 123);\n  console.log(storage.getSync(\"foo\")); // 123\n\u003c/script\u003e\n```\n\n---\n\n## 기본 사용법\n\nstosh의 기본 API(set/get/remove 등)는 모두 `Promise` 기반의 비동기 함수입니다. `await` 또는 `.then()`으로 사용하세요.\n예외(에러)는 자동으로 무시되지 않고, 반드시 사용자가 직접 `try/catch` 또는 `.catch()`로 처리해야 안전 합니다.\n\n```ts\nimport { stosh } from \"stosh\";\n\nconst storage = stosh({ namespace: \"myApp\" });\n\n// 값 저장/조회/삭제\n// try/catch 방식\ntry {\n  await storage.set(\"user\", { name: \"홍길동\" }, { expire: 1000 * 60 * 10 });\n  const user = await storage.get\u003c{ name: string }\u003e(\"user\");\n  await storage.remove(\"user\");\n  await storage.clear();\n} catch (e) {\n  // 용량 초과, 직렬화 오류, 미들웨어 오류 등\n  console.error(\"저장 실패:\", e);\n}\n\n// .then().catch() 방식\nstorage\n  .set(\"user\", { name: \"홍길동\" }, { expire: 1000 * 60 * 10 })\n  .then(() =\u003e {\n    // 저장 성공 시 실행\n    return storage.get\u003c{ name: string }\u003e(\"user\");\n  })\n  .then((user) =\u003e {\n    // 조회 성공 시 실행\n    console.log(\"조회 결과:\", user);\n  })\n  .catch((e) =\u003e {\n    // 저장 또는 조회 중 에러 발생 시 실행\n    console.error(\"에러 발생:\", e);\n  });\n\n// 키 존재 여부\nlet exists = false;\ntry {\n  exists = await storage.has(\"user\");\n} catch (e) {\n  console.error(\"존재 여부 확인 실패:\", e);\n}\n\nif (exists) {\n  // ...\n}\n\n// 네임스페이스 내 전체 값 조회\ntry {\n  const all = await storage.getAll();\n} catch (e) {\n  console.error(\"전체 값 조회 실패:\", e);\n}\n```\n\n---\n\n## 동기 API 사용법\n\n동기 API가 필요한 경우 `setSync`/`getSync`/`removeSync` 등 `Sync` 접미사 메서드를 사용하세요.\nIndexedDB(비동기 저장소)만 제외하면 localStorage, sessionStorage, cookie, memory와 모든 기능(만료, 네임스페이스, 미들웨어, batch, 커스텀 직렬화 등)을 지원합니다.\n동기 메서드(setSync 등)는 `try/catch`로 감싸서 예외를 처리하는 것이 안전합니다.\n\n```ts\nconst storage = stosh({ type: \"local\", namespace: \"myApp\" });\ntry {\n  storage.setSync(\"foo\", 1);\n} catch (e) {\n  // 직렬화 오류, 미들웨어 오류 등\n  console.error(\"에러 발생:\", e);\n}\nconst v = storage.getSync(\"foo\");\nstorage.removeSync(\"foo\");\nstorage.clearSync();\n```\n\n---\n\n## 만료(expire) 옵션 사용 예시\n\n```ts\nawait storage.set(\"temp\", \"임시값\", { expire: 5000 });\nsetTimeout(async () =\u003e {\n  console.log(await storage.get(\"temp\")); // 5초 후에는 null 반환\n}, 6000);\n```\n\n---\n\n## 미들웨어 활용 예시\n\n- `storage.use(\"set\", ...)`처럼 `set`, `get`, `remove` 키워드로 미들웨어를 등록하면 기본적으로 동기 API(`setSync`, `getSync`, `removeSync` 등)와 비동기 API(`set`, `get`, `remove` 등) 모두에 동일하게 적용됩니다.\n\n```ts\nstorage.use(\"set\", async (ctx, next) =\u003e {\n  ctx.value = await encryptAsync(ctx.value);\n  await next();\n});\n\nstorage.use(\"get\", async (ctx, next) =\u003e {\n  await next();\n  if (ctx.result) ctx.result = await decryptAsync(ctx.result);\n});\n```\n\n- `ctx.isSync`는 현재 동작이 동기 API에서 호출된 것인지, 비동기 API에서 호출된 것인지를 구분할 수 있는 플래그입니다.\n\n```ts\nstorage.use(\"set\", async (ctx, next) =\u003e {\n  if (ctx.isSync) {\n    // 동기 API에서만 실행할 로직\n    console.log(\"Sync set middleware\");\n  } else {\n    // 비동기 API에서만 실행할 로직\n    console.log(\"Async set middleware\");\n  }\n  await next();\n});\n```\n\n---\n\n## 여러 인스턴스/네임스페이스 분리 예시\n\n```ts\nconst userStorage = stosh({ namespace: \"user\" });\nconst cacheStorage = stosh({ namespace: \"cache\", type: \"session\" });\n\nawait userStorage.set(\"profile\", { name: \"홍길동\" });\nawait cacheStorage.set(\"temp\", 123);\n```\n\n---\n\n## 스토리지 이벤트 구독 예시\n\n- 현재 인스턴스에서 `set`/`remove`/`clear` 등으로 값이 변경될 때 콜백이 즉시 실행됩니다.\n- **`clear`/`clearSync` 관련 참고**: 이 메서드들은 내부적으로 삭제되는 각 키에 대해 개별적인 `remove` 이벤트를 발생시킵니다. 따라서 `clear` 또는 `clearSync` 호출 시, 단일 'clear' 이벤트가 아니라 각 키마다 한 번씩, 즉 여러 번 `onChange` 콜백이 실행될 수 있습니다.\n- 다른 탭/윈도우에서는 **localStorage 또는 sessionStorage** 값 변경시에만 콜백이 실행됩니다.\n\n```ts\nstorage.onChange(async (key, value) =\u003e {\n  await syncToServer(key, value);\n});\n\n// 여러 개 등록 가능\nconst unsub1 = storage.onChange((key, value) =\u003e {});\nconst unsub2 = storage.onChange((key, value) =\u003e {});\n\n// 각각 해제 가능\nunsub1();\nunsub2();\n```\n\n---\n\n## 스토리지 타입 선택\n\nstosh 생성자 옵션(`type`)으로 IndexedDB, localStorage, sessionStorage, cookie 중 원하는 스토리지를 선택할 수 있습니다.\n\n**사용 예시:**\n\n```ts\nconst storage = stosh({\n  type: \"local\", // localStorage 사용\n  namespace: \"fb\",\n});\n```\n\n---\n\n## 저장소 폴백 우선순위(priority) 체계\n\n여러 저장소를 우선순위대로 시도하여, 사용 가능한 첫 번째 저장소를 자동으로 적용합니다.\n기본 우선순위는 `[\"idb\", \"local\", \"session\", \"cookie\", \"memory\"]`이며 동기 API(`*Sync`) 사용 시에는 IndexedDB(`idb`)가 비동기 전용이므로, 기본 우선순위에서 `idb`를 제외하고 `local`부터 적용됩니다.\n생성자 옵션의 `priority`로 직접 순서를 변경할 수 있습니다.\n\n- 예시: IndexedDB가 불가하면 localStorage, 그다음 sessionStorage, cookie, 마지막으로 memory 순으로 자동 폴백\n- 활용: 다양한 브라우저 환경, 프라이빗 모드, 제한 환경 등에서 일관된 API로 안전하게 데이터 저장\n\n**사용 예시:**\n\n```ts\nconst storage = stosh({\n  namespace: \"fb\",\n});\nawait storage.set(\"foo\", \"bar\"); // idb(IndexedDB)에 최우선 저장 시도\nconsole.log(await storage.get(\"foo\"));\n\n// 동기 API\nstorage.setSync(\"foo\", \"bar\"); // local(localStorage)에 최우선 저장 시도\nconsole.log(storage.getSync(\"foo\"));\n\n// 우선순위 커스텀\nconst storage2 = stosh({\n  priority: [\"cookie\", \"local\", \"session\", \"idb\", \"memory\"],\n  namespace: \"fb\",\n});\nawait storage2.set(\"foo\", \"bar\"); // cookie에 최우선 저장 시도\n```\n\n**`priority`와 `type` 옵션 간의 상호작용**\n\n- `priority`를 지정하면 `type`은 무시되고, `priority` 배열에 명시된 순서대로 시도합니다.\n- `priority`가 없고 `type`이 지정되면 해당 타입만 사용됩니다. (폴백은 사용되지 않습니다)\n- 두 옵션 모두 없으면 기본 우선순위대로 자동 적용됩니다.\n\n---\n\n## strictSyncFallback 옵션\n\n__동기(*Sync) API와 IndexedDB 사용 정책__\n\nstosh 인스턴스를 별도의 옵션 없이 생성하면 IndexedDB(`idb`)가 primary storage로 선택됩니다.\nIndexedDB는 브라우저 특성상 동기(*Sync) API를 지원하지 않기 때문에, 아래와 같은 옵션을 지원합니다.\n\n| 옵션 | 동작 방식 |\n|-|-|\n| `strictSyncFallback: false` (기본값) | IndexedDB가 primary일 때 sync API(`setSync`, `getSync` 등) 호출 시 경고만 출력하고 fallback storage(localStorage 등)로 동작 |\n| `strictSyncFallback: true` | IndexedDB가 primary일 때 sync API 호출 시 에러를 throw하여 동기 API 사용을 강제로 막음 |\n```ts\nimport { stosh } from \"stosh\";\n\n// 기본값: IndexedDB가 primary storage, strictSyncFallback은 false\nconst storage = stosh();\n\n// strictSyncFallback: true (IndexedDB + sync API 사용 시 에러 발생)\nconst storage = stosh({\n  strictSyncFallback: true\n});\n\ntry {\n  storage.setSync(\"foo\", 1); // Error 발생!\n} catch (e) {\n  console.error(e.message);\n}\n```\n\n**참고**:\n- `strictSyncFallback` 옵션을 사용하지 않으면(또는 false) fallback storage(localStorage, memory 등)를 통해 sync API가 동작하지만, 데이터 일관성이나 동기화에 주의해야 합니다.\n- IndexedDB를 primary로 사용하면서 sync API를 반드시 금지하고 싶을 때 `strictSyncFallback: true`를 권장합니다.\n\n---\n\n## 쿠키 스토리지 지원\n\n생성자 옵션에서 `type: \"cookie\"`를 지정하면 동일한 API로 쿠키를 사용할 수 있습니다.\n\n- 활용 예시: IndexedDB를/localStorage/sessionStorage 사용할 수 없는 환경에서의 폴백, 서버와 공유해야 하는 소규모 데이터 저장 등\n- 참고:\n  - 쿠키는 도메인당 약 4KB로 용량이 작고, 모든 HTTP 요청에 자동으로 서버로 전송됩니다.\n  - 쿠키 전용 옵션 `path`, `domain`, `secure`, `sameSite`은 표준이지만 브라우저/플랫폼/HTTP/HTTPS 환경에 따라 세부 동작(쿠키 저장, 삭제, 전송, 접근 등)이 다를 수 있습니다.\n\n**예시:**\n\n```ts\nconst cookieStorage = stosh({ type: \"cookie\", namespace: \"ck\" });\nawait cookieStorage.set(\"foo\", \"bar\");\nconsole.log(await cookieStorage.get(\"foo\")); // \"bar\"\nawait cookieStorage.remove(\"foo\");\n\n// 쿠키 옵션 사용\nconst advancedCookieStorage = stosh({\n  type: \"cookie\",\n  namespace: \"advancedCk\",\n  path: \"/app\", // 이 인스턴스에서 생성되는 모든 쿠키의 기본 경로\n  secure: true, // 기본 secure 플래그\n});\n\n// 기본 옵션으로 설정\nadvancedCookieStorage.setSync(\"user\", \"Alice\");\n\n// 특정 옵션으로 설정 (기본 경로 덮어쓰기, 만료 시간 추가)\nadvancedCookieStorage.setSync(\"session\", \"xyz\", {\n  path: \"/\", // 기본 경로 덮어쓰기\n  expire: 1000 * 60 * 30, // 30분 만료\n});\n\n// 옵션(path)과 일치 하는경우 삭제\nadvancedCookieStorage.removeSync(\"user\", { path: \"/app\" });\n```\n\n---\n\n## SSR(서버사이드 렌더링) 환경 지원\n\nSSR(서버사이드 렌더링, 예: Next.js, Nuxt 등) 환경에서도 안전하게 import 및 인스턴스 생성이 가능합니다.\n\n- SSR 환경(window가 없는 경우)에는 자동으로 메모리 스토리지를 사용하며, 브라우저 전용 이벤트 리스너는 등록하지 않습니다.\n- `stosh.isSSR` 정적 속성으로 현재 환경이 SSR인지 코드에서 확인할 수 있습니다.\n\n```ts\nif (stosh.isSSR) {\n  // 서버 환경(SSR)에서만 실행되는 코드\n}\n```\n\n---\n\n## 메모리 폴백(자동 대체) 기능\n\n브라우저 스토리지(IndexedDB, localStorage, sessionStorage, cookie)를 사용할 수 없는 환경(예: SSR, 프라이빗 모드, 저장소 제한, 브라우저 미지원 등)에서 자동으로 메모리 스토리지로 전환되어 동작합니다. 이 경우, 데이터는 브라우저를 새로고침하거나 탭을 닫으면 사라집니다.\n\n- 폴백이 발생했는지는 인스턴스의 `isMemoryFallback` 속성으로 확인할 수 있습니다.\n- API 사용법은 동일하며, 별도의 예외처리 없이 안전하게 사용할 수 있습니다.\n\n```ts\nconst storage = stosh();\nif (storage.isMemoryFallback) {\n  console.warn(\n    \"현재 환경에서는 메모리 스토리지를 사용합니다. 새로고침 시 데이터가 사라집니다.\"\n  );\n}\n```\n\n---\n\n## 객체/배열 자동 직렬화/역직렬화\n\n객체, 배열 등 비원시값을 저장할 때 자동으로 JSON.stringify/parse를 적용하므로 별도의 처리 없이 안전하게 데이터를 저장하고 불러올 수 있습니다.\n\n---\n\n## 커스텀 직렬화/역직렬화 예제\n\n생성자 옵션으로 serialize/deserialize 함수를 지정할 수 있습니다. 이를 활용하면 JSON 이외의 포맷(예: 암호화, 압축 등)이나 외부 포맷을 자유롭게 사용할 수 있습니다.\n\n```ts\nconst b64 = (s: string) =\u003e btoa(unescape(encodeURIComponent(s)));\nconst b64d = (s: string) =\u003e decodeURIComponent(escape(atob(s)));\nconst storage = stosh({\n  namespace: \"enc\",\n  serialize: (data) =\u003e b64(JSON.stringify(data)),\n  deserialize: (raw) =\u003e JSON.parse(b64d(raw)),\n});\nawait storage.set(\"foo\", { a: 1 });\nconsole.log(await storage.get(\"foo\")); // { a: 1 }\n```\n\n---\n\n## 일괄 처리(Batch) API 예제\n\n`batchSet`, `batchSetSync`, `batchRemove`, `batchRemoveSync` 메서드는 두 번째 인자로 공통 옵션을 받을 수 있습니다.\n`batchSet`, `batchSetSync` 메서드는 각 객체별로 개별 옵션(`options` 필드)을 지정할 수도 있습니다.\n실제 동작 시, 각 객체의 개별 옵션과 공통 옵션이 병합되어 적용됩니다. (객체별 옵션이 우선, 공통 옵션은 기본값 역할)\n\n```ts\nconst storage = stosh({ namespace: \"batch\" });\n// 여러 값 한 번에 저장\nawait storage.batchSet([\n  { key: \"a\", value: 1 },\n  { key: \"b\", value: 2 },\n  { key: \"c\", value: 3 },\n]);\n\n// 여러 값 한 번에 조회\nconsole.log(await storage.batchGet([\"a\", \"b\", \"c\"])); // [1, 2, 3]\n\n// 여러 값 한 번에 삭제\nawait storage.batchRemove([\"a\", \"b\"]);\n\n// \"a\"는 expire, path, secure, \"b\"는 path, secure만 적용됨\nawait storage.batchSet(\n  [\n    { key: \"a\", value: 1, options: { expire: 1000 } },\n    { key: \"b\", value: 2 },\n  ],\n  { path: \"/app\", secure: true }\n);\n```\n\n---\n\n## 타입 안전성\n\nstosh 인스턴스를 생성할 때 제네릭 타입을 명시하면, 저장/조회되는 데이터의 타입이 컴파일 타임에 강제되어 실수로 잘못된 타입의 데이터를 저장하거나 조회하는 것을 방지할 수 있습니다.\n\n```ts\nconst storage = stosh\u003c{ name: string }\u003e();\nawait storage.set(\"user\", { name: \"홍길동\" });\nconst user = await storage.get(\"user\"); // 타입: { name: string } | null\n```\n\n_특징_\n\n- 모든 key의 value 타입이 명확하게 강제됨\n- `batchSet`, `batchGet` 등에서도 동일한 구조만 허용\n- 타입 안전성이 최우선\n\n__유연성 우선 (제네릭 생략)__\n\nstosh 인스턴스를 제네릭 없이 생성하면 key별로 value 타입을 자유롭게 저장/조회할 수 있습니다.\n이 경우 타입 안전성은 보장되지 않지만, batch API 등에서 다양한 타입을 유연하게 다룰 수 있습니다.\n\n```ts\nconst storage = stosh(); // stosh\u003cany\u003e() 와 같음\nstorage.batchSet([\n  { key: \"a\", value: 1 },\n  { key: \"b\", value: { name: \"홍길동\" } },\n  { key: \"c\", value: [1, 2, 3] },\n]);\nconst a = storage.get(\"a\"); // 타입: any\nconst b = storage.get(\"b\"); // 타입: any\nconst c = storage.get(\"c\"); // 타입: any\n```\n\n_특징_\n\n- key별 value 타입이 자유로움\n- `batchSet`, `batchGet` 등에서 다양한 타입 혼용 가능\n- 타입 안전성은 보장되지 않으므로, get/set 시 타입 파라미터를 직접 지정해 사용하는 것이 권장됨\n\n---\n\n## 추가 활용 예제\n\n### 동적 네임스페이스(다중 사용자 지원)\n\n```ts\nfunction getUserStorage(userId: string) {\n  return stosh({ namespace: `user:${userId}` });\n}\nconst user1Storage = getUserStorage(\"alice\");\nawait user1Storage.set(\"profile\", { name: \"Alice\" });\n```\n\n### 미들웨어로 데이터 검증/로깅\n\n```ts\nconst storage = stosh({ namespace: \"log\" });\n\nstorage.use(\"set\", async (ctx, next) =\u003e {\n  if (typeof ctx.value !== \"string\") throw new Error(\"Only strings allowed!\");\n  await logToServer(ctx.key, ctx.value);\n  await next();\n});\nawait storage.set(\"greeting\", \"hello\"); // 정상\n// await storage.set('fail', 123); // 예외 발생\n```\n\n### 만료(expire)와 미들웨어 조합\n\n```ts\nconst storage = stosh({ namespace: \"expire\" });\nstorage.use(\"set\", async (ctx, next) =\u003e {\n  // 모든 값에 1분 만료 자동 적용\n  ctx.options = { ...ctx.options, expire: 60000 };\n  await next();\n});\nawait storage.set(\"temp\", \"data\");\n```\n\n---\n\n## API\n\n- `stosh(options?: StoshOptions)`\n  - `StoshOptions`: `type`, `priority`, `namespace`, `serialize`, `deserialize`, `strictSyncFallback`, and _`Cookie Options`_ (`path`, `domain`, `secure`, `sameSite`)\n- `set(key, value, options?: SetOptions): Promise\u003cvoid\u003e`\n  - `SetOptions`: `expire` and _`Cookie Options`_\n- `get\u003cT\u003e(key): Promise\u003cT | null\u003e`\n- `remove(key, options?: RemoveOptions): Promise\u003cvoid\u003e`\n  - `RemoveOptions`: _`Cookie Options`_\n- `clear(): Promise\u003cvoid\u003e`\n- `has(key): Promise\u003cboolean\u003e`\n- `getAll(): Promise\u003cRecord\u003cstring, T\u003e\u003e`\n- `setSync(key, value, options?: SetOptions): void`\n- `getSync\u003cT\u003e(key): T | null`\n- `removeSync(key, options?: RemoveOptions): void`\n- `clearSync(): void`\n- `hasSync(key): boolean`\n- `getAllSync(): Record\u003cstring, T\u003e`\n- `batchSet(entries: { key: string; value: any, options?: SetOptions }[], options?: SetOptions): Promise\u003cvoid\u003e`\n  - Applies `SetOptions` (like `expire`, cookie options) to all entries being set.\n- `batchGet\u003cU = T\u003e(keys: string[]): Promise\u003c(U | null)[]\u003e`\n- `batchRemove(keys: string[], options?: RemoveOptions): Promise\u003cvoid\u003e`\n  - Applies `RemoveOptions` (cookie options) to all keys being removed.\n- `batchSetSync(entries: { key: string; value: any, options?: SetOptions }[], options?: SetOptions): void`\n- `batchGetSync\u003cU = T\u003e(keys: string[]): (U | null)[]`\n- `batchRemoveSync(keys: string[], options?: RemoveOptions): void`\n- `use(method: 'get' | 'set' | 'remove', middleware, options?)`\n  - `middleware`:\n    - Synchronous: `(ctx: MiddlewareContext, next: () =\u003e void) =\u003e void`  \n    - Asynchronous: `(ctx: MiddlewareContext, next: () =\u003e Promise\u003cvoid\u003e | void) =\u003e Promise\u003cvoid\u003e | void`\n  - `options`: `{prepend?: boolean; append?: boolean}`\n- `onChange(cb: (key: string | null, value: any | null) =\u003e void)`\n\n전체 API 문서는 [API.ko.md](https://github.com/num2k/stosh/blob/main/documents/API.ko.md)에서 확인할 수 있습니다.\n\n---\n\n## 라이선스\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnum2k%2Fstosh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnum2k%2Fstosh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnum2k%2Fstosh/lists"}