@vryzel/file-next 0.4.0 → 0.4.1

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.
Files changed (2) hide show
  1. package/README.md +183 -3
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # @vryzel/file-next
2
2
 
3
- S3/R2 filesystem for Next.js: object adapter, metadata index, server actions, write-through.
3
+ S3/R2 filesystem for Next.js. Bytes in your bucket. The folder tree in **your** SQLite or Postgres. Tenant comes from the server, never from the client.
4
4
 
5
5
  ```bash
6
6
  pnpm add @vryzel/file-next
7
+ # optional, depending on the store:
8
+ pnpm add better-sqlite3 # SQLite
9
+ pnpm add pg # Postgres
7
10
  ```
8
11
 
12
+ Peer deps `better-sqlite3` and `pg` are optional. Install only the one you use.
13
+
14
+ ## Quick path
15
+
9
16
  ```ts
17
+ // app/lib/file-next.ts
10
18
  import {
11
19
  asTenantId,
12
20
  asUserId,
@@ -29,6 +37,7 @@ const fs = createFileSystem(
29
37
  },
30
38
  { store },
31
39
  );
40
+
32
41
  export const actions = createServerActions({
33
42
  store,
34
43
  fs,
@@ -40,6 +49,177 @@ export const actions = createServerActions({
40
49
  });
41
50
  ```
42
51
 
43
- Tenant never comes from the client. Replace `getAuth` with your session.
52
+ ```ts
53
+ // app/lib/actions.ts
54
+ "use server";
55
+ import { actions } from "./file-next";
56
+
57
+ export const listFiles = (input: { parentId: string | null; cursor?: string; limit?: number }) =>
58
+ actions.listFiles(input);
59
+ ```
60
+
61
+ Replace `getAuth` with Clerk / Auth.js / your session. Import `@vryzel/file-next/server` only from server modules.
62
+
63
+ ## Use cases
64
+
65
+ ### 1. SQLite (one process)
66
+
67
+ Default for a single Next.js server.
68
+
69
+ ```ts
70
+ import { createSqliteStore } from "@vryzel/file-next";
71
+ const store = createSqliteStore({ path: ".data/metadata.db" });
72
+ ```
73
+
74
+ Schema is created on first call. Path is relative to `process.cwd()`.
75
+
76
+ ### 2. Postgres (multi-instance + RLS)
77
+
78
+ ```ts
79
+ import { createPostgresStore } from "@vryzel/file-next";
80
+ const store = createPostgresStore({
81
+ connectionString: process.env.DATABASE_URL!,
82
+ });
83
+ ```
84
+
85
+ Install `pg`. Tenant isolation uses `SET LOCAL app.current_tenant` + row-level security.
86
+
87
+ ### 3. In-memory (tests / Storybook)
88
+
89
+ ```ts
90
+ import { createMemoryStore, createMemoryFileSystem } from "@vryzel/file-next";
91
+
92
+ const store = createMemoryStore();
93
+ const fs = createMemoryFileSystem({ store });
94
+ ```
95
+
96
+ No AWS. Data dies with the process.
97
+
98
+ ### 4. Cloudflare R2
99
+
100
+ ```ts
101
+ const fs = createFileSystem(
102
+ {
103
+ provider: "r2",
104
+ bucket: process.env.FILE_NEXT_BUCKET!,
105
+ endpoint: process.env.FILE_NEXT_ENDPOINT!, // https://<accountid>.r2.cloudflarestorage.com
106
+ credentials: {
107
+ accessKeyId: process.env.FILE_NEXT_ACCESS_KEY_ID!,
108
+ secretAccessKey: process.env.FILE_NEXT_SECRET_ACCESS_KEY!,
109
+ },
110
+ },
111
+ { store },
112
+ );
113
+ ```
114
+
115
+ R2 is S3-compatible. Set bucket CORS for `PUT`/`GET` from your origin.
116
+
117
+ ### 5. Env singleton
118
+
119
+ ```ts
120
+ import { getFileSystem } from "@vryzel/file-next";
121
+ const fs = getFileSystem(); // memoized for the process
122
+ ```
123
+
124
+ Reads:
125
+
126
+ | Var | Required |
127
+ |---|---|
128
+ | `FILE_NEXT_PROVIDER` | `s3` or `r2` |
129
+ | `FILE_NEXT_BUCKET` | yes |
130
+ | `FILE_NEXT_REGION` | S3 |
131
+ | `FILE_NEXT_ENDPOINT` | R2 |
132
+ | `FILE_NEXT_ACCESS_KEY_ID` | yes |
133
+ | `FILE_NEXT_SECRET_ACCESS_KEY` | yes |
134
+ | `FILE_NEXT_FORCE_PATH_STYLE` | optional |
135
+
136
+ Missing config throws `FileSystemError` (not retryable).
137
+
138
+ ### 6. Multi-tenant keys
139
+
140
+ ```ts
141
+ const scoped = fs.forTenant("acme");
142
+ // object keys are prefixed t/acme/{nodeId} unless prefixTenantKeys: false
143
+ ```
144
+
145
+ Every store method also takes `tenantId`. Do not take tenant from the request body.
146
+
147
+ ### 7. Server actions
148
+
149
+ `createServerActions` returns:
150
+
151
+ `listFiles`, `searchFiles`, `listTrash`, `createFolder`, `deleteFile`, `moveFile`, `copyFile`, `setMetadata`, `prepareUpload`, `confirmUpload`, `restoreNode`, `createShare`, `resolveShare`, `revokeShare`.
152
+
153
+ All return `Result<T, FileSystemError>`: `{ ok: true, value }` or `{ ok: false, error }`.
154
+
155
+ ```ts
156
+ const listed = await actions.listFiles({ parentId: null, limit: 50 });
157
+ if (!listed.ok) throw listed.error;
158
+ ```
159
+
160
+ `listFiles` / `listTrash` accept `cursor` + `limit`.
161
+
162
+ ### 8. Route handlers (upload / download)
163
+
164
+ ```ts
165
+ // app/api/upload/route.ts
166
+ import { createUploadRouteHandler } from "@vryzel/file-next/server";
167
+ import { getFileSystem } from "@vryzel/file-next";
168
+
169
+ export const PUT = createUploadRouteHandler({
170
+ fs: getFileSystem(),
171
+ maxBytes: 25 * 1024 * 1024,
172
+ allowedContentTypes: ["image/*", "application/pdf"],
173
+ });
174
+ ```
175
+
176
+ Many apps skip this and PUT to a signed URL / a thin `writeThroughFile` route (see the test app). Use whichever matches your upload flow.
177
+
178
+ ### 9. Write-through (object first, then the tree)
179
+
180
+ ```ts
181
+ import { createWriteThrough } from "@vryzel/file-next/sync";
182
+
183
+ const writeThrough = createWriteThrough(fs, store);
184
+ await writeThrough.writeThroughFile({
185
+ tenantId,
186
+ parentId: null,
187
+ name: "photo.png",
188
+ body,
189
+ contentType: "image/png",
190
+ ownerId,
191
+ });
192
+ ```
193
+
194
+ If the metadata insert fails after the object is written, the key lands in `pending_orphans`. `reconcile()` drains them.
195
+
196
+ ### 10. Result + errors
197
+
198
+ ```ts
199
+ import { ok, err, unwrap, type Result } from "@vryzel/file-next";
200
+ import { FileSystemError } from "@vryzel/file-next/errors";
201
+
202
+ if (!result.ok) {
203
+ if (result.error.code === "Conflict") { /* duplicate name */ }
204
+ if (result.error.retryable) { /* backoff */ }
205
+ }
206
+ ```
207
+
208
+ `@vryzel/file-next/errors` is the client-safe entry (no Node builtins).
209
+
210
+ ## Imports
211
+
212
+ | Entry | Use from |
213
+ |---|---|
214
+ | `@vryzel/file-next` | server or shared types |
215
+ | `@vryzel/file-next/server` | Server Actions / Route Handlers only (`server-only`) |
216
+ | `@vryzel/file-next/sync` | server |
217
+ | `@vryzel/file-next/errors` | client or server |
218
+
219
+ ## Not this package
220
+
221
+ - React hooks → `@vryzel/file-next-headless`
222
+ - Ready-made explorer → `@vryzel/file-next-ui`
223
+ - CLI → `@vryzel/file-next-cli`
44
224
 
45
- Public on npmjs. See the repository README.
225
+ Bucket CORS and IAM: [provider-setup](https://github.com/Vryzel/file-next/blob/main/docs/provider-setup.md), [security](https://github.com/Vryzel/file-next/blob/main/docs/security.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vryzel/file-next",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "File-system abstraction over AWS S3 / Cloudflare R2 for Next.js, with a secondary metadata index and shadcn/ui components.",
5
5
  "type": "module",
6
6
  "license": "MIT",