@vryzel/file-next 0.4.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/errors/index.cjs +183 -0
- package/dist/errors/index.cjs.map +1 -0
- package/dist/errors/index.d.cts +126 -0
- package/dist/errors/index.d.ts +126 -0
- package/dist/errors/index.js +176 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/filesystem-BbZJLy4A.d.cts +830 -0
- package/dist/filesystem-D1Jjx3md.d.ts +830 -0
- package/dist/index.cjs +5745 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +131 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +5706 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +628 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +252 -0
- package/dist/server/index.d.ts +252 -0
- package/dist/server/index.js +624 -0
- package/dist/server/index.js.map +1 -0
- package/dist/sync/index.cjs +441 -0
- package/dist/sync/index.cjs.map +1 -0
- package/dist/sync/index.d.cts +73 -0
- package/dist/sync/index.d.ts +73 -0
- package/dist/sync/index.js +439 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/with-auth-BueIpNZ7.d.cts +14 -0
- package/dist/with-auth-yfezt7YS.d.ts +14 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 file-next contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @vryzel/file-next
|
|
2
|
+
|
|
3
|
+
S3/R2 filesystem for Next.js: object adapter, metadata index, server actions, write-through.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @vryzel/file-next
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
asTenantId,
|
|
12
|
+
asUserId,
|
|
13
|
+
createFileSystem,
|
|
14
|
+
createSqliteStore,
|
|
15
|
+
} from "@vryzel/file-next";
|
|
16
|
+
import { createServerActions } from "@vryzel/file-next/server";
|
|
17
|
+
import { createWriteThrough } from "@vryzel/file-next/sync";
|
|
18
|
+
|
|
19
|
+
const store = createSqliteStore({ path: ".data/metadata.db" });
|
|
20
|
+
const fs = createFileSystem(
|
|
21
|
+
{
|
|
22
|
+
provider: "s3",
|
|
23
|
+
bucket: process.env.FILE_NEXT_BUCKET!,
|
|
24
|
+
region: process.env.FILE_NEXT_REGION ?? "us-east-1",
|
|
25
|
+
credentials: {
|
|
26
|
+
accessKeyId: process.env.FILE_NEXT_ACCESS_KEY_ID!,
|
|
27
|
+
secretAccessKey: process.env.FILE_NEXT_SECRET_ACCESS_KEY!,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{ store },
|
|
31
|
+
);
|
|
32
|
+
export const actions = createServerActions({
|
|
33
|
+
store,
|
|
34
|
+
fs,
|
|
35
|
+
writeThrough: createWriteThrough(fs, store),
|
|
36
|
+
getAuth: () => ({
|
|
37
|
+
tenantId: asTenantId("acme"),
|
|
38
|
+
userId: asUserId("user-1"),
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Tenant never comes from the client. Replace `getAuth` with your session.
|
|
44
|
+
|
|
45
|
+
Public on npmjs. See the repository README.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors/mappers.ts
|
|
4
|
+
var HTTP_STATUS_TO_CODE = {
|
|
5
|
+
401: { code: "Unauthorized", retryable: false },
|
|
6
|
+
403: { code: "Forbidden", retryable: false },
|
|
7
|
+
404: { code: "NotFound", retryable: false },
|
|
8
|
+
408: { code: "NetworkError", retryable: true },
|
|
9
|
+
409: { code: "Conflict", retryable: false },
|
|
10
|
+
413: { code: "PayloadTooLarge", retryable: false },
|
|
11
|
+
415: { code: "UnsupportedMediaType", retryable: false },
|
|
12
|
+
429: { code: "RateLimited", retryable: true }
|
|
13
|
+
};
|
|
14
|
+
var AWS_NAME_TO_CODE = {
|
|
15
|
+
NoSuchKey: { code: "NotFound", retryable: false },
|
|
16
|
+
NoSuchBucket: { code: "NotFound", retryable: false },
|
|
17
|
+
// v0.2 multipart: unknown/aborted/expired uploadId.
|
|
18
|
+
NoSuchUpload: { code: "NotFound", retryable: false },
|
|
19
|
+
AccessDenied: { code: "Forbidden", retryable: false },
|
|
20
|
+
SlowDown: { code: "QuotaExceeded", retryable: true },
|
|
21
|
+
BadDigest: { code: "ChecksumMismatch", retryable: true },
|
|
22
|
+
XAmzContentSHA256Mismatch: { code: "ChecksumMismatch", retryable: true },
|
|
23
|
+
NetworkingError: { code: "NetworkError", retryable: true },
|
|
24
|
+
TimeoutError: { code: "NetworkError", retryable: true }
|
|
25
|
+
};
|
|
26
|
+
var PG_SQLSTATE_TO_CODE = {
|
|
27
|
+
"23505": { code: "Conflict", retryable: false }
|
|
28
|
+
};
|
|
29
|
+
var SQLITE_CODE_TO_CODE = {
|
|
30
|
+
SQLITE_CONSTRAINT_UNIQUE: { code: "Conflict", retryable: false },
|
|
31
|
+
SQLITE_CONSTRAINT_PRIMARYKEY: { code: "Conflict", retryable: false }
|
|
32
|
+
};
|
|
33
|
+
var toCause = (code, message, extra) => ({
|
|
34
|
+
code,
|
|
35
|
+
message,
|
|
36
|
+
...extra ?? {}
|
|
37
|
+
});
|
|
38
|
+
var fallback = (err) => new FileSystemError({
|
|
39
|
+
code: "InternalError",
|
|
40
|
+
message: err instanceof Error ? err.message : String(err),
|
|
41
|
+
retryable: true,
|
|
42
|
+
cause: toCause(
|
|
43
|
+
"Unknown",
|
|
44
|
+
err instanceof Error ? err.message : String(err)
|
|
45
|
+
)
|
|
46
|
+
});
|
|
47
|
+
var fromAws = (err) => {
|
|
48
|
+
if (err instanceof FileSystemError) return err;
|
|
49
|
+
if (typeof err !== "object" || err === null) return fallback(err);
|
|
50
|
+
const awsErr = err;
|
|
51
|
+
const name = typeof awsErr.name === "string" ? awsErr.name : void 0;
|
|
52
|
+
const message = typeof awsErr.message === "string" ? awsErr.message : `AWS error: ${name ?? "unknown"}`;
|
|
53
|
+
const status = typeof awsErr.$metadata?.httpStatusCode === "number" ? awsErr.$metadata.httpStatusCode : void 0;
|
|
54
|
+
const cause = toCause(
|
|
55
|
+
name ?? (status !== void 0 ? String(status) : "Unknown"),
|
|
56
|
+
message,
|
|
57
|
+
status !== void 0 ? { httpStatusCode: status } : void 0
|
|
58
|
+
);
|
|
59
|
+
if (name && name in AWS_NAME_TO_CODE) {
|
|
60
|
+
const m = AWS_NAME_TO_CODE[name];
|
|
61
|
+
return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
|
|
62
|
+
}
|
|
63
|
+
if (status !== void 0 && status in HTTP_STATUS_TO_CODE) {
|
|
64
|
+
const m = HTTP_STATUS_TO_CODE[status];
|
|
65
|
+
return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
|
|
66
|
+
}
|
|
67
|
+
if (status !== void 0 && status >= 500 && status < 600) {
|
|
68
|
+
return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
|
|
69
|
+
}
|
|
70
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
71
|
+
return new FileSystemError({ code: "Forbidden", message, retryable: false, cause });
|
|
72
|
+
}
|
|
73
|
+
return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
|
|
74
|
+
};
|
|
75
|
+
var fromPg = (err) => {
|
|
76
|
+
if (err instanceof FileSystemError) return err;
|
|
77
|
+
if (typeof err !== "object" || err === null) return fallback(err);
|
|
78
|
+
const pgErr = err;
|
|
79
|
+
const sqlstate = typeof pgErr.code === "string" ? pgErr.code : void 0;
|
|
80
|
+
const message = typeof pgErr.message === "string" ? pgErr.message : `Postgres error: ${sqlstate ?? "unknown"}`;
|
|
81
|
+
const cause = toCause(sqlstate ?? "Unknown", message);
|
|
82
|
+
if (sqlstate && sqlstate in PG_SQLSTATE_TO_CODE) {
|
|
83
|
+
const m = PG_SQLSTATE_TO_CODE[sqlstate];
|
|
84
|
+
return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
|
|
85
|
+
}
|
|
86
|
+
return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
|
|
87
|
+
};
|
|
88
|
+
var fromSqlite = (err) => {
|
|
89
|
+
if (err instanceof FileSystemError) return err;
|
|
90
|
+
if (typeof err !== "object" || err === null) return fallback(err);
|
|
91
|
+
const sqlErr = err;
|
|
92
|
+
const code = typeof sqlErr.code === "string" ? sqlErr.code : void 0;
|
|
93
|
+
const message = typeof sqlErr.message === "string" ? sqlErr.message : `SQLite error: ${code ?? "unknown"}`;
|
|
94
|
+
const cause = toCause(code ?? "Unknown", message);
|
|
95
|
+
if (code && code in SQLITE_CODE_TO_CODE) {
|
|
96
|
+
const m = SQLITE_CODE_TO_CODE[code];
|
|
97
|
+
return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });
|
|
98
|
+
}
|
|
99
|
+
return new FileSystemError({ code: "InternalError", message, retryable: true, cause });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/errors/index.ts
|
|
103
|
+
var FILE_SYSTEM_ERROR_CODES = [
|
|
104
|
+
"NotFound",
|
|
105
|
+
"Forbidden",
|
|
106
|
+
"Conflict",
|
|
107
|
+
"QuotaExceeded",
|
|
108
|
+
"NetworkError",
|
|
109
|
+
"InternalError",
|
|
110
|
+
"Unauthorized",
|
|
111
|
+
"PayloadTooLarge",
|
|
112
|
+
"UnsupportedMediaType",
|
|
113
|
+
"RateLimited",
|
|
114
|
+
"ChecksumMismatch"
|
|
115
|
+
];
|
|
116
|
+
var RETRYABLE_BY_CODE = {
|
|
117
|
+
NotFound: false,
|
|
118
|
+
Forbidden: false,
|
|
119
|
+
Conflict: false,
|
|
120
|
+
QuotaExceeded: true,
|
|
121
|
+
NetworkError: true,
|
|
122
|
+
InternalError: true,
|
|
123
|
+
Unauthorized: false,
|
|
124
|
+
PayloadTooLarge: false,
|
|
125
|
+
UnsupportedMediaType: false,
|
|
126
|
+
RateLimited: true,
|
|
127
|
+
ChecksumMismatch: true
|
|
128
|
+
};
|
|
129
|
+
var FileSystemError = class extends Error {
|
|
130
|
+
code;
|
|
131
|
+
retryable;
|
|
132
|
+
cause;
|
|
133
|
+
constructor(opts) {
|
|
134
|
+
super(opts.message);
|
|
135
|
+
this.name = "FileSystemError";
|
|
136
|
+
this.code = opts.code;
|
|
137
|
+
this.retryable = opts.retryable;
|
|
138
|
+
if (opts.cause !== void 0) {
|
|
139
|
+
this.cause = opts.cause;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stable JSON shape for RSC pass-through. Error.prototype.message
|
|
144
|
+
* is non-enumerable, so plain `JSON.stringify(err)` would emit `{}`
|
|
145
|
+
* and lose the message + code + retryable flag. `cause` is omitted
|
|
146
|
+
* entirely (not set to undefined) when absent so the shape stays
|
|
147
|
+
* stable for hash-based caches.
|
|
148
|
+
*/
|
|
149
|
+
toJSON() {
|
|
150
|
+
return {
|
|
151
|
+
name: this.name,
|
|
152
|
+
code: this.code,
|
|
153
|
+
message: this.message,
|
|
154
|
+
retryable: this.retryable,
|
|
155
|
+
...this.cause !== void 0 ? { cause: this.cause } : {}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Mappers from upstream error shapes (AWS SDK v3, node-postgres,
|
|
160
|
+
* better-sqlite3) to FileSystemError. See `./mappers.ts` for the
|
|
161
|
+
* mapping tables. Re-exposed as static methods so callers can pick
|
|
162
|
+
* the import style that fits; the free functions are the canonical
|
|
163
|
+
* implementation.
|
|
164
|
+
*/
|
|
165
|
+
static fromAws(err) {
|
|
166
|
+
return fromAws(err);
|
|
167
|
+
}
|
|
168
|
+
static fromPg(err) {
|
|
169
|
+
return fromPg(err);
|
|
170
|
+
}
|
|
171
|
+
static fromSqlite(err) {
|
|
172
|
+
return fromSqlite(err);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
exports.FILE_SYSTEM_ERROR_CODES = FILE_SYSTEM_ERROR_CODES;
|
|
177
|
+
exports.FileSystemError = FileSystemError;
|
|
178
|
+
exports.RETRYABLE_BY_CODE = RETRYABLE_BY_CODE;
|
|
179
|
+
exports.fromAws = fromAws;
|
|
180
|
+
exports.fromPg = fromPg;
|
|
181
|
+
exports.fromSqlite = fromSqlite;
|
|
182
|
+
//# sourceMappingURL=index.cjs.map
|
|
183
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/mappers.ts","../../src/errors/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Upstream error mappers for FileSystemError.\n *\n * Each mapper is a pure function `(*unknown) -> FileSystemError`.\n * The class re-exposes them as static methods (FileSystemError.fromAws\n * etc.) so callers can pick the import style that fits.\n *\n * Mapping strategy:\n * 1. If the input is already a FileSystemError, return it as-is\n * (we never re-wrap our own errors).\n * 2. Try the most specific signal first: S3 `name`, Postgres\n * SQLSTATE, SQLite extended code.\n * 3. Fall back to the HTTP status code (AWS SDK v3 exposes\n * `$metadata.httpStatusCode`).\n * 4. Last resort: InternalError, retryable, with the original\n * payload in `cause`.\n *\n * Provider-specific codes are preserved on `cause.code` (e.g. S3\n * \"NoSuchKey\", Postgres \"23505\") so callers can still branch on\n * them while the top-level `code` stays in the 11-code spec catalog.\n *\n * Mapping notes (post-C' merge, see\n * `sdd/file-next/decisions/error-codes-deviation`):\n * - S3 `SlowDown` -> QuotaExceeded (broader than RateLimited;\n * SlowDown means reduce request rate, which\n * is conceptually quota pressure, not\n * throttling of a single call).\n * - S3 `BadDigest` -> ChecksumMismatch (checksum verified by\n * S3 on upload did not match the client).\n * - HTTP 429 -> RateLimited (was QuotaExceeded in the\n * pre-C' catalog; RateLimited is the more\n * specific code for throttling responses).\n * - HTTP 408 -> NetworkError (transient transport error).\n * - HTTP 400 / Postgres 23514 / S3 InvalidRequest\n * -> fall through to the 4xx branch (no\n * top-level 400 code in the C' catalog;\n * these inputs are usually caught by the\n * name/SQLSTATE mapping first anyway).\n */\n\nimport { FileSystemError, type FileSystemErrorCode } from \"./index\";\n\n/* ---------------------------------------------------------------- *\n * Mapping tables (private) *\n * ---------------------------------------------------------------- */\n\ninterface CodeMapping {\n code: FileSystemErrorCode;\n retryable: boolean;\n}\n\nconst HTTP_STATUS_TO_CODE: Readonly<Record<number, CodeMapping>> = {\n 401: { code: \"Unauthorized\", retryable: false },\n 403: { code: \"Forbidden\", retryable: false },\n 404: { code: \"NotFound\", retryable: false },\n 408: { code: \"NetworkError\", retryable: true },\n 409: { code: \"Conflict\", retryable: false },\n 413: { code: \"PayloadTooLarge\", retryable: false },\n 415: { code: \"UnsupportedMediaType\", retryable: false },\n 429: { code: \"RateLimited\", retryable: true },\n};\n\nconst AWS_NAME_TO_CODE: Readonly<Record<string, CodeMapping>> = {\n NoSuchKey: { code: \"NotFound\", retryable: false },\n NoSuchBucket: { code: \"NotFound\", retryable: false },\n // v0.2 multipart: unknown/aborted/expired uploadId.\n NoSuchUpload: { code: \"NotFound\", retryable: false },\n AccessDenied: { code: \"Forbidden\", retryable: false },\n SlowDown: { code: \"QuotaExceeded\", retryable: true },\n BadDigest: { code: \"ChecksumMismatch\", retryable: true },\n XAmzContentSHA256Mismatch: { code: \"ChecksumMismatch\", retryable: true },\n NetworkingError: { code: \"NetworkError\", retryable: true },\n TimeoutError: { code: \"NetworkError\", retryable: true },\n};\n\nconst PG_SQLSTATE_TO_CODE: Readonly<Record<string, CodeMapping>> = {\n \"23505\": { code: \"Conflict\", retryable: false },\n};\n\nconst SQLITE_CODE_TO_CODE: Readonly<Record<string, CodeMapping>> = {\n SQLITE_CONSTRAINT_UNIQUE: { code: \"Conflict\", retryable: false },\n SQLITE_CONSTRAINT_PRIMARYKEY: { code: \"Conflict\", retryable: false },\n};\n\n/* ---------------------------------------------------------------- *\n * Public mappers *\n * ---------------------------------------------------------------- */\n\nconst toCause = (code: string, message: string, extra?: Record<string, unknown>) => ({\n code,\n message,\n ...(extra ?? {}),\n});\n\nconst fallback = (err: unknown): FileSystemError =>\n new FileSystemError({\n code: \"InternalError\",\n message: err instanceof Error ? err.message : String(err),\n retryable: true,\n cause: toCause(\n \"Unknown\",\n err instanceof Error ? err.message : String(err),\n ),\n });\n\nexport const fromAws = (err: unknown): FileSystemError => {\n if (err instanceof FileSystemError) return err;\n\n if (typeof err !== \"object\" || err === null) return fallback(err);\n\n const awsErr = err as {\n name?: unknown;\n message?: unknown;\n $metadata?: { httpStatusCode?: unknown };\n };\n const name = typeof awsErr.name === \"string\" ? awsErr.name : undefined;\n const message =\n typeof awsErr.message === \"string\" ? awsErr.message : `AWS error: ${name ?? \"unknown\"}`;\n const status =\n typeof awsErr.$metadata?.httpStatusCode === \"number\"\n ? awsErr.$metadata.httpStatusCode\n : undefined;\n\n const cause = toCause(\n name ?? (status !== undefined ? String(status) : \"Unknown\"),\n message,\n status !== undefined ? { httpStatusCode: status } : undefined,\n );\n\n // 1) name-based mapping wins (more specific than HTTP status)\n if (name && name in AWS_NAME_TO_CODE) {\n const m = AWS_NAME_TO_CODE[name]!;\n return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });\n }\n\n // 2) HTTP status mapping\n if (status !== undefined && status in HTTP_STATUS_TO_CODE) {\n const m = HTTP_STATUS_TO_CODE[status]!;\n return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });\n }\n\n // 3) 5xx bucket\n if (status !== undefined && status >= 500 && status < 600) {\n return new FileSystemError({ code: \"InternalError\", message, retryable: true, cause });\n }\n\n // 4) anything else 4xx-shaped\n if (status !== undefined && status >= 400 && status < 500) {\n return new FileSystemError({ code: \"Forbidden\", message, retryable: false, cause });\n }\n\n return new FileSystemError({ code: \"InternalError\", message, retryable: true, cause });\n};\n\nexport const fromPg = (err: unknown): FileSystemError => {\n if (err instanceof FileSystemError) return err;\n\n if (typeof err !== \"object\" || err === null) return fallback(err);\n\n const pgErr = err as { code?: unknown; message?: unknown };\n const sqlstate = typeof pgErr.code === \"string\" ? pgErr.code : undefined;\n const message =\n typeof pgErr.message === \"string\" ? pgErr.message : `Postgres error: ${sqlstate ?? \"unknown\"}`;\n const cause = toCause(sqlstate ?? \"Unknown\", message);\n\n if (sqlstate && sqlstate in PG_SQLSTATE_TO_CODE) {\n const m = PG_SQLSTATE_TO_CODE[sqlstate]!;\n return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });\n }\n\n return new FileSystemError({ code: \"InternalError\", message, retryable: true, cause });\n};\n\nexport const fromSqlite = (err: unknown): FileSystemError => {\n if (err instanceof FileSystemError) return err;\n\n if (typeof err !== \"object\" || err === null) return fallback(err);\n\n const sqlErr = err as { code?: unknown; message?: unknown };\n const code = typeof sqlErr.code === \"string\" ? sqlErr.code : undefined;\n const message =\n typeof sqlErr.message === \"string\" ? sqlErr.message : `SQLite error: ${code ?? \"unknown\"}`;\n const cause = toCause(code ?? \"Unknown\", message);\n\n if (code && code in SQLITE_CODE_TO_CODE) {\n const m = SQLITE_CODE_TO_CODE[code]!;\n return new FileSystemError({ code: m.code, message, retryable: m.retryable, cause });\n }\n\n return new FileSystemError({ code: \"InternalError\", message, retryable: true, cause });\n};\n","/**\n * FileSystemError — the single error class every adapter, store,\n * server action, and route handler in `file-next` returns or throws.\n *\n * Spec reference: requirement \"FileSystemError discriminated union\"\n * in `sdd/file-next/spec`. The 11 codes are the CLOSED set; any\n * provider-specific code (e.g. S3 `NoSuchKey`, Postgres `23505`) is\n * preserved on `cause.code` so callers can still distinguish upstream\n * reasons while the top-level `code` stays in the catalog.\n *\n * The catalog is a merge of spec + impl: the 6 fixed codes\n * (NotFound, Forbidden, Conflict, QuotaExceeded, NetworkError,\n * InternalError) are non-negotiable; the remaining 5 keep the wire\n * aligned with both the HTTP boundary (route handlers map the\n * top-level `code` to a status directly) and the storage boundary\n * (`RateLimited` and `ChecksumMismatch` carry semantics that have\n * no natural HTTP equivalent). See\n * `sdd/file-next/decisions/error-codes-deviation` for the\n * deviation log and rationale.\n *\n * Retryability table is the design's `error-mapping` excerpt; see\n * `sdd/file-next/design` §C. Any future code MUST add an entry in\n * `RETRYABLE_BY_CODE` (the catalog test enforces this).\n *\n * The placeholder from T-006 is replaced by this file in T-007a.\n * The upstream mappers (fromAws / fromPg / fromSqlite) land in T-007b\n * in `mappers.ts`.\n */\n\nexport const FILE_SYSTEM_ERROR_CODES = [\n \"NotFound\",\n \"Forbidden\",\n \"Conflict\",\n \"QuotaExceeded\",\n \"NetworkError\",\n \"InternalError\",\n \"Unauthorized\",\n \"PayloadTooLarge\",\n \"UnsupportedMediaType\",\n \"RateLimited\",\n \"ChecksumMismatch\",\n] as const satisfies readonly string[];\n\nexport type FileSystemErrorCode = (typeof FILE_SYSTEM_ERROR_CODES)[number];\n\nexport const RETRYABLE_BY_CODE: Readonly<Record<FileSystemErrorCode, boolean>> = {\n NotFound: false,\n Forbidden: false,\n Conflict: false,\n QuotaExceeded: true,\n NetworkError: true,\n InternalError: true,\n Unauthorized: false,\n PayloadTooLarge: false,\n UnsupportedMediaType: false,\n RateLimited: true,\n ChecksumMismatch: true,\n};\n\nexport interface FileSystemErrorOptions {\n code: FileSystemErrorCode;\n message: string;\n retryable: boolean;\n cause?: { code: string; message: string; [k: string]: unknown };\n}\n\nexport interface FileSystemErrorJson {\n name: string;\n code: FileSystemErrorCode;\n message: string;\n retryable: boolean;\n cause?: { code: string; message: string; [k: string]: unknown };\n}\n\nexport class FileSystemError extends Error {\n readonly code: FileSystemErrorCode;\n readonly retryable: boolean;\n override readonly cause?: { code: string; message: string; [k: string]: unknown };\n\n constructor(opts: FileSystemErrorOptions) {\n super(opts.message);\n this.name = \"FileSystemError\";\n this.code = opts.code;\n this.retryable = opts.retryable;\n if (opts.cause !== undefined) {\n this.cause = opts.cause;\n }\n }\n\n /**\n * Stable JSON shape for RSC pass-through. Error.prototype.message\n * is non-enumerable, so plain `JSON.stringify(err)` would emit `{}`\n * and lose the message + code + retryable flag. `cause` is omitted\n * entirely (not set to undefined) when absent so the shape stays\n * stable for hash-based caches.\n */\n toJSON(): FileSystemErrorJson {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n retryable: this.retryable,\n ...(this.cause !== undefined ? { cause: this.cause } : {}),\n };\n }\n\n /**\n * Mappers from upstream error shapes (AWS SDK v3, node-postgres,\n * better-sqlite3) to FileSystemError. See `./mappers.ts` for the\n * mapping tables. Re-exposed as static methods so callers can pick\n * the import style that fits; the free functions are the canonical\n * implementation.\n */\n static fromAws(err: unknown): FileSystemError {\n // Lazy import via the runtime binding avoids the static-field\n // circular-dep trap in ESM. The mapper file imports this class\n // for instanceof checks; we look up the function at call time.\n return _fromAws(err);\n }\n\n static fromPg(err: unknown): FileSystemError {\n return _fromPg(err);\n }\n\n static fromSqlite(err: unknown): FileSystemError {\n return _fromSqlite(err);\n }\n}\n\n// Imported here as bindings (not values) so the static methods above\n// can resolve them lazily. The actual implementation lives in mappers.ts.\nimport { fromAws as _fromAws, fromPg as _fromPg, fromSqlite as _fromSqlite } from \"./mappers\";\nexport { fromAws, fromPg, fromSqlite } from \"./mappers\";\n"]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upstream error mappers for FileSystemError.
|
|
3
|
+
*
|
|
4
|
+
* Each mapper is a pure function `(*unknown) -> FileSystemError`.
|
|
5
|
+
* The class re-exposes them as static methods (FileSystemError.fromAws
|
|
6
|
+
* etc.) so callers can pick the import style that fits.
|
|
7
|
+
*
|
|
8
|
+
* Mapping strategy:
|
|
9
|
+
* 1. If the input is already a FileSystemError, return it as-is
|
|
10
|
+
* (we never re-wrap our own errors).
|
|
11
|
+
* 2. Try the most specific signal first: S3 `name`, Postgres
|
|
12
|
+
* SQLSTATE, SQLite extended code.
|
|
13
|
+
* 3. Fall back to the HTTP status code (AWS SDK v3 exposes
|
|
14
|
+
* `$metadata.httpStatusCode`).
|
|
15
|
+
* 4. Last resort: InternalError, retryable, with the original
|
|
16
|
+
* payload in `cause`.
|
|
17
|
+
*
|
|
18
|
+
* Provider-specific codes are preserved on `cause.code` (e.g. S3
|
|
19
|
+
* "NoSuchKey", Postgres "23505") so callers can still branch on
|
|
20
|
+
* them while the top-level `code` stays in the 11-code spec catalog.
|
|
21
|
+
*
|
|
22
|
+
* Mapping notes (post-C' merge, see
|
|
23
|
+
* `sdd/file-next/decisions/error-codes-deviation`):
|
|
24
|
+
* - S3 `SlowDown` -> QuotaExceeded (broader than RateLimited;
|
|
25
|
+
* SlowDown means reduce request rate, which
|
|
26
|
+
* is conceptually quota pressure, not
|
|
27
|
+
* throttling of a single call).
|
|
28
|
+
* - S3 `BadDigest` -> ChecksumMismatch (checksum verified by
|
|
29
|
+
* S3 on upload did not match the client).
|
|
30
|
+
* - HTTP 429 -> RateLimited (was QuotaExceeded in the
|
|
31
|
+
* pre-C' catalog; RateLimited is the more
|
|
32
|
+
* specific code for throttling responses).
|
|
33
|
+
* - HTTP 408 -> NetworkError (transient transport error).
|
|
34
|
+
* - HTTP 400 / Postgres 23514 / S3 InvalidRequest
|
|
35
|
+
* -> fall through to the 4xx branch (no
|
|
36
|
+
* top-level 400 code in the C' catalog;
|
|
37
|
+
* these inputs are usually caught by the
|
|
38
|
+
* name/SQLSTATE mapping first anyway).
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
declare const fromAws: (err: unknown) => FileSystemError;
|
|
42
|
+
declare const fromPg: (err: unknown) => FileSystemError;
|
|
43
|
+
declare const fromSqlite: (err: unknown) => FileSystemError;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* FileSystemError — the single error class every adapter, store,
|
|
47
|
+
* server action, and route handler in `file-next` returns or throws.
|
|
48
|
+
*
|
|
49
|
+
* Spec reference: requirement "FileSystemError discriminated union"
|
|
50
|
+
* in `sdd/file-next/spec`. The 11 codes are the CLOSED set; any
|
|
51
|
+
* provider-specific code (e.g. S3 `NoSuchKey`, Postgres `23505`) is
|
|
52
|
+
* preserved on `cause.code` so callers can still distinguish upstream
|
|
53
|
+
* reasons while the top-level `code` stays in the catalog.
|
|
54
|
+
*
|
|
55
|
+
* The catalog is a merge of spec + impl: the 6 fixed codes
|
|
56
|
+
* (NotFound, Forbidden, Conflict, QuotaExceeded, NetworkError,
|
|
57
|
+
* InternalError) are non-negotiable; the remaining 5 keep the wire
|
|
58
|
+
* aligned with both the HTTP boundary (route handlers map the
|
|
59
|
+
* top-level `code` to a status directly) and the storage boundary
|
|
60
|
+
* (`RateLimited` and `ChecksumMismatch` carry semantics that have
|
|
61
|
+
* no natural HTTP equivalent). See
|
|
62
|
+
* `sdd/file-next/decisions/error-codes-deviation` for the
|
|
63
|
+
* deviation log and rationale.
|
|
64
|
+
*
|
|
65
|
+
* Retryability table is the design's `error-mapping` excerpt; see
|
|
66
|
+
* `sdd/file-next/design` §C. Any future code MUST add an entry in
|
|
67
|
+
* `RETRYABLE_BY_CODE` (the catalog test enforces this).
|
|
68
|
+
*
|
|
69
|
+
* The placeholder from T-006 is replaced by this file in T-007a.
|
|
70
|
+
* The upstream mappers (fromAws / fromPg / fromSqlite) land in T-007b
|
|
71
|
+
* in `mappers.ts`.
|
|
72
|
+
*/
|
|
73
|
+
declare const FILE_SYSTEM_ERROR_CODES: readonly ["NotFound", "Forbidden", "Conflict", "QuotaExceeded", "NetworkError", "InternalError", "Unauthorized", "PayloadTooLarge", "UnsupportedMediaType", "RateLimited", "ChecksumMismatch"];
|
|
74
|
+
type FileSystemErrorCode = (typeof FILE_SYSTEM_ERROR_CODES)[number];
|
|
75
|
+
declare const RETRYABLE_BY_CODE: Readonly<Record<FileSystemErrorCode, boolean>>;
|
|
76
|
+
interface FileSystemErrorOptions {
|
|
77
|
+
code: FileSystemErrorCode;
|
|
78
|
+
message: string;
|
|
79
|
+
retryable: boolean;
|
|
80
|
+
cause?: {
|
|
81
|
+
code: string;
|
|
82
|
+
message: string;
|
|
83
|
+
[k: string]: unknown;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
interface FileSystemErrorJson {
|
|
87
|
+
name: string;
|
|
88
|
+
code: FileSystemErrorCode;
|
|
89
|
+
message: string;
|
|
90
|
+
retryable: boolean;
|
|
91
|
+
cause?: {
|
|
92
|
+
code: string;
|
|
93
|
+
message: string;
|
|
94
|
+
[k: string]: unknown;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
declare class FileSystemError extends Error {
|
|
98
|
+
readonly code: FileSystemErrorCode;
|
|
99
|
+
readonly retryable: boolean;
|
|
100
|
+
readonly cause?: {
|
|
101
|
+
code: string;
|
|
102
|
+
message: string;
|
|
103
|
+
[k: string]: unknown;
|
|
104
|
+
};
|
|
105
|
+
constructor(opts: FileSystemErrorOptions);
|
|
106
|
+
/**
|
|
107
|
+
* Stable JSON shape for RSC pass-through. Error.prototype.message
|
|
108
|
+
* is non-enumerable, so plain `JSON.stringify(err)` would emit `{}`
|
|
109
|
+
* and lose the message + code + retryable flag. `cause` is omitted
|
|
110
|
+
* entirely (not set to undefined) when absent so the shape stays
|
|
111
|
+
* stable for hash-based caches.
|
|
112
|
+
*/
|
|
113
|
+
toJSON(): FileSystemErrorJson;
|
|
114
|
+
/**
|
|
115
|
+
* Mappers from upstream error shapes (AWS SDK v3, node-postgres,
|
|
116
|
+
* better-sqlite3) to FileSystemError. See `./mappers.ts` for the
|
|
117
|
+
* mapping tables. Re-exposed as static methods so callers can pick
|
|
118
|
+
* the import style that fits; the free functions are the canonical
|
|
119
|
+
* implementation.
|
|
120
|
+
*/
|
|
121
|
+
static fromAws(err: unknown): FileSystemError;
|
|
122
|
+
static fromPg(err: unknown): FileSystemError;
|
|
123
|
+
static fromSqlite(err: unknown): FileSystemError;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { FILE_SYSTEM_ERROR_CODES, FileSystemError, type FileSystemErrorCode, type FileSystemErrorJson, type FileSystemErrorOptions, RETRYABLE_BY_CODE, fromAws, fromPg, fromSqlite };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upstream error mappers for FileSystemError.
|
|
3
|
+
*
|
|
4
|
+
* Each mapper is a pure function `(*unknown) -> FileSystemError`.
|
|
5
|
+
* The class re-exposes them as static methods (FileSystemError.fromAws
|
|
6
|
+
* etc.) so callers can pick the import style that fits.
|
|
7
|
+
*
|
|
8
|
+
* Mapping strategy:
|
|
9
|
+
* 1. If the input is already a FileSystemError, return it as-is
|
|
10
|
+
* (we never re-wrap our own errors).
|
|
11
|
+
* 2. Try the most specific signal first: S3 `name`, Postgres
|
|
12
|
+
* SQLSTATE, SQLite extended code.
|
|
13
|
+
* 3. Fall back to the HTTP status code (AWS SDK v3 exposes
|
|
14
|
+
* `$metadata.httpStatusCode`).
|
|
15
|
+
* 4. Last resort: InternalError, retryable, with the original
|
|
16
|
+
* payload in `cause`.
|
|
17
|
+
*
|
|
18
|
+
* Provider-specific codes are preserved on `cause.code` (e.g. S3
|
|
19
|
+
* "NoSuchKey", Postgres "23505") so callers can still branch on
|
|
20
|
+
* them while the top-level `code` stays in the 11-code spec catalog.
|
|
21
|
+
*
|
|
22
|
+
* Mapping notes (post-C' merge, see
|
|
23
|
+
* `sdd/file-next/decisions/error-codes-deviation`):
|
|
24
|
+
* - S3 `SlowDown` -> QuotaExceeded (broader than RateLimited;
|
|
25
|
+
* SlowDown means reduce request rate, which
|
|
26
|
+
* is conceptually quota pressure, not
|
|
27
|
+
* throttling of a single call).
|
|
28
|
+
* - S3 `BadDigest` -> ChecksumMismatch (checksum verified by
|
|
29
|
+
* S3 on upload did not match the client).
|
|
30
|
+
* - HTTP 429 -> RateLimited (was QuotaExceeded in the
|
|
31
|
+
* pre-C' catalog; RateLimited is the more
|
|
32
|
+
* specific code for throttling responses).
|
|
33
|
+
* - HTTP 408 -> NetworkError (transient transport error).
|
|
34
|
+
* - HTTP 400 / Postgres 23514 / S3 InvalidRequest
|
|
35
|
+
* -> fall through to the 4xx branch (no
|
|
36
|
+
* top-level 400 code in the C' catalog;
|
|
37
|
+
* these inputs are usually caught by the
|
|
38
|
+
* name/SQLSTATE mapping first anyway).
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
declare const fromAws: (err: unknown) => FileSystemError;
|
|
42
|
+
declare const fromPg: (err: unknown) => FileSystemError;
|
|
43
|
+
declare const fromSqlite: (err: unknown) => FileSystemError;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* FileSystemError — the single error class every adapter, store,
|
|
47
|
+
* server action, and route handler in `file-next` returns or throws.
|
|
48
|
+
*
|
|
49
|
+
* Spec reference: requirement "FileSystemError discriminated union"
|
|
50
|
+
* in `sdd/file-next/spec`. The 11 codes are the CLOSED set; any
|
|
51
|
+
* provider-specific code (e.g. S3 `NoSuchKey`, Postgres `23505`) is
|
|
52
|
+
* preserved on `cause.code` so callers can still distinguish upstream
|
|
53
|
+
* reasons while the top-level `code` stays in the catalog.
|
|
54
|
+
*
|
|
55
|
+
* The catalog is a merge of spec + impl: the 6 fixed codes
|
|
56
|
+
* (NotFound, Forbidden, Conflict, QuotaExceeded, NetworkError,
|
|
57
|
+
* InternalError) are non-negotiable; the remaining 5 keep the wire
|
|
58
|
+
* aligned with both the HTTP boundary (route handlers map the
|
|
59
|
+
* top-level `code` to a status directly) and the storage boundary
|
|
60
|
+
* (`RateLimited` and `ChecksumMismatch` carry semantics that have
|
|
61
|
+
* no natural HTTP equivalent). See
|
|
62
|
+
* `sdd/file-next/decisions/error-codes-deviation` for the
|
|
63
|
+
* deviation log and rationale.
|
|
64
|
+
*
|
|
65
|
+
* Retryability table is the design's `error-mapping` excerpt; see
|
|
66
|
+
* `sdd/file-next/design` §C. Any future code MUST add an entry in
|
|
67
|
+
* `RETRYABLE_BY_CODE` (the catalog test enforces this).
|
|
68
|
+
*
|
|
69
|
+
* The placeholder from T-006 is replaced by this file in T-007a.
|
|
70
|
+
* The upstream mappers (fromAws / fromPg / fromSqlite) land in T-007b
|
|
71
|
+
* in `mappers.ts`.
|
|
72
|
+
*/
|
|
73
|
+
declare const FILE_SYSTEM_ERROR_CODES: readonly ["NotFound", "Forbidden", "Conflict", "QuotaExceeded", "NetworkError", "InternalError", "Unauthorized", "PayloadTooLarge", "UnsupportedMediaType", "RateLimited", "ChecksumMismatch"];
|
|
74
|
+
type FileSystemErrorCode = (typeof FILE_SYSTEM_ERROR_CODES)[number];
|
|
75
|
+
declare const RETRYABLE_BY_CODE: Readonly<Record<FileSystemErrorCode, boolean>>;
|
|
76
|
+
interface FileSystemErrorOptions {
|
|
77
|
+
code: FileSystemErrorCode;
|
|
78
|
+
message: string;
|
|
79
|
+
retryable: boolean;
|
|
80
|
+
cause?: {
|
|
81
|
+
code: string;
|
|
82
|
+
message: string;
|
|
83
|
+
[k: string]: unknown;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
interface FileSystemErrorJson {
|
|
87
|
+
name: string;
|
|
88
|
+
code: FileSystemErrorCode;
|
|
89
|
+
message: string;
|
|
90
|
+
retryable: boolean;
|
|
91
|
+
cause?: {
|
|
92
|
+
code: string;
|
|
93
|
+
message: string;
|
|
94
|
+
[k: string]: unknown;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
declare class FileSystemError extends Error {
|
|
98
|
+
readonly code: FileSystemErrorCode;
|
|
99
|
+
readonly retryable: boolean;
|
|
100
|
+
readonly cause?: {
|
|
101
|
+
code: string;
|
|
102
|
+
message: string;
|
|
103
|
+
[k: string]: unknown;
|
|
104
|
+
};
|
|
105
|
+
constructor(opts: FileSystemErrorOptions);
|
|
106
|
+
/**
|
|
107
|
+
* Stable JSON shape for RSC pass-through. Error.prototype.message
|
|
108
|
+
* is non-enumerable, so plain `JSON.stringify(err)` would emit `{}`
|
|
109
|
+
* and lose the message + code + retryable flag. `cause` is omitted
|
|
110
|
+
* entirely (not set to undefined) when absent so the shape stays
|
|
111
|
+
* stable for hash-based caches.
|
|
112
|
+
*/
|
|
113
|
+
toJSON(): FileSystemErrorJson;
|
|
114
|
+
/**
|
|
115
|
+
* Mappers from upstream error shapes (AWS SDK v3, node-postgres,
|
|
116
|
+
* better-sqlite3) to FileSystemError. See `./mappers.ts` for the
|
|
117
|
+
* mapping tables. Re-exposed as static methods so callers can pick
|
|
118
|
+
* the import style that fits; the free functions are the canonical
|
|
119
|
+
* implementation.
|
|
120
|
+
*/
|
|
121
|
+
static fromAws(err: unknown): FileSystemError;
|
|
122
|
+
static fromPg(err: unknown): FileSystemError;
|
|
123
|
+
static fromSqlite(err: unknown): FileSystemError;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { FILE_SYSTEM_ERROR_CODES, FileSystemError, type FileSystemErrorCode, type FileSystemErrorJson, type FileSystemErrorOptions, RETRYABLE_BY_CODE, fromAws, fromPg, fromSqlite };
|