@storyshelf/storage-local 0.6.2 → 0.8.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/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { StorageAdapter } from "@storyshelf/core/adapter/storage";
4
- //#region src/index.d.ts
5
4
  /**
6
5
  * Create a local filesystem-backed StorageAdapter rooted at the given directory.
7
6
  *
@@ -9,6 +8,5 @@ import { StorageAdapter } from "@storyshelf/core/adapter/storage";
9
8
  * @returns A StorageAdapter that reads and writes files under `dataDir`.
10
9
  */
11
10
  declare function createLocalStorage(dataDir: string): StorageAdapter;
12
- //#endregion
13
11
  export { createLocalStorage };
14
12
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,11 +1,12 @@
1
- import "node:path";
1
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { storageKey } from "./keys.mjs";
5
+ import { randomUUID } from "node:crypto";
4
6
  import { createReadStream, createWriteStream } from "node:fs";
5
- import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
6
- import { dirname, join, relative, resolve, sep } from "node:path";
7
+ import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
7
8
  import { pipeline } from "node:stream/promises";
8
- //#region src/index.ts
9
+ // src/index.ts
9
10
  /**
10
11
  * Create a local filesystem-backed StorageAdapter rooted at the given directory.
11
12
  *
@@ -17,7 +18,7 @@ function createLocalStorage(dataDir) {
17
18
  return {
18
19
  metadata: {
19
20
  name: "Local Storage",
20
- version: globalThis.__PKG_VERSION__ ?? "0.0.0",
21
+ version: "0.8.0",
21
22
  description: "Local filesystem storage adapter",
22
23
  kind: "local",
23
24
  category: "storage"
@@ -37,9 +38,9 @@ function createLocalStorage(dataDir) {
37
38
  return await readFile(toAbsolute(root, path));
38
39
  },
39
40
  async write(path, data) {
40
- const target = toAbsolute(root, path);
41
- await mkdir(dirname(target), { recursive: true });
42
- await writeFile(target, data);
41
+ await writeAtomically(toAbsolute(root, path), async (temp) => {
42
+ await writeFile(temp, data);
43
+ });
43
44
  },
44
45
  async delete(path) {
45
46
  await rm(toAbsolute(root, path), { force: true });
@@ -63,6 +64,23 @@ async function pathExists(target) {
63
64
  return false;
64
65
  }
65
66
  }
67
+ /**
68
+ * Write through a temp file in the target's directory, then rename it into place: a write cut
69
+ * short by a crash or kill never leaves a torn file at `target` (content-addressed screenshots
70
+ * trust an existing object, so a torn one would poison every later capture with those bytes).
71
+ * The temp name is a dotfile ending in `.tmp`, so it never looks like a stored screenshot.
72
+ */
73
+ async function writeAtomically(target, fill) {
74
+ await mkdir(dirname(target), { recursive: true });
75
+ const temp = join(dirname(target), `.${basename(target)}.${randomUUID()}.tmp`);
76
+ try {
77
+ await fill(temp);
78
+ await rename(temp, target);
79
+ } catch (error) {
80
+ await rm(temp, { force: true });
81
+ throw error;
82
+ }
83
+ }
66
84
  function toAbsolute(root, path) {
67
85
  const target = resolve(root, path);
68
86
  const rel = relative(root, target);
@@ -74,22 +92,16 @@ async function walk(root, dir) {
74
92
  return (await Promise.all(entries.map(async (entry) => {
75
93
  const full = join(dir, entry.name);
76
94
  if (entry.isDirectory()) return await walk(root, full);
77
- return [relative(root, full)];
95
+ return [storageKey(relative(root, full))];
78
96
  }))).flat();
79
97
  }
80
98
  /** Streaming reads/writes for a local storage root (split for lint limits). */
81
99
  function buildStreamMethods(root) {
82
100
  return {
83
101
  async writeStream(path, stream) {
84
- const target = toAbsolute(root, path);
85
- await mkdir(dirname(target), { recursive: true });
86
- const out = createWriteStream(target);
87
- try {
88
- await pipeline(stream, out);
89
- } catch (error) {
90
- await rm(target, { force: true });
91
- throw error;
92
- }
102
+ await writeAtomically(toAbsolute(root, path), async (temp) => {
103
+ await pipeline(stream, createWriteStream(temp));
104
+ });
93
105
  },
94
106
  async readStream(path) {
95
107
  const target = toAbsolute(root, path);
@@ -98,7 +110,6 @@ function buildStreamMethods(root) {
98
110
  }
99
111
  };
100
112
  }
101
- //#endregion
102
113
  export { createLocalStorage };
103
114
 
104
115
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport { createReadStream, createWriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { dirname, join, relative, resolve, sep } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/**\n * Create a local filesystem-backed StorageAdapter rooted at the given directory.\n *\n * @param dataDir - Root directory in which all stored files live.\n * @returns A StorageAdapter that reads and writes files under `dataDir`.\n */\nexport function createLocalStorage(dataDir: string): StorageAdapter {\n const root = resolve(dataDir);\n\n return {\n metadata: {\n name: \"Local Storage\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"Local filesystem storage adapter\",\n kind: \"local\",\n category: \"storage\",\n },\n lifecycle: {\n setup: async () => {\n await mkdir(root, { recursive: true });\n },\n teardown: async () => {\n // No handles held — nothing to destroy.\n await Promise.resolve();\n },\n health: async () => {\n const ok = await pathExists(root);\n return { ok };\n },\n },\n async read(path) {\n return await readFile(toAbsolute(root, path));\n },\n async write(path, data) {\n const target = toAbsolute(root, path);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, data);\n },\n async delete(path) {\n await rm(toAbsolute(root, path), { force: true });\n },\n async exists(path) {\n return await pathExists(toAbsolute(root, path));\n },\n async list(prefix) {\n const dir = toAbsolute(root, prefix);\n if (!(await pathExists(dir))) {\n return [];\n }\n return walk(root, dir);\n },\n ...buildStreamMethods(root),\n };\n}\n\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await access(target);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction toAbsolute(root: string, path: string): string {\n const target = resolve(root, path);\n const rel = relative(root, target);\n if (rel === \"..\" || rel.startsWith(`..${sep}`)) {\n throw new Error(`Path escapes storage directory: ${path}`);\n }\n return target;\n}\n\nasync function walk(root: string, dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n return await walk(root, full);\n }\n return [relative(root, full)];\n }),\n );\n return nested.flat();\n}\n\n/** Streaming reads/writes for a local storage root (split for lint limits). */\nfunction buildStreamMethods(root: string): Pick<StorageAdapter, \"writeStream\" | \"readStream\"> {\n return {\n async writeStream(path, stream) {\n const target = toAbsolute(root, path);\n await mkdir(dirname(target), { recursive: true });\n const out = createWriteStream(target);\n try {\n await pipeline(stream, out);\n } catch (error) {\n await rm(target, { force: true });\n throw error;\n }\n },\n async readStream(path) {\n const target = toAbsolute(root, path);\n if (!(await pathExists(target))) {\n throw new Error(`No object at \"${path}\"`);\n }\n return createReadStream(target);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,SAAiC;CAClE,MAAM,OAAO,QAAQ,OAAO;CAE5B,OAAO;EACL,UAAU;GACR,MAAM;GACN,SAAU,WAAuD,mBAAmB;GACpF,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA,WAAW;GACT,OAAO,YAAY;IACjB,MAAM,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC;GACvC;GACA,UAAU,YAAY;IAEpB,MAAM,QAAQ,QAAQ;GACxB;GACA,QAAQ,YAAY;IAElB,OAAO,EAAE,IAAA,MADQ,WAAW,IAAI,EACpB;GACd;EACF;EACA,MAAM,KAAK,MAAM;GACf,OAAO,MAAM,SAAS,WAAW,MAAM,IAAI,CAAC;EAC9C;EACA,MAAM,MAAM,MAAM,MAAM;GACtB,MAAM,SAAS,WAAW,MAAM,IAAI;GACpC,MAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;GAChD,MAAM,UAAU,QAAQ,IAAI;EAC9B;EACA,MAAM,OAAO,MAAM;GACjB,MAAM,GAAG,WAAW,MAAM,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;EAClD;EACA,MAAM,OAAO,MAAM;GACjB,OAAO,MAAM,WAAW,WAAW,MAAM,IAAI,CAAC;EAChD;EACA,MAAM,KAAK,QAAQ;GACjB,MAAM,MAAM,WAAW,MAAM,MAAM;GACnC,IAAI,CAAE,MAAM,WAAW,GAAG,GACxB,OAAO,CAAC;GAEV,OAAO,KAAK,MAAM,GAAG;EACvB;EACA,GAAG,mBAAmB,IAAI;CAC5B;AACF;AAEA,eAAe,WAAW,QAAkC;CAC1D,IAAI;EACF,MAAM,OAAO,MAAM;EACnB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,WAAW,MAAc,MAAsB;CACtD,MAAM,SAAS,QAAQ,MAAM,IAAI;CACjC,MAAM,MAAM,SAAS,MAAM,MAAM;CACjC,IAAI,QAAQ,QAAQ,IAAI,WAAW,KAAK,KAAK,GAC3C,MAAM,IAAI,MAAM,mCAAmC,MAAM;CAE3D,OAAO;AACT;AAEA,eAAe,KAAK,MAAc,KAAgC;CAChE,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAU1D,QAAO,MATc,QAAQ,IAC3B,QAAQ,IAAI,OAAO,UAAU;EAC3B,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI;EACjC,IAAI,MAAM,YAAY,GACpB,OAAO,MAAM,KAAK,MAAM,IAAI;EAE9B,OAAO,CAAC,SAAS,MAAM,IAAI,CAAC;CAC9B,CAAC,CACH,EAAA,CACc,KAAK;AACrB;;AAGA,SAAS,mBAAmB,MAAkE;CAC5F,OAAO;EACL,MAAM,YAAY,MAAM,QAAQ;GAC9B,MAAM,SAAS,WAAW,MAAM,IAAI;GACpC,MAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;GAChD,MAAM,MAAM,kBAAkB,MAAM;GACpC,IAAI;IACF,MAAM,SAAS,QAAQ,GAAG;GAC5B,SAAS,OAAO;IACd,MAAM,GAAG,QAAQ,EAAE,OAAO,KAAK,CAAC;IAChC,MAAM;GACR;EACF;EACA,MAAM,WAAW,MAAM;GACrB,MAAM,SAAS,WAAW,MAAM,IAAI;GACpC,IAAI,CAAE,MAAM,WAAW,MAAM,GAC3B,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE;GAE1C,OAAO,iBAAiB,MAAM;EAChC;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport { randomUUID } from \"node:crypto\";\nimport { createReadStream, createWriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, relative, resolve, sep } from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\nimport { storageKey } from \"./keys.ts\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/**\n * Create a local filesystem-backed StorageAdapter rooted at the given directory.\n *\n * @param dataDir - Root directory in which all stored files live.\n * @returns A StorageAdapter that reads and writes files under `dataDir`.\n */\nexport function createLocalStorage(dataDir: string): StorageAdapter {\n const root = resolve(dataDir);\n\n return {\n metadata: {\n name: \"Local Storage\",\n version: typeof __PKG_VERSION__ === \"string\" ? __PKG_VERSION__ : \"0.0.0\",\n description: \"Local filesystem storage adapter\",\n kind: \"local\",\n category: \"storage\",\n },\n lifecycle: {\n setup: async () => {\n await mkdir(root, { recursive: true });\n },\n teardown: async () => {\n // No handles held — nothing to destroy.\n await Promise.resolve();\n },\n health: async () => {\n const ok = await pathExists(root);\n return { ok };\n },\n },\n async read(path) {\n return await readFile(toAbsolute(root, path));\n },\n async write(path, data) {\n await writeAtomically(toAbsolute(root, path), async (temp) => {\n await writeFile(temp, data);\n });\n },\n async delete(path) {\n await rm(toAbsolute(root, path), { force: true });\n },\n async exists(path) {\n return await pathExists(toAbsolute(root, path));\n },\n async list(prefix) {\n const dir = toAbsolute(root, prefix);\n if (!(await pathExists(dir))) {\n return [];\n }\n return walk(root, dir);\n },\n ...buildStreamMethods(root),\n };\n}\n\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await access(target);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Write through a temp file in the target's directory, then rename it into place: a write cut\n * short by a crash or kill never leaves a torn file at `target` (content-addressed screenshots\n * trust an existing object, so a torn one would poison every later capture with those bytes).\n * The temp name is a dotfile ending in `.tmp`, so it never looks like a stored screenshot.\n */\nasync function writeAtomically(\n target: string,\n fill: (temp: string) => Promise<void>,\n): Promise<void> {\n await mkdir(dirname(target), { recursive: true });\n const temp = join(dirname(target), `.${basename(target)}.${randomUUID()}.tmp`);\n try {\n await fill(temp);\n await rename(temp, target);\n } catch (error) {\n await rm(temp, { force: true });\n throw error;\n }\n}\n\nfunction toAbsolute(root: string, path: string): string {\n const target = resolve(root, path);\n const rel = relative(root, target);\n if (rel === \"..\" || rel.startsWith(`..${sep}`)) {\n throw new Error(`Path escapes storage directory: ${path}`);\n }\n return target;\n}\n\nasync function walk(root: string, dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const nested = await Promise.all(\n entries.map(async (entry) => {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n return await walk(root, full);\n }\n return [storageKey(relative(root, full))];\n }),\n );\n return nested.flat();\n}\n\n/** Streaming reads/writes for a local storage root (split for lint limits). */\nfunction buildStreamMethods(root: string): Pick<StorageAdapter, \"writeStream\" | \"readStream\"> {\n return {\n async writeStream(path, stream) {\n await writeAtomically(toAbsolute(root, path), async (temp) => {\n await pipeline(stream, createWriteStream(temp));\n });\n },\n async readStream(path) {\n const target = toAbsolute(root, path);\n if (!(await pathExists(target))) {\n throw new Error(`No object at \"${path}\"`);\n }\n return createReadStream(target);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,SAAgB,mBAAmB,SAAiC;CAClE,MAAM,OAAO,QAAQ,OAAO;CAE5B,OAAO;EACL,UAAU;GACR,MAAM;GACN,SAAA;GACA,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA,WAAW;GACT,OAAO,YAAY;IACjB,MAAM,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC;GACvC;GACA,UAAU,YAAY;IAEpB,MAAM,QAAQ,QAAQ;GACxB;GACA,QAAQ,YAAY;IAElB,OAAO,EAAE,IAAA,MADQ,WAAW,IAAI,EACpB;GACd;EACF;EACA,MAAM,KAAK,MAAM;GACf,OAAO,MAAM,SAAS,WAAW,MAAM,IAAI,CAAC;EAC9C;EACA,MAAM,MAAM,MAAM,MAAM;GACtB,MAAM,gBAAgB,WAAW,MAAM,IAAI,GAAG,OAAO,SAAS;IAC5D,MAAM,UAAU,MAAM,IAAI;GAC5B,CAAC;EACH;EACA,MAAM,OAAO,MAAM;GACjB,MAAM,GAAG,WAAW,MAAM,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;EAClD;EACA,MAAM,OAAO,MAAM;GACjB,OAAO,MAAM,WAAW,WAAW,MAAM,IAAI,CAAC;EAChD;EACA,MAAM,KAAK,QAAQ;GACjB,MAAM,MAAM,WAAW,MAAM,MAAM;GACnC,IAAI,CAAE,MAAM,WAAW,GAAG,GACxB,OAAO,CAAC;GAEV,OAAO,KAAK,MAAM,GAAG;EACvB;EACA,GAAG,mBAAmB,IAAI;CAC5B;AACF;AAEA,eAAe,WAAW,QAAkC;CAC1D,IAAI;EACF,MAAM,OAAO,MAAM;EACnB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,eAAe,gBACb,QACA,MACe;CACf,MAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;CAChD,MAAM,OAAO,KAAK,QAAQ,MAAM,GAAG,IAAI,SAAS,MAAM,EAAE,GAAG,WAAW,EAAE,KAAK;CAC7E,IAAI;EACF,MAAM,KAAK,IAAI;EACf,MAAM,OAAO,MAAM,MAAM;CAC3B,SAAS,OAAO;EACd,MAAM,GAAG,MAAM,EAAE,OAAO,KAAK,CAAC;EAC9B,MAAM;CACR;AACF;AAEA,SAAS,WAAW,MAAc,MAAsB;CACtD,MAAM,SAAS,QAAQ,MAAM,IAAI;CACjC,MAAM,MAAM,SAAS,MAAM,MAAM;CACjC,IAAI,QAAQ,QAAQ,IAAI,WAAW,KAAK,KAAK,GAC3C,MAAM,IAAI,MAAM,mCAAmC,MAAM;CAE3D,OAAO;AACT;AAEA,eAAe,KAAK,MAAc,KAAgC;CAChE,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAU1D,QAAO,MATc,QAAQ,IAC3B,QAAQ,IAAI,OAAO,UAAU;EAC3B,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI;EACjC,IAAI,MAAM,YAAY,GACpB,OAAO,MAAM,KAAK,MAAM,IAAI;EAE9B,OAAO,CAAC,WAAW,SAAS,MAAM,IAAI,CAAC,CAAC;CAC1C,CAAC,CACH,EAAA,CACc,KAAK;AACrB;;AAGA,SAAS,mBAAmB,MAAkE;CAC5F,OAAO;EACL,MAAM,YAAY,MAAM,QAAQ;GAC9B,MAAM,gBAAgB,WAAW,MAAM,IAAI,GAAG,OAAO,SAAS;IAC5D,MAAM,SAAS,QAAQ,kBAAkB,IAAI,CAAC;GAChD,CAAC;EACH;EACA,MAAM,WAAW,MAAM;GACrB,MAAM,SAAS,WAAW,MAAM,IAAI;GACpC,IAAI,CAAE,MAAM,WAAW,MAAM,GAC3B,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE;GAE1C,OAAO,iBAAiB,MAAM;EAChC;CACF;AACF"}
package/dist/keys.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import { sep } from "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ // src/keys.ts
5
+ /** Storage keys for local files: `/`-separated on every host. */
6
+ /**
7
+ * A listed file's storage key. Keys are always `/`-separated, whatever the host's path separator,
8
+ * so `list()` returns the same strings `write()` was given and the database stores (on Windows,
9
+ * `relative()` yields `p1\content\x.png`, which would match no reference).
10
+ */
11
+ function storageKey(relativePath, separator = sep) {
12
+ return relativePath.split(separator).join("/");
13
+ }
14
+ export { storageKey };
15
+
16
+ //# sourceMappingURL=keys.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.mjs","names":[],"sources":["../src/keys.ts"],"sourcesContent":["/** Storage keys for local files: `/`-separated on every host. */\nimport { sep } from \"node:path\";\n\n/**\n * A listed file's storage key. Keys are always `/`-separated, whatever the host's path separator,\n * so `list()` returns the same strings `write()` was given and the database stores (on Windows,\n * `relative()` yields `p1\\content\\x.png`, which would match no reference).\n */\nexport function storageKey(relativePath: string, separator: string = sep): string {\n return relativePath.split(separator).join(\"/\");\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,WAAW,cAAsB,YAAoB,KAAa;CAChF,OAAO,aAAa,MAAM,SAAS,CAAC,CAAC,KAAK,GAAG;AAC/C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storyshelf/storage-local",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Local filesystem storage adapter for StoryShelf.",
5
5
  "keywords": [
6
6
  "filesystem",
@@ -26,9 +26,6 @@
26
26
  "dist"
27
27
  ],
28
28
  "type": "module",
29
- "engines": {
30
- "node": ">=24"
31
- },
32
29
  "sideEffects": false,
33
30
  "types": "./dist/index.d.mts",
34
31
  "exports": {
@@ -54,7 +51,7 @@
54
51
  "prepublishOnly": "nub run build"
55
52
  },
56
53
  "dependencies": {
57
- "@storyshelf/core": "0.6.2"
54
+ "@storyshelf/core": "0.8.0"
58
55
  },
59
56
  "devDependencies": {
60
57
  "@types/node": "^26.0.0",
@@ -64,7 +61,10 @@
64
61
  "oxlint-tsgolint": "^7.0.2001",
65
62
  "tsdown": "^0.22.14",
66
63
  "typescript": "^7.0.2",
67
- "vitest": "^5.0.0"
64
+ "vitest": "^5.0.5"
65
+ },
66
+ "engines": {
67
+ "node": ">=24"
68
68
  },
69
69
  "jsr": {
70
70
  "runtimeCompat": {