datavolve 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +7 -0
- package/README.md +289 -0
- package/dist/index.d.mts +29 -0
- package/dist/index.mjs +52 -0
- package/package.json +63 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Pavel Grinchenko
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# datavolve
|
|
2
|
+
|
|
3
|
+
Tiny, type-safe evolutions for versioned data. Storage-agnostic, dependency-free, synchronous.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/datavolve)
|
|
6
|
+
[](https://github.com/psd-coder/datavolve/blob/main/packages/datavolve/package.json)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
`datavolve` brings an old persisted value forward to its latest shape through a chain of forward-only evolutions. Point it at whatever you persisted last year (localStorage, IndexedDB, a synced blob, a config file) and get back the shape your code expects today, with full type inference along the way.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- ✅ Full type inference through the evolution chain — no intermediate annotations
|
|
14
|
+
- ✅ Forward-only, pure `(prev) => next` transforms — no reverse evolutions, no side effects
|
|
15
|
+
- ✅ Total `run` — returns a discriminated result, never throws
|
|
16
|
+
- ✅ Storage-agnostic — the version comes in as an argument; `datavolve` never reads or writes it
|
|
17
|
+
- ✅ Zero dependencies, tiny (258 B), ESM-only
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# npm
|
|
23
|
+
npm install datavolve
|
|
24
|
+
|
|
25
|
+
# pnpm
|
|
26
|
+
pnpm add datavolve
|
|
27
|
+
|
|
28
|
+
# yarn
|
|
29
|
+
yarn add datavolve
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires an ESM environment. No peer dependencies.
|
|
33
|
+
|
|
34
|
+
## Core Concepts
|
|
35
|
+
|
|
36
|
+
Real apps change the shape of the data they persist. A returning user's browser can hold data written by a version of your app from six months ago. Without an evolution step that old value flows into new code and either crashes or, worse, silently corrupts state. `datavolve` is the small primitive that closes that gap: declare how each version differs from the last, and let it walk any stored value up to the current shape.
|
|
37
|
+
|
|
38
|
+
It is useful wherever the shape of persisted client data evolves over time:
|
|
39
|
+
|
|
40
|
+
- localStorage / sessionStorage state
|
|
41
|
+
- IndexedDB records
|
|
42
|
+
- A synced JSON blob or config file
|
|
43
|
+
- Any versioned value read back by newer code
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Step 1: Declare your evolutions
|
|
48
|
+
|
|
49
|
+
You add evolutions in order, starting at version `1`. Each one takes the previous version's value and returns the next. The input to the **first** evolution is `unknown` on purpose — it is genuinely untrusted data, and narrowing it is the evolution's job.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { datavolve } from "datavolve";
|
|
53
|
+
|
|
54
|
+
const settings = datavolve()
|
|
55
|
+
.add(1, (raw) => ({ theme: "light" })) // legacy -> v1
|
|
56
|
+
.add(2, (v1) => ({ ...v1, fontScale: 1 })) // v1 -> v2
|
|
57
|
+
.add(3, (v2) => ({ ...v2, theme: v2.theme === "light" ? "day" : "night" })); // v2 -> v3
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 2: Run against stored data
|
|
61
|
+
|
|
62
|
+
You tell `run` what version the incoming data is; `datavolve` never looks for a `version` field inside your value. Data with no version at all is version `0` and runs the whole chain.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const result = settings.run(storedValue, storedVersion);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Step 3: Handle the result
|
|
69
|
+
|
|
70
|
+
`run` returns a discriminated result, so the compiler makes you handle failure. Each error `code` carries exactly the fields relevant to it. On success the result carries the evolved `value` together with the `version` it is now at (always `latestVersion`), so you can persist the upgraded data in one place.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
if (result.success) {
|
|
74
|
+
useSettings(result.value); // typed as the v3 shape
|
|
75
|
+
save({ version: result.version, value: result.value }); // store them together
|
|
76
|
+
} else {
|
|
77
|
+
switch (result.error.code) {
|
|
78
|
+
case "ahead":
|
|
79
|
+
// stored data is newer than this build understands
|
|
80
|
+
promptUserToUpdateApp(result.error.latestVersion);
|
|
81
|
+
break;
|
|
82
|
+
case "malformed":
|
|
83
|
+
// fromVersion was negative or non-integer
|
|
84
|
+
resetToDefaults();
|
|
85
|
+
break;
|
|
86
|
+
case "failed":
|
|
87
|
+
// one of your evolutions threw; the original error is in `cause`
|
|
88
|
+
report(result.error.cause);
|
|
89
|
+
resetToDefaults();
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Narrowing Untrusted Input
|
|
96
|
+
|
|
97
|
+
The first evolution's input is `unknown`. Narrow it with a runtime check, not a cast. Either a small type guard (zero dependencies):
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const isRecord = (x: unknown): x is Record<string, unknown> =>
|
|
101
|
+
typeof x === "object" && x !== null;
|
|
102
|
+
|
|
103
|
+
datavolve().add(1, (legacy) => {
|
|
104
|
+
const old = isRecord(legacy) ? legacy : {};
|
|
105
|
+
return { theme: old["theme"] === "light" ? "light" : "dark" };
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
or a validator you already use (Valibot, Zod, etc.):
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import * as v from "valibot";
|
|
113
|
+
|
|
114
|
+
const legacySchema = v.object({ theme: v.optional(v.string()) });
|
|
115
|
+
|
|
116
|
+
datavolve().add(1, (legacy) => {
|
|
117
|
+
const old = v.parse(legacySchema, legacy); // throws on garbage -> reported as `failed`
|
|
118
|
+
return { theme: old.theme === "light" ? "light" : "dark" };
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A raw `as` would skip the check and let malformed legacy data through untyped — exactly what you don't want at this boundary.
|
|
123
|
+
|
|
124
|
+
## API
|
|
125
|
+
|
|
126
|
+
### `datavolve()`
|
|
127
|
+
|
|
128
|
+
Creates an empty, immutable datavolve instance.
|
|
129
|
+
|
|
130
|
+
### `.add(version, evolve)`
|
|
131
|
+
|
|
132
|
+
Appends an evolution and returns a **new** datavolve instance whose output type is inferred from `evolve`'s return value.
|
|
133
|
+
|
|
134
|
+
- `version` — a positive integer, sequential from `1`. Adding a non-sequential version throws immediately (a build-time programmer error), which also protects you from silently misaligning stored data if an evolution is deleted.
|
|
135
|
+
- `evolve` — `(prev) => next`. `prev` is `unknown` for version `1`, and the previous evolution's output type thereafter.
|
|
136
|
+
|
|
137
|
+
### `.run(data, fromVersion)`
|
|
138
|
+
|
|
139
|
+
Runs the applicable evolutions. **Total** — it never throws; an evolution that itself throws is caught and reported. Returns an `EvolveResult`.
|
|
140
|
+
|
|
141
|
+
- `data: unknown` — the stored value.
|
|
142
|
+
- `fromVersion: number` — the version `data` is currently at. Required.
|
|
143
|
+
|
|
144
|
+
### `.latestVersion`
|
|
145
|
+
|
|
146
|
+
The highest version datavolve knows about. Use it to stamp data on write-back:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
storage.setItem(
|
|
150
|
+
key,
|
|
151
|
+
JSON.stringify({ version: settings.latestVersion, value }),
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Types
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
type EvolveResult<T> =
|
|
159
|
+
| { success: true; value: T; version: number } // version = the version `value` is now at (always latestVersion)
|
|
160
|
+
| { success: false; error: EvolveError };
|
|
161
|
+
|
|
162
|
+
type EvolveError =
|
|
163
|
+
| { code: "ahead"; fromVersion: number; latestVersion: number }
|
|
164
|
+
| { code: "malformed"; fromVersion: number }
|
|
165
|
+
| { code: "failed"; failedVersion: number; cause: unknown };
|
|
166
|
+
|
|
167
|
+
type Datavolve<T>; // the datavolve instance, generic over its latest output type
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Version Behavior
|
|
171
|
+
|
|
172
|
+
| `fromVersion` | Result |
|
|
173
|
+
| --------------------------------- | ------------------------------------------------------------------- |
|
|
174
|
+
| `=== latestVersion` | success, value returned unchanged (no-op fast path) |
|
|
175
|
+
| `0 ≤ fromVersion < latestVersion` | success, evolutions `(fromVersion, latestVersion]` applied in order |
|
|
176
|
+
| `> latestVersion` | failure `ahead` |
|
|
177
|
+
| `< 0` or non-integer | failure `malformed` |
|
|
178
|
+
| an evolution throws | failure `failed` (with `cause`) |
|
|
179
|
+
|
|
180
|
+
## Validation
|
|
181
|
+
|
|
182
|
+
`datavolve` does **no** validation of its own — the inferred output type of `run` is a _claim_ each evolution makes, not a runtime guarantee that untrusted stored data matches. Make it a guarantee by driving each evolution with a schema. Define one schema per version (each reusing the previous where it can), and let every evolution validate the shape it receives and transform it to the next with [Valibot](https://valibot.dev)'s `transform` pipeline:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import * as v from "valibot";
|
|
186
|
+
|
|
187
|
+
// One schema per version — each reuses the previous one's entries where it can.
|
|
188
|
+
const settingsV1 = v.object({
|
|
189
|
+
theme: v.picklist(["light", "dark"]),
|
|
190
|
+
});
|
|
191
|
+
const settingsV2 = v.object({
|
|
192
|
+
...settingsV1.entries,
|
|
193
|
+
fontScale: v.number(),
|
|
194
|
+
});
|
|
195
|
+
const settingsV3 = v.object({
|
|
196
|
+
...settingsV2.entries,
|
|
197
|
+
sidebar: v.boolean(),
|
|
198
|
+
});
|
|
199
|
+
// v4 deprecates `sidebar`: omit the key, keep the rest.
|
|
200
|
+
const settingsV4 = v.omit(settingsV3, ["sidebar"]);
|
|
201
|
+
const settingsV5 = v.object({
|
|
202
|
+
...settingsV4.entries,
|
|
203
|
+
theme: v.picklist(["day", "night"]),
|
|
204
|
+
});
|
|
205
|
+
// update this when a new version is added to the chain
|
|
206
|
+
const settingsLatest = settingsV5;
|
|
207
|
+
|
|
208
|
+
type Settings = v.InferOutput<typeof settingsLatest>;
|
|
209
|
+
|
|
210
|
+
// Each evolution validates the previous shape and transforms it to the next.
|
|
211
|
+
const toV2 = v.pipe(
|
|
212
|
+
settingsV1,
|
|
213
|
+
v.transform((s) => ({ ...s, fontScale: 1 })),
|
|
214
|
+
);
|
|
215
|
+
const toV3 = v.pipe(
|
|
216
|
+
settingsV2,
|
|
217
|
+
v.transform((s) => ({ ...s, sidebar: true })),
|
|
218
|
+
);
|
|
219
|
+
const toV4 = v.pipe(
|
|
220
|
+
settingsV3,
|
|
221
|
+
v.transform(({ sidebar, ...rest }) => rest),
|
|
222
|
+
);
|
|
223
|
+
const toV5 = v.pipe(
|
|
224
|
+
settingsV4,
|
|
225
|
+
v.transform((s) => ({
|
|
226
|
+
...s,
|
|
227
|
+
theme: s.theme === "light" ? ("day" as const) : ("night" as const),
|
|
228
|
+
})),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const settings = datavolve()
|
|
232
|
+
.add(1, (raw) => v.parse(settingsV1, raw)) // legacy -> v1
|
|
233
|
+
.add(2, (prev) => v.parse(toV2, prev))
|
|
234
|
+
.add(3, (prev) => v.parse(toV3, prev))
|
|
235
|
+
.add(4, (prev) => v.parse(toV4, prev))
|
|
236
|
+
.add(5, (prev) => v.parse(toV5, prev));
|
|
237
|
+
|
|
238
|
+
const result = settings.run(storedValue, storedVersion);
|
|
239
|
+
if (result.success) {
|
|
240
|
+
const value: Settings = result.value; // inferred *and* runtime-checked
|
|
241
|
+
useSettings(value);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Every evolution parses its input, so a value is validated at whatever version it enters the chain — corrupt or unexpected data surfaces as a `failed` result (`v.parse` throws, and `run` turns any thrown error into `failed`). You handle it through the same branch you already handle, with no extra `try`/`catch`.
|
|
246
|
+
|
|
247
|
+
The only value not re-checked is one already at `latestVersion`: it skips the chain and is returned unchanged (the no-op fast path). That is the shape your app just wrote, so it is trusted.
|
|
248
|
+
|
|
249
|
+
## Persisting to localStorage
|
|
250
|
+
|
|
251
|
+
`datavolve` never touches storage — you own the `{ version, value }` envelope and decide where it lives. A typical `localStorage` read evolves the stored value up to the latest shape and re-stores it, so the next read is a no-op; a fresh write stamps the current `latestVersion`:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const KEY = "settings";
|
|
255
|
+
|
|
256
|
+
// Read: evolve stored data up to the latest shape, then re-store it.
|
|
257
|
+
function load(): Settings {
|
|
258
|
+
const raw = localStorage.getItem(KEY);
|
|
259
|
+
const stored = raw ? JSON.parse(raw) : { version: 0, value: null }; // no data -> version 0
|
|
260
|
+
|
|
261
|
+
const result = settings.run(stored.value, stored.version);
|
|
262
|
+
if (!result.success) return defaults; // reset on ahead / malformed / failed
|
|
263
|
+
|
|
264
|
+
// persist the upgraded shape so the next read skips the chain
|
|
265
|
+
localStorage.setItem(
|
|
266
|
+
KEY,
|
|
267
|
+
JSON.stringify({ version: result.version, value: result.value }),
|
|
268
|
+
);
|
|
269
|
+
return result.value;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Write: a fresh in-app change is already the latest shape, so stamp latestVersion.
|
|
273
|
+
function save(value: Settings): void {
|
|
274
|
+
localStorage.setItem(
|
|
275
|
+
KEY,
|
|
276
|
+
JSON.stringify({ version: settings.latestVersion, value }),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Design Notes
|
|
282
|
+
|
|
283
|
+
- **Forward-only.** This evolves persisted client state; you never roll a user's storage back, so there are no down evolutions.
|
|
284
|
+
- **Synchronous.** Persisted-state evolutions are pure shape transforms. Keeping `run` sync means it fits synchronous read paths. Need async evolutions? [Open an issue](https://github.com/psd-coder/datavolve/issues) — an async variant is ready to build if there is demand.
|
|
285
|
+
- **Result over throw.** A newer-than-expected stored value is expected control flow, not an exception. Returning a discriminated result makes handling every reason compiler-enforced.
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
[MIT](./LICENSE.md)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
type EvolveError = {
|
|
3
|
+
code: "ahead";
|
|
4
|
+
fromVersion: number;
|
|
5
|
+
latestVersion: number;
|
|
6
|
+
} | {
|
|
7
|
+
code: "malformed";
|
|
8
|
+
fromVersion: number;
|
|
9
|
+
} | {
|
|
10
|
+
code: "failed";
|
|
11
|
+
failedVersion: number;
|
|
12
|
+
cause: unknown;
|
|
13
|
+
};
|
|
14
|
+
type EvolveResult<T> = {
|
|
15
|
+
success: true;
|
|
16
|
+
value: T;
|
|
17
|
+
version: number;
|
|
18
|
+
} | {
|
|
19
|
+
success: false;
|
|
20
|
+
error: EvolveError;
|
|
21
|
+
};
|
|
22
|
+
type Datavolve<Out> = {
|
|
23
|
+
readonly latestVersion: number;
|
|
24
|
+
add<NewOut>(version: number, evolve: (prev: Out) => NewOut): Datavolve<NewOut>;
|
|
25
|
+
run(data: unknown, fromVersion: number): EvolveResult<Out>;
|
|
26
|
+
};
|
|
27
|
+
declare function datavolve(): Datavolve<unknown>;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Datavolve, EvolveError, EvolveResult, datavolve };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
function create(evolutions) {
|
|
3
|
+
const latestVersion = evolutions.length;
|
|
4
|
+
return {
|
|
5
|
+
latestVersion,
|
|
6
|
+
add(version, evolve) {
|
|
7
|
+
const expected = latestVersion + 1;
|
|
8
|
+
if (!Number.isInteger(version) || version !== expected) throw new Error(`datavolve: expected ${expected}, got ${version}`);
|
|
9
|
+
return create([...evolutions, evolve]);
|
|
10
|
+
},
|
|
11
|
+
run(data, fromVersion) {
|
|
12
|
+
if (!Number.isInteger(fromVersion) || fromVersion < 0) return {
|
|
13
|
+
success: false,
|
|
14
|
+
error: {
|
|
15
|
+
code: "malformed",
|
|
16
|
+
fromVersion
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
if (fromVersion > latestVersion) return {
|
|
20
|
+
success: false,
|
|
21
|
+
error: {
|
|
22
|
+
code: "ahead",
|
|
23
|
+
fromVersion,
|
|
24
|
+
latestVersion
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
let value = data;
|
|
28
|
+
for (let index = fromVersion; index < latestVersion; index++) try {
|
|
29
|
+
value = evolutions[index](value);
|
|
30
|
+
} catch (cause) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: {
|
|
34
|
+
code: "failed",
|
|
35
|
+
failedVersion: index + 1,
|
|
36
|
+
cause
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
value,
|
|
43
|
+
version: latestVersion
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function datavolve() {
|
|
49
|
+
return create([]);
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { datavolve };
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "datavolve",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Pavel Grinchenko <psdcoder@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "psd-coder/datavolve"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/*",
|
|
13
|
+
"package.json",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE.md"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"import": "./dist/index.mjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
27
|
+
"@size-limit/esbuild": "^12.1.0",
|
|
28
|
+
"@size-limit/file": "^12.0.1",
|
|
29
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
30
|
+
"oxfmt": "^0.58.0",
|
|
31
|
+
"oxlint": "^1.56.0",
|
|
32
|
+
"publint": "^0.3.18",
|
|
33
|
+
"size-limit": "^12.0.1",
|
|
34
|
+
"tsdown": "0.22.5",
|
|
35
|
+
"typescript": "^7.0.2",
|
|
36
|
+
"valibot": "^1.4.2",
|
|
37
|
+
"vitest": "4.1.10"
|
|
38
|
+
},
|
|
39
|
+
"size-limit": [
|
|
40
|
+
{
|
|
41
|
+
"path": ".size-check/index.mjs",
|
|
42
|
+
"limit": "258B"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "tsdown --watch",
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"build:size": "tsdown --minify --out-dir .size-check --no-dts",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:coverage": "vitest run --coverage",
|
|
51
|
+
"typecheck": "pnpm run /^typecheck:/",
|
|
52
|
+
"typecheck:src": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"typecheck:test": "tsc -p tsconfig.test.json --noEmit",
|
|
54
|
+
"lint": "pnpm run /^lint:/",
|
|
55
|
+
"lint:oxlint": "oxlint .",
|
|
56
|
+
"lint:types": "pnpm run typecheck",
|
|
57
|
+
"lint:size": "pnpm run build:size && size-limit",
|
|
58
|
+
"format": "pnpm run /^format:/",
|
|
59
|
+
"format:oxlint": "oxlint --fix --fix-suggestions .",
|
|
60
|
+
"format:oxfmt": "oxfmt src/",
|
|
61
|
+
"format:check": "oxfmt --check src/"
|
|
62
|
+
}
|
|
63
|
+
}
|