@storyshelf/storage-local 0.3.2 → 0.5.3

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/README.md CHANGED
@@ -18,13 +18,13 @@ npm install @storyshelf/storage-local
18
18
 
19
19
  ```ts
20
20
  import { createLocalStorage } from "@storyshelf/storage-local";
21
- import { createShelfRouter } from "@storyshelf/core";
21
+ import { createShelfApp } from "@storyshelf/app";
22
22
  import { createSqliteDatabase } from "@storyshelf/db-sqlite";
23
23
 
24
24
  const storage = createLocalStorage("./data");
25
25
  const database = createSqliteDatabase("./data/shelf.db");
26
26
 
27
- const app = createShelfRouter({ database, storage });
27
+ const app = createShelfApp({ database, storage });
28
28
  ```
29
29
 
30
30
  ## API
@@ -37,6 +37,6 @@ The returned adapter implements every method of the `StorageAdapter` interface (
37
37
 
38
38
  ## How it fits in
39
39
 
40
- `storage-local` is the default `storage` option for `createShelfRouter` in single-node deployments, storing screenshots, diff overlays, and storybook archives on disk. It implements the same `StorageAdapter` interface as `@storyshelf/storage-s3`, so moving to object storage later is a drop-in swap.
40
+ `storage-local` is the default `storage` option for `createShelfApp` in single-node deployments, storing screenshots, diff overlays, and storybook archives on disk. It implements the same `StorageAdapter` interface as `@storyshelf/storage-s3`, so moving to object storage later is a drop-in swap.
41
41
 
42
42
  See `docs/architecture.md` and ADR 0006.
package/dist/index.mjs CHANGED
@@ -6,28 +6,6 @@ import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promise
6
6
  import { dirname, join, relative, resolve, sep } from "node:path";
7
7
  import { pipeline } from "node:stream/promises";
8
8
  //#region src/index.ts
9
- async function pathExists(target) {
10
- try {
11
- await access(target);
12
- return true;
13
- } catch {
14
- return false;
15
- }
16
- }
17
- function toAbsolute(root, path) {
18
- const target = resolve(root, path);
19
- const rel = relative(root, target);
20
- if (rel === ".." || rel.startsWith(`..${sep}`)) throw new Error(`Path escapes storage directory: ${path}`);
21
- return target;
22
- }
23
- async function walk(root, dir) {
24
- const entries = await readdir(dir, { withFileTypes: true });
25
- return (await Promise.all(entries.map(async (entry) => {
26
- const full = join(dir, entry.name);
27
- if (entry.isDirectory()) return await walk(root, full);
28
- return [relative(root, full)];
29
- }))).flat();
30
- }
31
9
  /**
32
10
  * Create a local filesystem-backed StorageAdapter rooted at the given directory.
33
11
  *
@@ -45,15 +23,14 @@ function createLocalStorage(dataDir) {
45
23
  category: "storage"
46
24
  },
47
25
  lifecycle: {
48
- init: async () => {
26
+ setup: async () => {
49
27
  await mkdir(root, { recursive: true });
50
28
  },
29
+ teardown: async () => {
30
+ await Promise.resolve();
31
+ },
51
32
  health: async () => {
52
- const started = Date.now();
53
- return {
54
- ok: await pathExists(root),
55
- latencyMs: Date.now() - started
56
- };
33
+ return { ok: await pathExists(root) };
57
34
  }
58
35
  },
59
36
  async read(path) {
@@ -78,6 +55,28 @@ function createLocalStorage(dataDir) {
78
55
  ...buildStreamMethods(root)
79
56
  };
80
57
  }
58
+ async function pathExists(target) {
59
+ try {
60
+ await access(target);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+ function toAbsolute(root, path) {
67
+ const target = resolve(root, path);
68
+ const rel = relative(root, target);
69
+ if (rel === ".." || rel.startsWith(`..${sep}`)) throw new Error(`Path escapes storage directory: ${path}`);
70
+ return target;
71
+ }
72
+ async function walk(root, dir) {
73
+ const entries = await readdir(dir, { withFileTypes: true });
74
+ return (await Promise.all(entries.map(async (entry) => {
75
+ const full = join(dir, entry.name);
76
+ if (entry.isDirectory()) return await walk(root, full);
77
+ return [relative(root, full)];
78
+ }))).flat();
79
+ }
81
80
  /** Streaming reads/writes for a local storage root (split for lint limits). */
82
81
  function buildStreamMethods(root) {
83
82
  return {
@@ -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\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/**\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 init: async () => {\n await mkdir(root, { recursive: true });\n },\n health: async () => {\n const started = Date.now();\n const ok = await pathExists(root);\n return { ok, latencyMs: Date.now() - started };\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\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":";;;;;;;;AAQA,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;;;;;;;AAQA,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,MAAM,YAAY;IAChB,MAAM,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC;GACvC;GACA,QAAQ,YAAY;IAClB,MAAM,UAAU,KAAK,IAAI;IAEzB,OAAO;KAAE,IAAA,MADQ,WAAW,IAAI;KACnB,WAAW,KAAK,IAAI,IAAI;IAAQ;GAC/C;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;;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 { 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"}
package/package.json CHANGED
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "name": "@storyshelf/storage-local",
3
- "version": "0.3.2",
3
+ "version": "0.5.3",
4
4
  "description": "Local filesystem storage adapter for StoryShelf.",
5
+ "keywords": [
6
+ "filesystem",
7
+ "self-hosted",
8
+ "storage",
9
+ "storybook"
10
+ ],
5
11
  "homepage": "https://github.com/GuptaSiddhant/StoryShelf#readme",
6
12
  "bugs": {
7
13
  "url": "https://github.com/GuptaSiddhant/StoryShelf/issues"
@@ -45,17 +51,17 @@
45
51
  "prepublishOnly": "nub run build"
46
52
  },
47
53
  "dependencies": {
48
- "@storyshelf/core": "^0.3.2"
54
+ "@storyshelf/core": "0.5.3"
49
55
  },
50
56
  "devDependencies": {
51
- "@types/node": "catalog:",
52
- "@vitest/coverage-v8": "catalog:",
53
- "oxfmt": "catalog:",
54
- "oxlint": "catalog:",
55
- "oxlint-tsgolint": "catalog:",
56
- "tsdown": "catalog:",
57
- "typescript": "catalog:",
58
- "vitest": "catalog:"
57
+ "@types/node": "^26.0.0",
58
+ "@vitest/coverage-v8": "^4.0.0",
59
+ "oxfmt": "^0.65.0",
60
+ "oxlint": "^1.80.0",
61
+ "oxlint-tsgolint": "^7.0.2001",
62
+ "tsdown": "^0.22.14",
63
+ "typescript": "^7.0.2",
64
+ "vitest": "^5.0.0"
59
65
  },
60
66
  "jsr": {
61
67
  "runtimeCompat": {