Skip to content

Commit 24188ba

Browse files
authored
Merge pull request #15 from Haberkamp/add-core-package
Add core package
2 parents d4d92c2 + 0357767 commit 24188ba

File tree

13 files changed

+626
-0
lines changed

13 files changed

+626
-0
lines changed

.changeset/real-sheep-read.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@typed-storage/core": minor
3+
---
4+
5+
Add `@typed-storage/core` package

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Usage does vary depending on the framework you're using.
1313

1414
- [React](./packages/react)
1515
- Vue (coming soon)
16+
- [Vanilla JS](./packages/core)
1617

1718
## Feedback and Contributing
1819

packages/core/LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Nils Haberkamp
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/core/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# @typed-storage/core
2+
3+
![CI passing](https://github.com/Haberkamp/typed-storage/actions/workflows/ci.yml/badge.svg?event=push&branch=main)
4+
![Created by](https://img.shields.io/badge/created%20by-@n__haberkamp-065afa.svg)
5+
![NPM License](https://img.shields.io/npm/l/%40typed-storage%2Fcore)
6+
7+
## Highlights
8+
9+
- Schema validation
10+
- Automatic type inference
11+
- Use it with zod, validbot, arktype, etc.
12+
13+
## Overview
14+
15+
TypedStorage gives you a type-safe way to access `localStorage`. Moreover, you can use zod or any other schema validation library to make sure your data is always valid.
16+
17+
### Author
18+
19+
Hey, I'm Nils. In my spare time [I write about things I learned](https://www.haberkamp.dev/) or I [create open source packages](https://github.com/Haberkamp), that help me (and hopefully you) to build better apps.
20+
21+
## Usage
22+
23+
Two steps: define your schema and use it like normal localStorage.
24+
25+
1. Define your schema
26+
27+
```ts
28+
// schema.ts
29+
30+
import { defineStorage } from "@typed-storage/core"
31+
// Feel free to use any other StandardSchema compatible library
32+
import { z } from "zod"
33+
34+
const { storage } = defineStorage({
35+
theme: z.enum(["light", "dark"]),
36+
})
37+
38+
export { storage }
39+
```
40+
41+
2. Use the storage
42+
43+
```ts
44+
// my-file.ts
45+
46+
import { storage } from "./schema"
47+
48+
storage.set("theme", "light")
49+
50+
const theme = storage.get("theme")
51+
```
52+
53+
## Installation
54+
55+
You can install this package with any package manager you like.
56+
57+
```bash
58+
pnpm add @typed-storage/core
59+
```
60+
61+
## Feedback and Contributing
62+
63+
I highly appreceate your feedback! Please create an [issue](https://github.com/Haberkamp/typed-storage/issues/new), if you've found any bugs or want to request a feature.

packages/core/eslint.config.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @ts-check
2+
3+
import eslint from "@eslint/js"
4+
import tseslint from "typescript-eslint"
5+
import eslintPluginUnicorn from "eslint-plugin-unicorn"
6+
7+
/** @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.Config[]} */
8+
export default tseslint.config(
9+
{
10+
ignores: ["dist"],
11+
},
12+
eslint.configs.recommended,
13+
eslintPluginUnicorn.configs.recommended,
14+
tseslint.configs.recommendedTypeChecked,
15+
{
16+
languageOptions: {
17+
parserOptions: {
18+
projectService: true,
19+
tsconfigRootDir: import.meta.dirname,
20+
},
21+
},
22+
rules: {
23+
"unicorn/no-null": "off",
24+
},
25+
},
26+
)

packages/core/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@typed-storage/core",
3+
"version": "0.0.1",
4+
"license": "MIT",
5+
"type": "module",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"scripts": {
9+
"build": "tsc",
10+
"test": "vitest --typecheck",
11+
"test:headless": "vitest run --browser.headless --typecheck",
12+
"test:exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
13+
"lint": "eslint ./src"
14+
},
15+
"dependencies": {
16+
"@standard-schema/spec": "^1.0.0"
17+
},
18+
"devDependencies": {
19+
"@arethetypeswrong/cli": "^0.17.4",
20+
"@eslint/js": "^9.24.0",
21+
"@vitest/browser": "3.1.1",
22+
"eslint": "^9.24.0",
23+
"eslint-plugin-unicorn": "^58.0.0",
24+
"playwright": "^1.51.1",
25+
"typescript": "^5.8.3",
26+
"typescript-eslint": "^8.29.1",
27+
"vitest": "3.1.1",
28+
"zod": "^3.23.8"
29+
}
30+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { z } from "zod"
2+
import { defineStorage } from "./define-storage.js"
3+
import { test, assertType } from "vitest"
4+
5+
test("it's not possible to set a value that violates the schema", () => {
6+
// ARRANGE
7+
const { storage } = defineStorage({
8+
someKey: z.string(),
9+
})
10+
11+
// ACT & ASSERT
12+
// @ts-expect-error -- Cannot set a value that violates the schema
13+
assertType(storage.set("someKey", 1))
14+
})
15+
16+
test("it's not possible to access a non string key", () => {
17+
// ARRANGE
18+
const { storage } = defineStorage({
19+
someKey: z.string(),
20+
})
21+
22+
// ACT & ASSERT
23+
// @ts-expect-error -- Cannot access a non string key
24+
assertType(storage.get(1))
25+
})
26+
27+
test("it's not possible to access a non existent key", () => {
28+
// ARRANGE
29+
const { storage } = defineStorage({
30+
someKey: z.string(),
31+
})
32+
33+
// ACT & ASSERT
34+
// @ts-expect-error -- Cannot access a non existent key
35+
assertType(storage.get("some-non-existent-key"))
36+
})
37+
38+
test("the value has a type of string", () => {
39+
// ARRANGE
40+
const { storage } = defineStorage({
41+
someKey: z.string(),
42+
})
43+
44+
// ACT & ASSERT
45+
assertType<string | null>(storage.get("someKey"))
46+
})
47+
48+
test("the value has a type of boolean", () => {
49+
// ARRANGE
50+
const { storage } = defineStorage({
51+
someBooleanKey: z.boolean(),
52+
})
53+
54+
// ACT & ASSERT
55+
assertType<boolean | null>(storage.get("someBooleanKey"))
56+
})
57+
58+
test("the value has a type of number", () => {
59+
// ARRANGE
60+
const { storage } = defineStorage({
61+
someNumberKey: z.number(),
62+
})
63+
64+
// ACT & ASSERT
65+
assertType<number | null>(storage.get("someNumberKey"))
66+
})
67+
68+
test("value must have type of null", () => {
69+
// ARRANGE
70+
const { storage } = defineStorage({
71+
someNumberKey: z.number(),
72+
})
73+
74+
// ACT & ASSERT
75+
assertType<number | null>(storage.get("someNumberKey"))
76+
})
77+
78+
test("returns nothing when removing an item", () => {
79+
// ARRANGE
80+
const { storage } = defineStorage({
81+
someKey: z.string(),
82+
})
83+
84+
// ACT
85+
storage.remove("someKey")
86+
87+
// ASSERT
88+
assertType<void>(storage.remove("someKey"))
89+
})

0 commit comments

Comments
 (0)