@uplift-io/uplift 1.0.0 → 1.1.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 -21
- package/README.md +102 -85
- package/dist/client.cjs +23 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +23 -2
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +32 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -7
- package/dist/index.d.ts +10 -7
- package/dist/index.js +32 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +23 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +23 -2
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +75 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +75 -3
- package/dist/server.js.map +1 -1
- package/dist/{types-BdcszAj8.d.cts → types-Cw4fuNs_.d.cts} +36 -2
- package/dist/{types-BdcszAj8.d.ts → types-Cw4fuNs_.d.ts} +36 -2
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Uplift 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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Uplift 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
CHANGED
|
@@ -1,85 +1,102 @@
|
|
|
1
|
-
# Uplift
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@uplift-io/uplift)
|
|
4
|
-
[](https://www.npmjs.com/package/@uplift-io/uplift)
|
|
5
|
-
[](https://www.npmjs.com/package/@uplift-io/uplift)
|
|
6
|
-
[](https://github.com/Itzfeminisce/uplift/actions/workflows/ci.yml)
|
|
7
|
-
[](https://github.com/Itzfeminisce/uplift/blob/main/docs/BUNDLE_SIZE.md)
|
|
8
|
-
[](https://github.com/Itzfeminisce/uplift/blob/main/LICENSE)
|
|
9
|
-
|
|
10
|
-
Dead-simple, type-safe file uploads for TypeScript applications.
|
|
11
|
-
|
|
12
|
-
Define upload routes once on the server. Get a typed client on the frontend.
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
await upload.avatar(file);
|
|
16
|
-
await upload.gallery(files);
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Install
|
|
20
|
-
|
|
21
|
-
Install core plus the adapters you use:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
pnpm add @uplift-io/uplift @uplift-io/next @uplift-io/s3
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Quick Start
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
import { image, uplift } from "@uplift-io/uplift";
|
|
31
|
-
import { s3 } from "@uplift-io/s3";
|
|
32
|
-
|
|
33
|
-
export const uploads = uplift({
|
|
34
|
-
storage: s3({
|
|
35
|
-
bucket: process.env.S3_BUCKET!,
|
|
36
|
-
region: "us-east-1",
|
|
37
|
-
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
|
38
|
-
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!
|
|
39
|
-
}),
|
|
40
|
-
routes: {
|
|
41
|
-
avatar: image()
|
|
42
|
-
.max("2mb")
|
|
43
|
-
.auth(async ({ req }) => ({ id: req.headers.get("x-user-id")! }))
|
|
44
|
-
.key(({ user }) => `avatars/${user.id}.png`)
|
|
45
|
-
.done(async ({ file }) => {
|
|
46
|
-
console.log(file.url);
|
|
47
|
-
}),
|
|
48
|
-
gallery: image().max("8mb").multiple(10)
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
export type Uploads = typeof uploads;
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
import { createUploadClient } from "@uplift-io/uplift/client";
|
|
57
|
-
import type { Uploads } from "./uploads";
|
|
58
|
-
|
|
59
|
-
export const upload = createUploadClient<Uploads>("/api/upload");
|
|
60
|
-
|
|
61
|
-
const avatar = await upload.avatar(file);
|
|
62
|
-
const gallery = await upload.gallery(fileList);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
# Uplift
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@uplift-io/uplift)
|
|
4
|
+
[](https://www.npmjs.com/package/@uplift-io/uplift)
|
|
5
|
+
[](https://www.npmjs.com/package/@uplift-io/uplift)
|
|
6
|
+
[](https://github.com/Itzfeminisce/uplift/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/Itzfeminisce/uplift/blob/main/docs/BUNDLE_SIZE.md)
|
|
8
|
+
[](https://github.com/Itzfeminisce/uplift/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
Dead-simple, type-safe file uploads for TypeScript applications.
|
|
11
|
+
|
|
12
|
+
Define upload routes once on the server. Get a typed client on the frontend.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
await upload.avatar(file);
|
|
16
|
+
await upload.gallery(files);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
Install core plus the adapters you use:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @uplift-io/uplift @uplift-io/next @uplift-io/s3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { image, uplift } from "@uplift-io/uplift";
|
|
31
|
+
import { s3 } from "@uplift-io/s3";
|
|
32
|
+
|
|
33
|
+
export const uploads = uplift({
|
|
34
|
+
storage: s3({
|
|
35
|
+
bucket: process.env.S3_BUCKET!,
|
|
36
|
+
region: "us-east-1",
|
|
37
|
+
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
|
38
|
+
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!
|
|
39
|
+
}),
|
|
40
|
+
routes: {
|
|
41
|
+
avatar: image()
|
|
42
|
+
.max("2mb")
|
|
43
|
+
.auth(async ({ req }) => ({ id: req.headers.get("x-user-id")! }))
|
|
44
|
+
.key(({ user }) => `avatars/${user.id}.png`)
|
|
45
|
+
.done(async ({ file }) => {
|
|
46
|
+
console.log(file.url);
|
|
47
|
+
}),
|
|
48
|
+
gallery: image().max("8mb").multiple(10)
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export type Uploads = typeof uploads;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { createUploadClient } from "@uplift-io/uplift/client";
|
|
57
|
+
import type { Uploads } from "./uploads";
|
|
58
|
+
|
|
59
|
+
export const upload = createUploadClient<Uploads>("/api/upload");
|
|
60
|
+
|
|
61
|
+
const avatar = await upload.avatar(file);
|
|
62
|
+
const gallery = await upload.gallery(fileList);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Media Transforms
|
|
66
|
+
|
|
67
|
+
Core exports the typed `.transform()` and `.outputs()` pipeline, while optional domain packages own media behavior and dependencies:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { image } from "@uplift-io/uplift";
|
|
71
|
+
import { resize, convert, variant } from "@uplift-io/image";
|
|
72
|
+
|
|
73
|
+
const avatar = image()
|
|
74
|
+
.transform(resize({ width: 512, height: 512 }), convert("webp"))
|
|
75
|
+
.outputs(variant("thumb", resize({ width: 96 }), convert("webp")));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The frontend call remains `upload.avatar(file)`. Declared outputs are available with `uploaded.output("thumb")`.
|
|
79
|
+
|
|
80
|
+
Core stays free of Sharp and ffmpeg. Media packages own those runtime dependencies, and storage adapters may implement `delete(key)` so core can roll back already-written files when a later derived output write fails.
|
|
81
|
+
|
|
82
|
+
## More
|
|
83
|
+
|
|
84
|
+
- Full docs: [itzfeminisce.github.io/uplift](https://itzfeminisce.github.io/uplift/)
|
|
85
|
+
- Bundle size report: [docs/BUNDLE_SIZE.md](https://github.com/Itzfeminisce/uplift/blob/main/docs/BUNDLE_SIZE.md)
|
|
86
|
+
- Next local example: [examples/next-local](https://github.com/Itzfeminisce/uplift/tree/main/examples/next-local)
|
|
87
|
+
- GitHub: [github.com/Itzfeminisce/uplift](https://github.com/Itzfeminisce/uplift)
|
|
88
|
+
- License: MIT
|
|
89
|
+
|
|
90
|
+
## Storage
|
|
91
|
+
|
|
92
|
+
Uplift publishes S3, R2, Bunny, Cloudinary, local, memory, and UploadThing-compatible adapters as separate packages. The UploadThing adapter accepts a server-side uploader compatible with `UTApi.uploadFiles()` and keeps UploadThing optional:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { uploadthing } from "@uplift-io/uploadthing";
|
|
96
|
+
import { UTApi } from "uploadthing/server";
|
|
97
|
+
|
|
98
|
+
const utapi = new UTApi();
|
|
99
|
+
const storage = uploadthing({
|
|
100
|
+
uploader: (file) => utapi.uploadFiles(file)
|
|
101
|
+
});
|
|
102
|
+
```
|
package/dist/client.cjs
CHANGED
|
@@ -62,7 +62,7 @@ function createUploadClient(baseUrl, options = {}) {
|
|
|
62
62
|
throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
|
|
63
63
|
}
|
|
64
64
|
options.onProgress?.(property, 100);
|
|
65
|
-
return body.result;
|
|
65
|
+
return attachOutputGetters(body.result);
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
});
|
|
@@ -88,13 +88,34 @@ function uploadWithXhr(url, form, route, onProgress) {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
onProgress?.(route, 100);
|
|
91
|
-
resolve(body.result);
|
|
91
|
+
resolve(attachOutputGetters(body.result));
|
|
92
92
|
};
|
|
93
93
|
xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
|
|
94
94
|
onProgress?.(route, 0);
|
|
95
95
|
xhr.send(form);
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
+
function attachOutputGetters(result) {
|
|
99
|
+
if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));
|
|
100
|
+
if (!isUploadedFileLike(result)) return result;
|
|
101
|
+
if (!Object.prototype.hasOwnProperty.call(result, "output")) {
|
|
102
|
+
Object.defineProperty(result, "output", {
|
|
103
|
+
enumerable: false,
|
|
104
|
+
value(name) {
|
|
105
|
+
const output = result.outputs?.[name];
|
|
106
|
+
if (!output) throw new UploadError("VALIDATION_FAILED", `Unknown output: ${name}`);
|
|
107
|
+
return attachOutputGetters(output);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (result.outputs) {
|
|
112
|
+
for (const output of Object.values(result.outputs)) attachOutputGetters(output);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function isUploadedFileLike(value) {
|
|
117
|
+
return typeof value === "object" && value !== null;
|
|
118
|
+
}
|
|
98
119
|
// Annotate the CommonJS export names for ESM import in node:
|
|
99
120
|
0 && (module.exports = {
|
|
100
121
|
createUploadClient
|
package/dist/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/types.ts"],"sourcesContent":["import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\r\n\r\nexport type UploadProgressHandler = (progress: number) => void;\r\n\r\nexport function createUploadClient<TApp extends UpliftApp>(\r\n baseUrl: string,\r\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\r\n): UploadClient<TApp> {\r\n const fetcher = options.fetch ?? fetch;\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n return async (input: File | File[] | FileList) => {\r\n const files = input instanceof File ? [input] : Array.from(input);\r\n const form = new FormData();\r\n const field = files.length === 1 ? \"file\" : \"files\";\r\n for (const file of files) form.append(field, file);\r\n\r\n const url = routeUrl(baseUrl, property);\r\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\r\n return uploadWithXhr(url, form, property, options.onProgress);\r\n }\r\n\r\n options.onProgress?.(property, 0);\r\n const response = await fetcher(url, { method: \"POST\", body: form });\r\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\r\n if (!response.ok) {\r\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\r\n }\r\n options.onProgress?.(property, 100);\r\n return body.result;\r\n };\r\n }\r\n }) as UploadClient<TApp>;\r\n}\r\n\r\nfunction routeUrl(baseUrl: string, route: string): string {\r\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\r\n url.searchParams.set(\"route\", route);\r\n const value = url.toString();\r\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\r\n}\r\n\r\nfunction uploadWithXhr(\r\n url: string,\r\n form: FormData,\r\n route: string,\r\n onProgress?: (route: string, progress: number) => void\r\n): Promise<unknown> {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.upload.onprogress = (event) => {\r\n if (!event.lengthComputable) return;\r\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\r\n };\r\n xhr.onload = () => {\r\n const body = JSON.parse(xhr.responseText || \"{}\") as {\r\n result?: unknown;\r\n error?: { code: string; message: string };\r\n };\r\n if (xhr.status < 200 || xhr.status >= 300) {\r\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\r\n return;\r\n }\r\n onProgress?.(route, 100);\r\n resolve(body.result);\r\n };\r\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\r\n onProgress?.(route, 0);\r\n xhr.send(form);\r\n });\r\n}\r\n","export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\r\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\r\n\r\nexport type UploadInputFile = {\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string;\r\n file?: File;\r\n};\r\n\r\nexport type UploadedFile = {\r\n url: string;\r\n key: string;\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string | undefined;\r\n provider: string;\r\n};\r\n\r\nexport type UploadErrorCode =\r\n | \"FILE_TOO_LARGE\"\r\n | \"FILE_TOO_SMALL\"\r\n | \"INVALID_TYPE\"\r\n | \"AUTH_FAILED\"\r\n | \"VALIDATION_FAILED\"\r\n | \"UPLOAD_FAILED\"\r\n | \"UNKNOWN\";\r\n\r\nexport class UploadError extends Error {\r\n readonly code: UploadErrorCode;\r\n\r\n constructor(code: UploadErrorCode, message: string) {\r\n super(message);\r\n this.name = \"UploadError\";\r\n this.code = code;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n message: this.message,\r\n code: this.code\r\n };\r\n }\r\n}\r\n\r\nexport type StandardSchema<T = unknown> = {\r\n parse(input: unknown): T;\r\n};\r\n\r\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n};\r\n\r\nexport type DoneContext<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false\r\n> = TMultiple extends true\r\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\r\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\r\n\r\nexport type StoragePutInput = {\r\n key: string;\r\n file: UploadInputFile;\r\n body: File;\r\n};\r\n\r\nexport type StorageAdapter = {\r\n provider: string;\r\n put(input: StoragePutInput): Promise<UploadedFile>;\r\n};\r\n\r\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\r\n\r\nexport type UploadKind =\r\n | \"any\"\r\n | \"image\"\r\n | \"pdf\"\r\n | \"video\"\r\n | \"audio\"\r\n | \"text\"\r\n | \"json\"\r\n | \"csv\"\r\n | \"custom\";\r\n\r\nexport type UploadRouteDefinition = {\r\n kind: UploadKind;\r\n maxBytes?: number;\r\n minBytes?: number;\r\n multiple: boolean;\r\n multipleLimit?: number;\r\n auth?: Middleware<unknown>;\r\n overrideAuth: boolean;\r\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\r\n validate?: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n extensions?: string[];\r\n mimeTypes?: string[];\r\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\r\n requireSquare?: boolean;\r\n aspectRatio?: `${number}:${number}`;\r\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\r\n schema?: StandardSchema;\r\n headers?: string[];\r\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\r\n pageRule?: { min?: number; max?: number };\r\n encrypted?: boolean;\r\n durationRule?: { min?: DurationValue; max?: DurationValue };\r\n};\r\n\r\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\r\n\r\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown> | undefined;\r\n onUploadComplete?: ((ctx: {\r\n route: keyof TRoutes & string;\r\n result: UploadedFile | UploadedFile[];\r\n user: unknown;\r\n }) => void | Promise<void>) | undefined;\r\n};\r\n\r\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\r\n ? TMultiple extends true\r\n ? true\r\n : false\r\n : false;\r\n\r\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\r\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true ? UploadedFile[] : UploadedFile;\r\n\r\nexport type UploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: (\r\n input: ClientInput<TApp[\"routes\"][TRouteName]>\r\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC8BO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B;AAAA,EAET,YAAY,MAAuB,SAAiB;AAClD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,SAAS;AACP,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,IACb;AAAA,EACF;AACF;;;ADzCO,SAAS,mBACd,SACA,UAA4F,CAAC,GACzE;AACpB,QAAM,UAAU,QAAQ,SAAS;AAEjC,SAAO,IAAI,MAAM,CAAC,GAAG;AAAA,IACnB,IAAI,SAAS,UAAU;AACrB,UAAI,OAAO,aAAa,SAAU,QAAO;AACzC,aAAO,OAAO,UAAoC;AAChD,cAAM,QAAQ,iBAAiB,OAAO,CAAC,KAAK,IAAI,MAAM,KAAK,KAAK;AAChE,cAAM,OAAO,IAAI,SAAS;AAC1B,cAAM,QAAQ,MAAM,WAAW,IAAI,SAAS;AAC5C,mBAAW,QAAQ,MAAO,MAAK,OAAO,OAAO,IAAI;AAEjD,cAAM,MAAM,SAAS,SAAS,QAAQ;AACtC,YAAI,CAAC,QAAQ,SAAS,OAAO,mBAAmB,aAAa;AAC3D,iBAAO,cAAc,KAAK,MAAM,UAAU,QAAQ,UAAU;AAAA,QAC9D;AAEA,gBAAQ,aAAa,UAAU,CAAC;AAChC,cAAM,WAAW,MAAM,QAAQ,KAAK,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AAClE,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI,YAAa,KAAK,OAAO,QAAQ,WAAqB,KAAK,OAAO,WAAW,gBAAgB;AAAA,QACzG;AACA,gBAAQ,aAAa,UAAU,GAAG;AAClC,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,SAAS,SAAS,SAAiB,OAAuB;AACxD,QAAM,MAAM,IAAI,IAAI,SAAS,WAAW,UAAU,QAAQ,kBAAkB;AAC5E,MAAI,aAAa,IAAI,SAAS,KAAK;AACnC,QAAM,QAAQ,IAAI,SAAS;AAC3B,SAAO,QAAQ,WAAW,GAAG,IAAI,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,KAAK;AACpE;AAEA,SAAS,cACP,KACA,MACA,OACA,YACkB;AAClB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,eAAe;AAC/B,QAAI,KAAK,QAAQ,GAAG;AACpB,QAAI,OAAO,aAAa,CAAC,UAAU;AACjC,UAAI,CAAC,MAAM,iBAAkB;AAC7B,mBAAa,OAAO,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG,CAAC;AAAA,IACpE;AACA,QAAI,SAAS,MAAM;AACjB,YAAM,OAAO,KAAK,MAAM,IAAI,gBAAgB,IAAI;AAIhD,UAAI,IAAI,SAAS,OAAO,IAAI,UAAU,KAAK;AACzC,eAAO,IAAI,YAAa,KAAK,OAAO,QAAQ,WAAqB,KAAK,OAAO,WAAW,gBAAgB,CAAC;AACzG;AAAA,MACF;AACA,mBAAa,OAAO,GAAG;AACvB,cAAQ,KAAK,MAAM;AAAA,IACrB;AACA,QAAI,UAAU,MAAM,OAAO,IAAI,YAAY,iBAAiB,wBAAwB,CAAC;AACrF,iBAAa,OAAO,CAAC;AACrB,QAAI,KAAK,IAAI;AAAA,EACf,CAAC;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/types.ts"],"sourcesContent":["import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\n\nexport type UploadProgressHandler = (progress: number) => void;\n\nexport function createUploadClient<TApp extends UpliftApp>(\n baseUrl: string,\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\n): UploadClient<TApp> {\n const fetcher = options.fetch ?? fetch;\n\n return new Proxy({}, {\n get(_target, property) {\n if (typeof property !== \"string\") return undefined;\n return async (input: File | File[] | FileList) => {\n const files = input instanceof File ? [input] : Array.from(input);\n const form = new FormData();\n const field = files.length === 1 ? \"file\" : \"files\";\n for (const file of files) form.append(field, file);\n\n const url = routeUrl(baseUrl, property);\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\n return uploadWithXhr(url, form, property, options.onProgress);\n }\n\n options.onProgress?.(property, 0);\n const response = await fetcher(url, { method: \"POST\", body: form });\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\n if (!response.ok) {\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\n }\n options.onProgress?.(property, 100);\n return attachOutputGetters(body.result);\n };\n }\n }) as UploadClient<TApp>;\n}\n\nfunction routeUrl(baseUrl: string, route: string): string {\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\n url.searchParams.set(\"route\", route);\n const value = url.toString();\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\n}\n\nfunction uploadWithXhr(\n url: string,\n form: FormData,\n route: string,\n onProgress?: (route: string, progress: number) => void\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable) return;\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\n };\n xhr.onload = () => {\n const body = JSON.parse(xhr.responseText || \"{}\") as {\n result?: unknown;\n error?: { code: string; message: string };\n };\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\n return;\n }\n onProgress?.(route, 100);\n resolve(attachOutputGetters(body.result));\n };\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\n onProgress?.(route, 0);\n xhr.send(form);\n });\n}\n\nfunction attachOutputGetters(result: unknown): unknown {\n if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));\n if (!isUploadedFileLike(result)) return result;\n if (!Object.prototype.hasOwnProperty.call(result, \"output\")) {\n Object.defineProperty(result, \"output\", {\n enumerable: false,\n value(name: string) {\n const output = result.outputs?.[name];\n if (!output) throw new UploadError(\"VALIDATION_FAILED\", `Unknown output: ${name}`);\n return attachOutputGetters(output);\n }\n });\n }\n if (result.outputs) {\n for (const output of Object.values(result.outputs)) attachOutputGetters(output);\n }\n return result;\n}\n\nfunction isUploadedFileLike(value: unknown): value is {\n outputs?: Record<string, unknown>;\n output?: (name: string) => unknown;\n} {\n return typeof value === \"object\" && value !== null;\n}\n","export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\n\nexport type UploadInputFile = {\n name: string;\n type: string;\n size: number;\n extension?: string;\n file?: File;\n};\n\nexport type UploadedFile = {\n url: string;\n key: string;\n name: string;\n type: string;\n size: number;\n extension?: string | undefined;\n provider: string;\n outputs?: Record<string, UploadedFile> | undefined;\n};\n\nexport type ClientUploadedFile<TOutputNames extends string = never> = UploadedFile & (\n [TOutputNames] extends [never]\n ? object\n : {\n output<TName extends TOutputNames>(name: TName): UploadedFile;\n }\n);\n\nexport type UploadErrorCode =\n | \"FILE_TOO_LARGE\"\n | \"FILE_TOO_SMALL\"\n | \"INVALID_TYPE\"\n | \"AUTH_FAILED\"\n | \"VALIDATION_FAILED\"\n | \"UPLOAD_FAILED\"\n | \"UNKNOWN\";\n\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message: string) {\n super(message);\n this.name = \"UploadError\";\n this.code = code;\n }\n\n toJSON() {\n return {\n message: this.message,\n code: this.code\n };\n }\n}\n\nexport type StandardSchema<T = unknown> = {\n parse(input: unknown): T;\n};\n\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n};\n\nexport type DoneContext<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false\n> = TMultiple extends true\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\n\nexport type StoragePutInput = {\n key: string;\n file: UploadInputFile;\n body: File;\n};\n\nexport type StorageAdapter = {\n provider: string;\n put(input: StoragePutInput): Promise<UploadedFile>;\n delete?(key: string): Promise<void>;\n};\n\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\n\nexport type UploadKind =\n | \"any\"\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"text\"\n | \"json\"\n | \"csv\"\n | \"custom\";\n\nexport type PreparedUploadFile = {\n file: UploadInputFile;\n body: File;\n};\n\nexport type TransformContext = PreparedUploadFile;\n\nexport type UploadTransform<TKind extends UploadKind = UploadKind> = {\n readonly __kind?: TKind | undefined;\n transform(ctx: TransformContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type UploadTransformFunction<TKind extends UploadKind = UploadKind> = ((\n ctx: TransformContext\n) => File | PreparedUploadFile | Promise<File | PreparedUploadFile>) & {\n readonly __kind?: TKind | undefined;\n};\n\nexport type CompatibleTransform<TKind extends UploadKind> = TKind extends \"any\" | \"custom\"\n ? UploadTransform<UploadKind> | UploadTransformFunction<UploadKind>\n : UploadTransform<TKind> | UploadTransform<\"any\"> | UploadTransformFunction<TKind> | UploadTransformFunction<\"any\">;\n\nexport type OutputContext = PreparedUploadFile & {\n primary: UploadedFile;\n};\n\nexport type UploadOutput<TKind extends UploadKind = UploadKind, TName extends string = string> = {\n readonly __kind?: TKind | undefined;\n name: TName;\n produce(ctx: OutputContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type CompatibleOutput<TKind extends UploadKind, TName extends string = string> = TKind extends \"any\" | \"custom\"\n ? UploadOutput<UploadKind, TName>\n : UploadOutput<TKind, TName> | UploadOutput<\"any\", TName>;\n\nexport type UploadRouteDefinition = {\n kind: UploadKind;\n maxBytes?: number;\n minBytes?: number;\n multiple: boolean;\n multipleLimit?: number;\n auth?: Middleware<unknown>;\n overrideAuth: boolean;\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\n validate?: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n extensions?: string[];\n mimeTypes?: string[];\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\n requireSquare?: boolean;\n aspectRatio?: `${number}:${number}`;\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\n schema?: StandardSchema;\n headers?: string[];\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\n pageRule?: { min?: number; max?: number };\n encrypted?: boolean;\n durationRule?: { min?: DurationValue; max?: DurationValue };\n transforms?: Array<UploadTransform | UploadTransformFunction>;\n outputs?: Array<UploadOutput>;\n};\n\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\n\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown> | undefined;\n onUploadComplete?: ((ctx: {\n route: keyof TRoutes & string;\n result: UploadedFile | UploadedFile[];\n user: unknown;\n }) => void | Promise<void>) | undefined;\n};\n\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\n ? TMultiple extends true\n ? true\n : false\n : false;\n\nexport type OutputNames<TRoute> = TRoute extends { __outputs?: infer TOutputNames }\n ? TOutputNames extends string\n ? TOutputNames\n : never\n : never;\n\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true\n ? Array<ClientUploadedFile<OutputNames<TRoute>>>\n : ClientUploadedFile<OutputNames<TRoute>>;\n\nexport type UploadClient<TApp extends UpliftApp> = {\n [TRouteName in keyof TApp[\"routes\"] & string]: (\n input: ClientInput<TApp[\"routes\"][TRouteName]>\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\n};\n"],"mappings":"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","names":[]}
|
package/dist/client.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as UpliftApp, n as UploadClient } from './types-Cw4fuNs_.cjs';
|
|
2
2
|
|
|
3
3
|
type UploadProgressHandler = (progress: number) => void;
|
|
4
4
|
declare function createUploadClient<TApp extends UpliftApp>(baseUrl: string, options?: {
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as UpliftApp, n as UploadClient } from './types-Cw4fuNs_.js';
|
|
2
2
|
|
|
3
3
|
type UploadProgressHandler = (progress: number) => void;
|
|
4
4
|
declare function createUploadClient<TApp extends UpliftApp>(baseUrl: string, options?: {
|
package/dist/client.js
CHANGED
|
@@ -36,7 +36,7 @@ function createUploadClient(baseUrl, options = {}) {
|
|
|
36
36
|
throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
|
|
37
37
|
}
|
|
38
38
|
options.onProgress?.(property, 100);
|
|
39
|
-
return body.result;
|
|
39
|
+
return attachOutputGetters(body.result);
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -62,13 +62,34 @@ function uploadWithXhr(url, form, route, onProgress) {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
onProgress?.(route, 100);
|
|
65
|
-
resolve(body.result);
|
|
65
|
+
resolve(attachOutputGetters(body.result));
|
|
66
66
|
};
|
|
67
67
|
xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
|
|
68
68
|
onProgress?.(route, 0);
|
|
69
69
|
xhr.send(form);
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
|
+
function attachOutputGetters(result) {
|
|
73
|
+
if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));
|
|
74
|
+
if (!isUploadedFileLike(result)) return result;
|
|
75
|
+
if (!Object.prototype.hasOwnProperty.call(result, "output")) {
|
|
76
|
+
Object.defineProperty(result, "output", {
|
|
77
|
+
enumerable: false,
|
|
78
|
+
value(name) {
|
|
79
|
+
const output = result.outputs?.[name];
|
|
80
|
+
if (!output) throw new UploadError("VALIDATION_FAILED", `Unknown output: ${name}`);
|
|
81
|
+
return attachOutputGetters(output);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (result.outputs) {
|
|
86
|
+
for (const output of Object.values(result.outputs)) attachOutputGetters(output);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
function isUploadedFileLike(value) {
|
|
91
|
+
return typeof value === "object" && value !== null;
|
|
92
|
+
}
|
|
72
93
|
export {
|
|
73
94
|
createUploadClient
|
|
74
95
|
};
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/client.ts"],"sourcesContent":["export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\r\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\r\n\r\nexport type UploadInputFile = {\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string;\r\n file?: File;\r\n};\r\n\r\nexport type UploadedFile = {\r\n url: string;\r\n key: string;\r\n name: string;\r\n type: string;\r\n size: number;\r\n extension?: string | undefined;\r\n provider: string;\r\n};\r\n\r\nexport type UploadErrorCode =\r\n | \"FILE_TOO_LARGE\"\r\n | \"FILE_TOO_SMALL\"\r\n | \"INVALID_TYPE\"\r\n | \"AUTH_FAILED\"\r\n | \"VALIDATION_FAILED\"\r\n | \"UPLOAD_FAILED\"\r\n | \"UNKNOWN\";\r\n\r\nexport class UploadError extends Error {\r\n readonly code: UploadErrorCode;\r\n\r\n constructor(code: UploadErrorCode, message: string) {\r\n super(message);\r\n this.name = \"UploadError\";\r\n this.code = code;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n message: this.message,\r\n code: this.code\r\n };\r\n }\r\n}\r\n\r\nexport type StandardSchema<T = unknown> = {\r\n parse(input: unknown): T;\r\n};\r\n\r\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: TAuth;\r\n meta: TMeta;\r\n};\r\n\r\nexport type DoneContext<\r\n TAuth = unknown,\r\n TMeta = unknown,\r\n TMultiple extends boolean = false\r\n> = TMultiple extends true\r\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\r\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\r\n\r\nexport type StoragePutInput = {\r\n key: string;\r\n file: UploadInputFile;\r\n body: File;\r\n};\r\n\r\nexport type StorageAdapter = {\r\n provider: string;\r\n put(input: StoragePutInput): Promise<UploadedFile>;\r\n};\r\n\r\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\r\n\r\nexport type UploadKind =\r\n | \"any\"\r\n | \"image\"\r\n | \"pdf\"\r\n | \"video\"\r\n | \"audio\"\r\n | \"text\"\r\n | \"json\"\r\n | \"csv\"\r\n | \"custom\";\r\n\r\nexport type UploadRouteDefinition = {\r\n kind: UploadKind;\r\n maxBytes?: number;\r\n minBytes?: number;\r\n multiple: boolean;\r\n multipleLimit?: number;\r\n auth?: Middleware<unknown>;\r\n overrideAuth: boolean;\r\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\r\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\r\n validate?: (ctx: {\r\n req: Request;\r\n file: UploadInputFile;\r\n user: unknown;\r\n meta: unknown;\r\n }) => true | string | Promise<true | string>;\r\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\r\n extensions?: string[];\r\n mimeTypes?: string[];\r\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\r\n requireSquare?: boolean;\r\n aspectRatio?: `${number}:${number}`;\r\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\r\n schema?: StandardSchema;\r\n headers?: string[];\r\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\r\n pageRule?: { min?: number; max?: number };\r\n encrypted?: boolean;\r\n durationRule?: { min?: DurationValue; max?: DurationValue };\r\n};\r\n\r\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\r\n\r\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\r\n storage: StorageAdapter;\r\n routes: TRoutes;\r\n middleware?: Middleware<unknown> | undefined;\r\n onUploadComplete?: ((ctx: {\r\n route: keyof TRoutes & string;\r\n result: UploadedFile | UploadedFile[];\r\n user: unknown;\r\n }) => void | Promise<void>) | undefined;\r\n};\r\n\r\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\r\n ? TMultiple extends true\r\n ? true\r\n : false\r\n : false;\r\n\r\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\r\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true ? UploadedFile[] : UploadedFile;\r\n\r\nexport type UploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: (\r\n input: ClientInput<TApp[\"routes\"][TRouteName]>\r\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n};\r\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\r\n\r\nexport type UploadProgressHandler = (progress: number) => void;\r\n\r\nexport function createUploadClient<TApp extends UpliftApp>(\r\n baseUrl: string,\r\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\r\n): UploadClient<TApp> {\r\n const fetcher = options.fetch ?? fetch;\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n return async (input: File | File[] | FileList) => {\r\n const files = input instanceof File ? [input] : Array.from(input);\r\n const form = new FormData();\r\n const field = files.length === 1 ? \"file\" : \"files\";\r\n for (const file of files) form.append(field, file);\r\n\r\n const url = routeUrl(baseUrl, property);\r\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\r\n return uploadWithXhr(url, form, property, options.onProgress);\r\n }\r\n\r\n options.onProgress?.(property, 0);\r\n const response = await fetcher(url, { method: \"POST\", body: form });\r\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\r\n if (!response.ok) {\r\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\r\n }\r\n options.onProgress?.(property, 100);\r\n return body.result;\r\n };\r\n }\r\n }) as UploadClient<TApp>;\r\n}\r\n\r\nfunction routeUrl(baseUrl: string, route: string): string {\r\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\r\n url.searchParams.set(\"route\", route);\r\n const value = url.toString();\r\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\r\n}\r\n\r\nfunction uploadWithXhr(\r\n url: string,\r\n form: FormData,\r\n route: string,\r\n onProgress?: (route: string, progress: number) => void\r\n): Promise<unknown> {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.upload.onprogress = (event) => {\r\n if (!event.lengthComputable) return;\r\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\r\n };\r\n xhr.onload = () => {\r\n const body = JSON.parse(xhr.responseText || \"{}\") as {\r\n result?: unknown;\r\n error?: { code: string; message: string };\r\n };\r\n if (xhr.status < 200 || xhr.status >= 300) {\r\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\r\n return;\r\n }\r\n onProgress?.(route, 100);\r\n resolve(body.result);\r\n };\r\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\r\n onProgress?.(route, 0);\r\n xhr.send(form);\r\n });\r\n}\r\n"],"mappings":";AA8BO,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC5B;AAAA,EAET,YAAY,MAAuB,SAAiB;AAClD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,SAAS;AACP,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,IACb;AAAA,EACF;AACF;;;ACzCO,SAAS,mBACd,SACA,UAA4F,CAAC,GACzE;AACpB,QAAM,UAAU,QAAQ,SAAS;AAEjC,SAAO,IAAI,MAAM,CAAC,GAAG;AAAA,IACnB,IAAI,SAAS,UAAU;AACrB,UAAI,OAAO,aAAa,SAAU,QAAO;AACzC,aAAO,OAAO,UAAoC;AAChD,cAAM,QAAQ,iBAAiB,OAAO,CAAC,KAAK,IAAI,MAAM,KAAK,KAAK;AAChE,cAAM,OAAO,IAAI,SAAS;AAC1B,cAAM,QAAQ,MAAM,WAAW,IAAI,SAAS;AAC5C,mBAAW,QAAQ,MAAO,MAAK,OAAO,OAAO,IAAI;AAEjD,cAAM,MAAM,SAAS,SAAS,QAAQ;AACtC,YAAI,CAAC,QAAQ,SAAS,OAAO,mBAAmB,aAAa;AAC3D,iBAAO,cAAc,KAAK,MAAM,UAAU,QAAQ,UAAU;AAAA,QAC9D;AAEA,gBAAQ,aAAa,UAAU,CAAC;AAChC,cAAM,WAAW,MAAM,QAAQ,KAAK,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AAClE,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI,YAAa,KAAK,OAAO,QAAQ,WAAqB,KAAK,OAAO,WAAW,gBAAgB;AAAA,QACzG;AACA,gBAAQ,aAAa,UAAU,GAAG;AAClC,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,SAAS,SAAS,SAAiB,OAAuB;AACxD,QAAM,MAAM,IAAI,IAAI,SAAS,WAAW,UAAU,QAAQ,kBAAkB;AAC5E,MAAI,aAAa,IAAI,SAAS,KAAK;AACnC,QAAM,QAAQ,IAAI,SAAS;AAC3B,SAAO,QAAQ,WAAW,GAAG,IAAI,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM,KAAK;AACpE;AAEA,SAAS,cACP,KACA,MACA,OACA,YACkB;AAClB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,eAAe;AAC/B,QAAI,KAAK,QAAQ,GAAG;AACpB,QAAI,OAAO,aAAa,CAAC,UAAU;AACjC,UAAI,CAAC,MAAM,iBAAkB;AAC7B,mBAAa,OAAO,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG,CAAC;AAAA,IACpE;AACA,QAAI,SAAS,MAAM;AACjB,YAAM,OAAO,KAAK,MAAM,IAAI,gBAAgB,IAAI;AAIhD,UAAI,IAAI,SAAS,OAAO,IAAI,UAAU,KAAK;AACzC,eAAO,IAAI,YAAa,KAAK,OAAO,QAAQ,WAAqB,KAAK,OAAO,WAAW,gBAAgB,CAAC;AACzG;AAAA,MACF;AACA,mBAAa,OAAO,GAAG;AACvB,cAAQ,KAAK,MAAM;AAAA,IACrB;AACA,QAAI,UAAU,MAAM,OAAO,IAAI,YAAY,iBAAiB,wBAAwB,CAAC;AACrF,iBAAa,OAAO,CAAC;AACrB,QAAI,KAAK,IAAI;AAAA,EACf,CAAC;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/client.ts"],"sourcesContent":["export type SizeValue = `${number}b` | `${number}kb` | `${number}mb` | `${number}gb`;\nexport type DurationValue = `${number}s` | `${number}m` | `${number}h`;\n\nexport type UploadInputFile = {\n name: string;\n type: string;\n size: number;\n extension?: string;\n file?: File;\n};\n\nexport type UploadedFile = {\n url: string;\n key: string;\n name: string;\n type: string;\n size: number;\n extension?: string | undefined;\n provider: string;\n outputs?: Record<string, UploadedFile> | undefined;\n};\n\nexport type ClientUploadedFile<TOutputNames extends string = never> = UploadedFile & (\n [TOutputNames] extends [never]\n ? object\n : {\n output<TName extends TOutputNames>(name: TName): UploadedFile;\n }\n);\n\nexport type UploadErrorCode =\n | \"FILE_TOO_LARGE\"\n | \"FILE_TOO_SMALL\"\n | \"INVALID_TYPE\"\n | \"AUTH_FAILED\"\n | \"VALIDATION_FAILED\"\n | \"UPLOAD_FAILED\"\n | \"UNKNOWN\";\n\nexport class UploadError extends Error {\n readonly code: UploadErrorCode;\n\n constructor(code: UploadErrorCode, message: string) {\n super(message);\n this.name = \"UploadError\";\n this.code = code;\n }\n\n toJSON() {\n return {\n message: this.message,\n code: this.code\n };\n }\n}\n\nexport type StandardSchema<T = unknown> = {\n parse(input: unknown): T;\n};\n\nexport type KeyContext<TAuth = unknown, TMeta = unknown> = {\n req: Request;\n file: UploadInputFile;\n user: TAuth;\n meta: TMeta;\n};\n\nexport type DoneContext<\n TAuth = unknown,\n TMeta = unknown,\n TMultiple extends boolean = false\n> = TMultiple extends true\n ? { req: Request; files: UploadedFile[]; user: TAuth; meta: TMeta[] }\n : { req: Request; file: UploadedFile; user: TAuth; meta: TMeta };\n\nexport type StoragePutInput = {\n key: string;\n file: UploadInputFile;\n body: File;\n};\n\nexport type StorageAdapter = {\n provider: string;\n put(input: StoragePutInput): Promise<UploadedFile>;\n delete?(key: string): Promise<void>;\n};\n\nexport type Middleware<TUser = unknown> = (ctx: { req: Request }) => TUser | Promise<TUser>;\n\nexport type UploadKind =\n | \"any\"\n | \"image\"\n | \"pdf\"\n | \"video\"\n | \"audio\"\n | \"text\"\n | \"json\"\n | \"csv\"\n | \"custom\";\n\nexport type PreparedUploadFile = {\n file: UploadInputFile;\n body: File;\n};\n\nexport type TransformContext = PreparedUploadFile;\n\nexport type UploadTransform<TKind extends UploadKind = UploadKind> = {\n readonly __kind?: TKind | undefined;\n transform(ctx: TransformContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type UploadTransformFunction<TKind extends UploadKind = UploadKind> = ((\n ctx: TransformContext\n) => File | PreparedUploadFile | Promise<File | PreparedUploadFile>) & {\n readonly __kind?: TKind | undefined;\n};\n\nexport type CompatibleTransform<TKind extends UploadKind> = TKind extends \"any\" | \"custom\"\n ? UploadTransform<UploadKind> | UploadTransformFunction<UploadKind>\n : UploadTransform<TKind> | UploadTransform<\"any\"> | UploadTransformFunction<TKind> | UploadTransformFunction<\"any\">;\n\nexport type OutputContext = PreparedUploadFile & {\n primary: UploadedFile;\n};\n\nexport type UploadOutput<TKind extends UploadKind = UploadKind, TName extends string = string> = {\n readonly __kind?: TKind | undefined;\n name: TName;\n produce(ctx: OutputContext): File | PreparedUploadFile | Promise<File | PreparedUploadFile>;\n};\n\nexport type CompatibleOutput<TKind extends UploadKind, TName extends string = string> = TKind extends \"any\" | \"custom\"\n ? UploadOutput<UploadKind, TName>\n : UploadOutput<TKind, TName> | UploadOutput<\"any\", TName>;\n\nexport type UploadRouteDefinition = {\n kind: UploadKind;\n maxBytes?: number;\n minBytes?: number;\n multiple: boolean;\n multipleLimit?: number;\n auth?: Middleware<unknown>;\n overrideAuth: boolean;\n key?: (ctx: KeyContext<unknown, unknown>) => string | Promise<string>;\n meta?: (ctx: { req: Request; file: UploadInputFile; user: unknown }) => unknown | Promise<unknown>;\n validate?: (ctx: {\n req: Request;\n file: UploadInputFile;\n user: unknown;\n meta: unknown;\n }) => true | string | Promise<true | string>;\n done?: (ctx: DoneContext<unknown, unknown, boolean>) => void | Promise<void>;\n extensions?: string[];\n mimeTypes?: string[];\n dimensionRule?: { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number };\n requireSquare?: boolean;\n aspectRatio?: `${number}:${number}`;\n encoding?: \"utf-8\" | \"utf-16\" | \"ascii\";\n schema?: StandardSchema;\n headers?: string[];\n delimiter?: \",\" | \";\" | \"\\t\" | \"|\";\n pageRule?: { min?: number; max?: number };\n encrypted?: boolean;\n durationRule?: { min?: DurationValue; max?: DurationValue };\n transforms?: Array<UploadTransform | UploadTransformFunction>;\n outputs?: Array<UploadOutput>;\n};\n\nexport type UploadRoutes = Record<string, { _def: UploadRouteDefinition }>;\n\nexport type UpliftApp<TRoutes extends UploadRoutes = UploadRoutes> = {\n storage: StorageAdapter;\n routes: TRoutes;\n middleware?: Middleware<unknown> | undefined;\n onUploadComplete?: ((ctx: {\n route: keyof TRoutes & string;\n result: UploadedFile | UploadedFile[];\n user: unknown;\n }) => void | Promise<void>) | undefined;\n};\n\nexport type IsMultiple<TRoute> = TRoute extends { __multiple?: infer TMultiple }\n ? TMultiple extends true\n ? true\n : false\n : false;\n\nexport type OutputNames<TRoute> = TRoute extends { __outputs?: infer TOutputNames }\n ? TOutputNames extends string\n ? TOutputNames\n : never\n : never;\n\nexport type ClientInput<TRoute> = IsMultiple<TRoute> extends true ? File[] | FileList : File;\nexport type ClientOutput<TRoute> = IsMultiple<TRoute> extends true\n ? Array<ClientUploadedFile<OutputNames<TRoute>>>\n : ClientUploadedFile<OutputNames<TRoute>>;\n\nexport type UploadClient<TApp extends UpliftApp> = {\n [TRouteName in keyof TApp[\"routes\"] & string]: (\n input: ClientInput<TApp[\"routes\"][TRouteName]>\n ) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>;\n};\n","import { UploadError, type UpliftApp, type UploadClient } from \"./types\";\n\nexport type UploadProgressHandler = (progress: number) => void;\n\nexport function createUploadClient<TApp extends UpliftApp>(\n baseUrl: string,\n options: { fetch?: typeof fetch; onProgress?: (route: string, progress: number) => void } = {}\n): UploadClient<TApp> {\n const fetcher = options.fetch ?? fetch;\n\n return new Proxy({}, {\n get(_target, property) {\n if (typeof property !== \"string\") return undefined;\n return async (input: File | File[] | FileList) => {\n const files = input instanceof File ? [input] : Array.from(input);\n const form = new FormData();\n const field = files.length === 1 ? \"file\" : \"files\";\n for (const file of files) form.append(field, file);\n\n const url = routeUrl(baseUrl, property);\n if (!options.fetch && typeof XMLHttpRequest !== \"undefined\") {\n return uploadWithXhr(url, form, property, options.onProgress);\n }\n\n options.onProgress?.(property, 0);\n const response = await fetcher(url, { method: \"POST\", body: form });\n const body = await response.json() as { result?: unknown; error?: { code: string; message: string } };\n if (!response.ok) {\n throw new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\");\n }\n options.onProgress?.(property, 100);\n return attachOutputGetters(body.result);\n };\n }\n }) as UploadClient<TApp>;\n}\n\nfunction routeUrl(baseUrl: string, route: string): string {\n const url = new URL(baseUrl, globalThis.location?.href ?? \"http://localhost\");\n url.searchParams.set(\"route\", route);\n const value = url.toString();\n return baseUrl.startsWith(\"/\") ? `${url.pathname}${url.search}` : value;\n}\n\nfunction uploadWithXhr(\n url: string,\n form: FormData,\n route: string,\n onProgress?: (route: string, progress: number) => void\n): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.upload.onprogress = (event) => {\n if (!event.lengthComputable) return;\n onProgress?.(route, Math.round((event.loaded / event.total) * 100));\n };\n xhr.onload = () => {\n const body = JSON.parse(xhr.responseText || \"{}\") as {\n result?: unknown;\n error?: { code: string; message: string };\n };\n if (xhr.status < 200 || xhr.status >= 300) {\n reject(new UploadError((body.error?.code ?? \"UNKNOWN\") as never, body.error?.message ?? \"Upload failed.\"));\n return;\n }\n onProgress?.(route, 100);\n resolve(attachOutputGetters(body.result));\n };\n xhr.onerror = () => reject(new UploadError(\"UPLOAD_FAILED\", \"Upload request failed.\"));\n onProgress?.(route, 0);\n xhr.send(form);\n });\n}\n\nfunction attachOutputGetters(result: unknown): unknown {\n if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));\n if (!isUploadedFileLike(result)) return result;\n if (!Object.prototype.hasOwnProperty.call(result, \"output\")) {\n Object.defineProperty(result, \"output\", {\n enumerable: false,\n value(name: string) {\n const output = result.outputs?.[name];\n if (!output) throw new UploadError(\"VALIDATION_FAILED\", `Unknown output: ${name}`);\n return attachOutputGetters(output);\n }\n });\n }\n if (result.outputs) {\n for (const output of Object.values(result.outputs)) attachOutputGetters(output);\n }\n return result;\n}\n\nfunction isUploadedFileLike(value: unknown): value is {\n outputs?: Record<string, unknown>;\n output?: (name: string) => unknown;\n} {\n return typeof value === \"object\" && value !== null;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -73,6 +73,7 @@ var UploadBuilder = class {
|
|
|
73
73
|
__meta;
|
|
74
74
|
__multiple;
|
|
75
75
|
__kind;
|
|
76
|
+
__outputs;
|
|
76
77
|
_def;
|
|
77
78
|
constructor(kind, mimeTypes = [], extensions = []) {
|
|
78
79
|
this._def = {
|
|
@@ -166,6 +167,14 @@ var UploadBuilder = class {
|
|
|
166
167
|
this._def.durationRule = rule;
|
|
167
168
|
return this;
|
|
168
169
|
}
|
|
170
|
+
transform(...transforms) {
|
|
171
|
+
this._def.transforms = [...this._def.transforms ?? [], ...transforms];
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
outputs(...outputs) {
|
|
175
|
+
this._def.outputs = [...this._def.outputs ?? [], ...outputs];
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
169
178
|
};
|
|
170
179
|
function any() {
|
|
171
180
|
return new UploadBuilder("any");
|
|
@@ -218,7 +227,7 @@ function createUploadClient(baseUrl, options = {}) {
|
|
|
218
227
|
throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
|
|
219
228
|
}
|
|
220
229
|
options.onProgress?.(property, 100);
|
|
221
|
-
return body.result;
|
|
230
|
+
return attachOutputGetters(body.result);
|
|
222
231
|
};
|
|
223
232
|
}
|
|
224
233
|
});
|
|
@@ -244,13 +253,34 @@ function uploadWithXhr(url, form, route, onProgress) {
|
|
|
244
253
|
return;
|
|
245
254
|
}
|
|
246
255
|
onProgress?.(route, 100);
|
|
247
|
-
resolve(body.result);
|
|
256
|
+
resolve(attachOutputGetters(body.result));
|
|
248
257
|
};
|
|
249
258
|
xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
|
|
250
259
|
onProgress?.(route, 0);
|
|
251
260
|
xhr.send(form);
|
|
252
261
|
});
|
|
253
262
|
}
|
|
263
|
+
function attachOutputGetters(result) {
|
|
264
|
+
if (Array.isArray(result)) return result.map((item) => attachOutputGetters(item));
|
|
265
|
+
if (!isUploadedFileLike(result)) return result;
|
|
266
|
+
if (!Object.prototype.hasOwnProperty.call(result, "output")) {
|
|
267
|
+
Object.defineProperty(result, "output", {
|
|
268
|
+
enumerable: false,
|
|
269
|
+
value(name) {
|
|
270
|
+
const output = result.outputs?.[name];
|
|
271
|
+
if (!output) throw new UploadError("VALIDATION_FAILED", `Unknown output: ${name}`);
|
|
272
|
+
return attachOutputGetters(output);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (result.outputs) {
|
|
277
|
+
for (const output of Object.values(result.outputs)) attachOutputGetters(output);
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
function isUploadedFileLike(value) {
|
|
282
|
+
return typeof value === "object" && value !== null;
|
|
283
|
+
}
|
|
254
284
|
|
|
255
285
|
// src/index.ts
|
|
256
286
|
function uplift(config) {
|