@bunit/storage 0.0.0 → 0.0.2

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 ADDED
@@ -0,0 +1,80 @@
1
+ # @bunit/storage
2
+
3
+ ![npm version](https://img.shields.io/npm/v/@bunit/storage)
4
+ ![npm downloads](https://img.shields.io/npm/dw/@bunit/storage)
5
+ ![npm license](https://img.shields.io/npm/l/@bunit/storage)
6
+
7
+ > 🔌 Native Bun drivers for [**unstorage**](https://unstorage.unjs.io) - Filesystem, Redis, and S3.
8
+
9
+ ## Features
10
+
11
+ - ⚡️ **Native Bun APIs** - Zero abstraction overhead using Bun's built-in clients
12
+ - 🔌 **Unstorage Compatible** - Drop-in drivers for any unstorage setup
13
+ - 🎯 **Type-Safe** - Full TypeScript support with proper types
14
+ - 📦 **Zero Runtime Dependencies** - Uses only Bun's native capabilities
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ bun add @bunit/storage unstorage
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Filesystem Driver
25
+
26
+ ```typescript
27
+ import { createStorage } from "unstorage";
28
+ import fsDriver from "@bunit/storage/drivers/fs";
29
+
30
+ const storage = createStorage({
31
+ driver: fsDriver({
32
+ base: "./data",
33
+ ignore: ["node_modules", ".git"],
34
+ }),
35
+ });
36
+
37
+ await storage.setItem("config:user", JSON.stringify({ name: "Alice" }));
38
+ const data = await storage.getItem("config:user");
39
+ ```
40
+
41
+ ### Redis Driver
42
+
43
+ ```typescript
44
+ import { createStorage } from "unstorage";
45
+ import redisDriver from "@bunit/storage/drivers/redis";
46
+
47
+ const storage = createStorage({
48
+ driver: redisDriver({
49
+ url: "redis://localhost:6379",
50
+ base: "app:cache",
51
+ ttl: 3600, // 1 hour default TTL
52
+ }),
53
+ });
54
+
55
+ await storage.setItem("session:123", "data");
56
+ ```
57
+
58
+ ### S3 Driver
59
+
60
+ ```typescript
61
+ import { createStorage } from "unstorage";
62
+ import s3Driver from "@bunit/storage/drivers/s3";
63
+
64
+ const storage = createStorage({
65
+ driver: s3Driver({
66
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
67
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
68
+ bucket: "my-bucket",
69
+ region: "us-east-1",
70
+ endpoint: "https://s3.us-east-1.amazonaws.com",
71
+ base: "prefix/",
72
+ }),
73
+ });
74
+
75
+ await storage.setItem("uploads/image.png", buffer);
76
+ ```
77
+
78
+ ## License
79
+
80
+ [MIT](../../LICENSE) © [Demo Macro](https://www.demomacro.com/)
@@ -1,6 +1,7 @@
1
+ import type { Driver } from "unstorage";
1
2
  export interface FSDriverOptions {
2
3
  base?: string;
3
4
  ignore?: string | string[];
4
5
  }
5
- declare const _default: (opts: FSDriverOptions) => import("unstorage").Driver<FSDriverOptions, never>;
6
+ declare const _default: (opts: FSDriverOptions) => Driver<FSDriverOptions, any>;
6
7
  export default _default;
@@ -1,5 +1,5 @@
1
1
  import { defineDriver, normalizeKey } from "unstorage";
2
- import { mkdir } from "node:fs/promises";
2
+ import { mkdir, rm } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { Glob } from "bun";
5
5
  export default defineDriver((options = {}) => {
@@ -7,29 +7,50 @@ export default defineDriver((options = {}) => {
7
7
  return {
8
8
  name: "fs",
9
9
  options,
10
- hasItem(key) {
10
+ hasItem(key, _opts) {
11
11
  const path = join(base, key.replace(/:/g, "/"));
12
12
  return Bun.file(path).exists();
13
13
  },
14
- async getItem(key) {
14
+ async getItem(key, _opts) {
15
15
  const path = join(base, key.replace(/:/g, "/")), file = Bun.file(path);
16
16
  if (!await file.exists())
17
17
  return null;
18
18
  return await file.text();
19
19
  },
20
- async setItem(key, value) {
20
+ async getItemRaw(key, _opts) {
21
+ const path = join(base, key.replace(/:/g, "/")), file = Bun.file(path);
22
+ if (!await file.exists())
23
+ return null;
24
+ return await file.arrayBuffer();
25
+ },
26
+ async setItem(key, value, _opts) {
27
+ const path = join(base, key.replace(/:/g, "/"));
28
+ await mkdir(dirname(path), { recursive: !0 });
29
+ await Bun.write(path, value);
30
+ },
31
+ async setItemRaw(key, value, _opts) {
21
32
  const path = join(base, key.replace(/:/g, "/"));
22
33
  await mkdir(dirname(path), { recursive: !0 });
23
34
  await Bun.write(path, value);
24
35
  },
25
- async removeItem(key) {
36
+ async removeItem(key, _opts) {
26
37
  const path = join(base, key.replace(/:/g, "/"));
27
38
  await Bun.file(path).delete();
28
39
  },
29
- async getKeys() {
30
- const glob = new Glob("**/*"), keys = [];
31
- for await (const file of glob.scan(base)) {
32
- const key = normalizeKey(file);
40
+ async getMeta(key, _opts) {
41
+ const path = join(base, key.replace(/:/g, "/"));
42
+ if (!await Bun.file(path).exists())
43
+ return null;
44
+ const stat = await Bun.file(path).stat();
45
+ return {
46
+ mtime: stat.mtime,
47
+ size: stat.size
48
+ };
49
+ },
50
+ async getKeys(baseKey, _opts) {
51
+ const glob = new Glob("**/*"), keys = [], scanPath = baseKey ? join(base, baseKey.replace(/:/g, "/")) : base;
52
+ for await (const file of glob.scan(scanPath)) {
53
+ const relativePath = baseKey ? file.replace(baseKey.replace(/:/g, "/") + "/", "") : file, key = normalizeKey(relativePath);
33
54
  if (ignore.length > 0) {
34
55
  if (Array.isArray(ignore) ? ignore.some((pattern) => file.includes(pattern)) : file.includes(ignore))
35
56
  continue;
@@ -38,12 +59,11 @@ export default defineDriver((options = {}) => {
38
59
  }
39
60
  return keys;
40
61
  },
41
- async clear() {
42
- const glob = new Glob("**/*");
43
- for await (const file of glob.scan(base)) {
44
- const path = join(base, file);
45
- await Bun.file(path).delete();
46
- }
62
+ async clear(baseKey, _opts) {
63
+ const targetPath = baseKey ? join(base, baseKey.replace(/:/g, "/")) : base;
64
+ try {
65
+ await rm(targetPath, { recursive: !0, force: !0 });
66
+ } catch {}
47
67
  },
48
68
  async dispose() {}
49
69
  };
@@ -0,0 +1,20 @@
1
+ import type { Driver } from "unstorage";
2
+ import { RedisClient, type RedisOptions } from "bun";
3
+ export interface RedisDriverOptions extends RedisOptions {
4
+ /**
5
+ * Connection URL for Redis.
6
+ * Format: `redis://username:password@localhost:6379`
7
+ * Defaults to `redis://localhost:6379`
8
+ */
9
+ url?: string;
10
+ /**
11
+ * Optional prefix to use for all keys.
12
+ */
13
+ base?: string;
14
+ /**
15
+ * Default TTL for all items in seconds.
16
+ */
17
+ ttl?: number;
18
+ }
19
+ declare const _default: (opts: RedisDriverOptions) => Driver<RedisDriverOptions, RedisClient>;
20
+ export default _default;
@@ -0,0 +1,94 @@
1
+ import { defineDriver, normalizeKey } from "unstorage";
2
+ import { RedisClient } from "bun";
3
+ export default defineDriver((options = {}) => {
4
+ let client;
5
+ const getClient = () => {
6
+ if (!client)
7
+ client = new RedisClient(options.url, options);
8
+ return client;
9
+ }, base = (options.base || "").replace(/:$/, ""), p = (key) => base ? `${base}:${key}` : key, d = (key) => base ? key.replace(`${base}:`, "") : key;
10
+ return {
11
+ name: "redis",
12
+ options,
13
+ getInstance: getClient,
14
+ async hasItem(key, _opts) {
15
+ const exists = await getClient().exists(p(key));
16
+ return typeof exists === "number" ? exists > 0 : exists;
17
+ },
18
+ async getItem(key, _opts) {
19
+ return await getClient().get(p(key)) ?? null;
20
+ },
21
+ async getItems(items, _commonOpts) {
22
+ const keys = items.map((item) => p(item.key)), values = await getClient().mget(...keys);
23
+ return keys.map((key, index) => ({
24
+ key: d(key),
25
+ value: values[index] ?? null
26
+ }));
27
+ },
28
+ async getItemRaw(key, _opts) {
29
+ return await getClient().getBuffer(p(key));
30
+ },
31
+ async setItem(key, value, _opts) {
32
+ const client = getClient();
33
+ if (options.ttl) {
34
+ await client.set(p(key), value);
35
+ await client.expire(p(key), options.ttl);
36
+ } else
37
+ await client.set(p(key), value);
38
+ },
39
+ async setItems(items, _opts) {
40
+ const client = getClient(), pipeline = [];
41
+ for (const item of items) {
42
+ pipeline.push(client.set(p(item.key), item.value));
43
+ if (options.ttl)
44
+ pipeline.push(client.expire(p(item.key), options.ttl));
45
+ }
46
+ await Promise.allSettled(pipeline);
47
+ },
48
+ async setItemRaw(key, value, _opts) {
49
+ const client = getClient();
50
+ if (typeof value === "string")
51
+ await client.set(p(key), value);
52
+ else
53
+ await client.set(p(key), value);
54
+ if (options.ttl)
55
+ await client.expire(p(key), options.ttl);
56
+ },
57
+ async removeItem(key, _opts) {
58
+ await getClient().del(p(key));
59
+ },
60
+ async getMeta(key, _opts) {
61
+ const ttl = await getClient().ttl(p(key));
62
+ if (!await getClient().exists(p(key)))
63
+ return null;
64
+ return {
65
+ ttl: ttl > 0 ? ttl : void 0
66
+ };
67
+ },
68
+ async getKeys(baseKey, _opts) {
69
+ const keys = [];
70
+ let cursor = 0;
71
+ const pattern = p(baseKey + "*");
72
+ do {
73
+ const result = await getClient().scan(cursor, "MATCH", pattern);
74
+ cursor = typeof result[0] === "number" ? result[0] : parseInt(result[0], 10);
75
+ const scanKeys = result[1];
76
+ keys.push(...scanKeys);
77
+ } while (cursor !== 0);
78
+ return keys.map((key) => normalizeKey(d(key)));
79
+ },
80
+ async clear(base, _opts) {
81
+ const keys = await this.getKeys(base, _opts);
82
+ if (keys.length === 0)
83
+ return;
84
+ const client = getClient(), keysToDelete = keys.map((key) => p(key));
85
+ if (keysToDelete.length === 1)
86
+ await client.del(keysToDelete[0]);
87
+ else
88
+ await client.del(...keysToDelete);
89
+ },
90
+ async dispose() {
91
+ client = void 0;
92
+ }
93
+ };
94
+ });
@@ -0,0 +1,10 @@
1
+ import type { Driver } from "unstorage";
2
+ import { S3Client, type S3Options } from "bun";
3
+ export interface S3DriverOptions extends S3Options {
4
+ /**
5
+ * Optional prefix to use for all keys.
6
+ */
7
+ base?: string;
8
+ }
9
+ declare const _default: (opts: S3DriverOptions) => Driver<S3DriverOptions, S3Client>;
10
+ export default _default;
@@ -0,0 +1,96 @@
1
+ import { defineDriver, normalizeKey } from "unstorage";
2
+ import { S3Client } from "bun";
3
+ export default defineDriver((options) => {
4
+ let client;
5
+ const getClient = () => {
6
+ if (!client)
7
+ client = new S3Client(options);
8
+ return client;
9
+ }, base = (options.base || "").replace(/\/$/, ""), p = (key) => base ? `${base}/${normalizeKey(key)}` : normalizeKey(key), d = (key) => base ? key.replace(`${base}/`, "") : key;
10
+ return {
11
+ name: "s3",
12
+ options,
13
+ getInstance: getClient,
14
+ async hasItem(key, _opts) {
15
+ try {
16
+ await getClient().file(p(key)).exists();
17
+ return !0;
18
+ } catch {
19
+ return !1;
20
+ }
21
+ },
22
+ async getItem(key, _opts) {
23
+ try {
24
+ return await getClient().file(p(key)).text();
25
+ } catch {
26
+ return null;
27
+ }
28
+ },
29
+ async getItemRaw(key, _opts) {
30
+ try {
31
+ return await getClient().file(p(key)).bytes();
32
+ } catch {
33
+ return null;
34
+ }
35
+ },
36
+ async setItem(key, value, _opts) {
37
+ await getClient().file(p(key)).write(value);
38
+ },
39
+ async setItemRaw(key, value, _opts) {
40
+ await getClient().file(p(key)).write(value);
41
+ },
42
+ async removeItem(key, _opts) {
43
+ await getClient().file(p(key)).delete();
44
+ },
45
+ async getMeta(key, _opts) {
46
+ try {
47
+ const result = await S3Client.list({ prefix: p(key) }, options);
48
+ if (result.contents && result.contents.length > 0) {
49
+ const object = result.contents[0];
50
+ return {
51
+ size: object.size,
52
+ mtime: object.lastModified ? new Date(object.lastModified) : void 0,
53
+ etag: object.eTag
54
+ };
55
+ }
56
+ return null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ },
61
+ async getKeys(baseKey, _opts) {
62
+ const keys = [];
63
+ let startAfter = void 0;
64
+ const prefix = p(baseKey || "");
65
+ let hasMore = !0;
66
+ while (hasMore) {
67
+ const result = await S3Client.list({
68
+ prefix,
69
+ startAfter,
70
+ maxKeys: 1000
71
+ }, options);
72
+ if (result.contents)
73
+ for (const object of result.contents) {
74
+ const relativeKey = d(object.key);
75
+ keys.push(normalizeKey(relativeKey));
76
+ }
77
+ if (result.isTruncated && result.contents && result.contents.length > 0)
78
+ startAfter = result.contents[result.contents.length - 1].key;
79
+ else
80
+ hasMore = !1;
81
+ }
82
+ return keys;
83
+ },
84
+ async clear(baseKey, _opts) {
85
+ const keys = await this.getKeys(baseKey, _opts);
86
+ if (keys.length === 0)
87
+ return;
88
+ await Promise.allSettled(keys.map(async (key) => {
89
+ await getClient().file(p(key)).delete();
90
+ }));
91
+ },
92
+ async dispose() {
93
+ client = void 0;
94
+ }
95
+ };
96
+ });
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "@bunit/storage",
3
- "version": "0.0.0",
4
- "description": "",
5
- "keywords": [],
3
+ "version": "0.0.2",
4
+ "description": "Universal storage abstraction with native Bun drivers for filesystem, Redis, and S3",
5
+ "keywords": [
6
+ "bun",
7
+ "cloudflare",
8
+ "driver",
9
+ "filesystem",
10
+ "r2",
11
+ "redis",
12
+ "s3",
13
+ "storage",
14
+ "unstorage"
15
+ ],
6
16
  "homepage": "https://github.com/DemoMacro/BunIt#readme",
7
17
  "bugs": {
8
18
  "url": "https://github.com/DemoMacro/BunIt/issues"