@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.
- package/README.md +183 -3
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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