@uplift-io/uplift 1.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts","../src/types.ts","../src/client.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\r\nimport { createUploadClient } from \"./client\";\r\nimport type { ClientInput, ClientOutput, UpliftApp, UploadError } from \"./types\";\r\n\r\ntype RouteState<TData> = {\r\n progress: number;\r\n isUploading: boolean;\r\n error: UploadError | null;\r\n data: TData | null;\r\n};\r\n\r\ntype ReactUploadMethod<TApp extends UpliftApp, TRouteName extends keyof TApp[\"routes\"] & string> =\r\n ((input: ClientInput<TApp[\"routes\"][TRouteName]>) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>) &\r\n RouteState<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n\r\nexport type ReactUploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: ReactUploadMethod<TApp, TRouteName>;\r\n};\r\n\r\nexport function useUploads<TApp extends UpliftApp>(baseUrl: string): ReactUploadClient<TApp> {\r\n const [states, setStates] = useState<Record<string, RouteState<unknown>>>({});\r\n\r\n return useMemo(() => {\r\n const client = createUploadClient<TApp>(baseUrl, {\r\n onProgress(route, progress) {\r\n setStates((current) => ({\r\n ...current,\r\n [route]: { ...(current[route] ?? emptyState()), progress }\r\n }));\r\n }\r\n });\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n const state = states[property] ?? emptyState();\r\n const method = async (input: never) => {\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { ...(current[property] ?? emptyState()), isUploading: true, error: null }\r\n }));\r\n try {\r\n const upload = (client as Record<string, (value: never) => Promise<unknown>>)[property];\r\n if (!upload) throw new Error(`Unknown upload route: ${property}`);\r\n const data = await upload(input);\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { progress: 100, isUploading: false, error: null, data }\r\n }));\r\n return data;\r\n } catch (error) {\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { ...(current[property] ?? emptyState()), isUploading: false, error: error as UploadError }\r\n }));\r\n throw error;\r\n }\r\n };\r\n\r\n return Object.assign(method, state);\r\n }\r\n }) as ReactUploadClient<TApp>;\r\n }, [baseUrl, states]);\r\n}\r\n\r\nfunction emptyState(): RouteState<unknown> {\r\n return {\r\n progress: 0,\r\n isUploading: false,\r\n error: null,\r\n data: null\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","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":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import { g as UpliftApp, C as ClientInput, h as ClientOutput, k as UploadError } from './types-BdcszAj8.cjs';
2
+
3
+ type RouteState<TData> = {
4
+ progress: number;
5
+ isUploading: boolean;
6
+ error: UploadError | null;
7
+ data: TData | null;
8
+ };
9
+ type ReactUploadMethod<TApp extends UpliftApp, TRouteName extends keyof TApp["routes"] & string> = ((input: ClientInput<TApp["routes"][TRouteName]>) => Promise<ClientOutput<TApp["routes"][TRouteName]>>) & RouteState<ClientOutput<TApp["routes"][TRouteName]>>;
10
+ type ReactUploadClient<TApp extends UpliftApp> = {
11
+ [TRouteName in keyof TApp["routes"] & string]: ReactUploadMethod<TApp, TRouteName>;
12
+ };
13
+ declare function useUploads<TApp extends UpliftApp>(baseUrl: string): ReactUploadClient<TApp>;
14
+
15
+ export { type ReactUploadClient, useUploads };
@@ -0,0 +1,15 @@
1
+ import { g as UpliftApp, C as ClientInput, h as ClientOutput, k as UploadError } from './types-BdcszAj8.js';
2
+
3
+ type RouteState<TData> = {
4
+ progress: number;
5
+ isUploading: boolean;
6
+ error: UploadError | null;
7
+ data: TData | null;
8
+ };
9
+ type ReactUploadMethod<TApp extends UpliftApp, TRouteName extends keyof TApp["routes"] & string> = ((input: ClientInput<TApp["routes"][TRouteName]>) => Promise<ClientOutput<TApp["routes"][TRouteName]>>) & RouteState<ClientOutput<TApp["routes"][TRouteName]>>;
10
+ type ReactUploadClient<TApp extends UpliftApp> = {
11
+ [TRouteName in keyof TApp["routes"] & string]: ReactUploadMethod<TApp, TRouteName>;
12
+ };
13
+ declare function useUploads<TApp extends UpliftApp>(baseUrl: string): ReactUploadClient<TApp>;
14
+
15
+ export { type ReactUploadClient, useUploads };
package/dist/react.js ADDED
@@ -0,0 +1,130 @@
1
+ // src/react.ts
2
+ import { useMemo, useState } from "react";
3
+
4
+ // src/types.ts
5
+ var UploadError = class extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = "UploadError";
10
+ this.code = code;
11
+ }
12
+ toJSON() {
13
+ return {
14
+ message: this.message,
15
+ code: this.code
16
+ };
17
+ }
18
+ };
19
+
20
+ // src/client.ts
21
+ function createUploadClient(baseUrl, options = {}) {
22
+ const fetcher = options.fetch ?? fetch;
23
+ return new Proxy({}, {
24
+ get(_target, property) {
25
+ if (typeof property !== "string") return void 0;
26
+ return async (input) => {
27
+ const files = input instanceof File ? [input] : Array.from(input);
28
+ const form = new FormData();
29
+ const field = files.length === 1 ? "file" : "files";
30
+ for (const file of files) form.append(field, file);
31
+ const url = routeUrl(baseUrl, property);
32
+ if (!options.fetch && typeof XMLHttpRequest !== "undefined") {
33
+ return uploadWithXhr(url, form, property, options.onProgress);
34
+ }
35
+ options.onProgress?.(property, 0);
36
+ const response = await fetcher(url, { method: "POST", body: form });
37
+ const body = await response.json();
38
+ if (!response.ok) {
39
+ throw new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed.");
40
+ }
41
+ options.onProgress?.(property, 100);
42
+ return body.result;
43
+ };
44
+ }
45
+ });
46
+ }
47
+ function routeUrl(baseUrl, route) {
48
+ const url = new URL(baseUrl, globalThis.location?.href ?? "http://localhost");
49
+ url.searchParams.set("route", route);
50
+ const value = url.toString();
51
+ return baseUrl.startsWith("/") ? `${url.pathname}${url.search}` : value;
52
+ }
53
+ function uploadWithXhr(url, form, route, onProgress) {
54
+ return new Promise((resolve, reject) => {
55
+ const xhr = new XMLHttpRequest();
56
+ xhr.open("POST", url);
57
+ xhr.upload.onprogress = (event) => {
58
+ if (!event.lengthComputable) return;
59
+ onProgress?.(route, Math.round(event.loaded / event.total * 100));
60
+ };
61
+ xhr.onload = () => {
62
+ const body = JSON.parse(xhr.responseText || "{}");
63
+ if (xhr.status < 200 || xhr.status >= 300) {
64
+ reject(new UploadError(body.error?.code ?? "UNKNOWN", body.error?.message ?? "Upload failed."));
65
+ return;
66
+ }
67
+ onProgress?.(route, 100);
68
+ resolve(body.result);
69
+ };
70
+ xhr.onerror = () => reject(new UploadError("UPLOAD_FAILED", "Upload request failed."));
71
+ onProgress?.(route, 0);
72
+ xhr.send(form);
73
+ });
74
+ }
75
+
76
+ // src/react.ts
77
+ function useUploads(baseUrl) {
78
+ const [states, setStates] = useState({});
79
+ return useMemo(() => {
80
+ const client = createUploadClient(baseUrl, {
81
+ onProgress(route, progress) {
82
+ setStates((current) => ({
83
+ ...current,
84
+ [route]: { ...current[route] ?? emptyState(), progress }
85
+ }));
86
+ }
87
+ });
88
+ return new Proxy({}, {
89
+ get(_target, property) {
90
+ if (typeof property !== "string") return void 0;
91
+ const state = states[property] ?? emptyState();
92
+ const method = async (input) => {
93
+ setStates((current) => ({
94
+ ...current,
95
+ [property]: { ...current[property] ?? emptyState(), isUploading: true, error: null }
96
+ }));
97
+ try {
98
+ const upload = client[property];
99
+ if (!upload) throw new Error(`Unknown upload route: ${property}`);
100
+ const data = await upload(input);
101
+ setStates((current) => ({
102
+ ...current,
103
+ [property]: { progress: 100, isUploading: false, error: null, data }
104
+ }));
105
+ return data;
106
+ } catch (error) {
107
+ setStates((current) => ({
108
+ ...current,
109
+ [property]: { ...current[property] ?? emptyState(), isUploading: false, error }
110
+ }));
111
+ throw error;
112
+ }
113
+ };
114
+ return Object.assign(method, state);
115
+ }
116
+ });
117
+ }, [baseUrl, states]);
118
+ }
119
+ function emptyState() {
120
+ return {
121
+ progress: 0,
122
+ isUploading: false,
123
+ error: null,
124
+ data: null
125
+ };
126
+ }
127
+ export {
128
+ useUploads
129
+ };
130
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts","../src/types.ts","../src/client.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\r\nimport { createUploadClient } from \"./client\";\r\nimport type { ClientInput, ClientOutput, UpliftApp, UploadError } from \"./types\";\r\n\r\ntype RouteState<TData> = {\r\n progress: number;\r\n isUploading: boolean;\r\n error: UploadError | null;\r\n data: TData | null;\r\n};\r\n\r\ntype ReactUploadMethod<TApp extends UpliftApp, TRouteName extends keyof TApp[\"routes\"] & string> =\r\n ((input: ClientInput<TApp[\"routes\"][TRouteName]>) => Promise<ClientOutput<TApp[\"routes\"][TRouteName]>>) &\r\n RouteState<ClientOutput<TApp[\"routes\"][TRouteName]>>;\r\n\r\nexport type ReactUploadClient<TApp extends UpliftApp> = {\r\n [TRouteName in keyof TApp[\"routes\"] & string]: ReactUploadMethod<TApp, TRouteName>;\r\n};\r\n\r\nexport function useUploads<TApp extends UpliftApp>(baseUrl: string): ReactUploadClient<TApp> {\r\n const [states, setStates] = useState<Record<string, RouteState<unknown>>>({});\r\n\r\n return useMemo(() => {\r\n const client = createUploadClient<TApp>(baseUrl, {\r\n onProgress(route, progress) {\r\n setStates((current) => ({\r\n ...current,\r\n [route]: { ...(current[route] ?? emptyState()), progress }\r\n }));\r\n }\r\n });\r\n\r\n return new Proxy({}, {\r\n get(_target, property) {\r\n if (typeof property !== \"string\") return undefined;\r\n const state = states[property] ?? emptyState();\r\n const method = async (input: never) => {\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { ...(current[property] ?? emptyState()), isUploading: true, error: null }\r\n }));\r\n try {\r\n const upload = (client as Record<string, (value: never) => Promise<unknown>>)[property];\r\n if (!upload) throw new Error(`Unknown upload route: ${property}`);\r\n const data = await upload(input);\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { progress: 100, isUploading: false, error: null, data }\r\n }));\r\n return data;\r\n } catch (error) {\r\n setStates((current) => ({\r\n ...current,\r\n [property]: { ...(current[property] ?? emptyState()), isUploading: false, error: error as UploadError }\r\n }));\r\n throw error;\r\n }\r\n };\r\n\r\n return Object.assign(method, state);\r\n }\r\n }) as ReactUploadClient<TApp>;\r\n }, [baseUrl, states]);\r\n}\r\n\r\nfunction emptyState(): RouteState<unknown> {\r\n return {\r\n progress: 0,\r\n isUploading: false,\r\n error: null,\r\n data: null\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","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":"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","names":[]}
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ handleUploadRequest: () => handleUploadRequest
24
+ });
25
+ module.exports = __toCommonJS(server_exports);
26
+
27
+ // src/types.ts
28
+ var UploadError = class extends Error {
29
+ code;
30
+ constructor(code, message) {
31
+ super(message);
32
+ this.name = "UploadError";
33
+ this.code = code;
34
+ }
35
+ toJSON() {
36
+ return {
37
+ message: this.message,
38
+ code: this.code
39
+ };
40
+ }
41
+ };
42
+
43
+ // src/utils.ts
44
+ var sizeUnits = {
45
+ b: 1,
46
+ kb: 1024,
47
+ mb: 1024 * 1024,
48
+ gb: 1024 * 1024 * 1024
49
+ };
50
+ function extensionFor(name) {
51
+ const index = name.lastIndexOf(".");
52
+ if (index < 0 || index === name.length - 1) return void 0;
53
+ return name.slice(index + 1).toLowerCase();
54
+ }
55
+ function toInputFile(file) {
56
+ const extension = extensionFor(file.name);
57
+ const input = {
58
+ name: file.name,
59
+ type: file.type,
60
+ size: file.size,
61
+ file
62
+ };
63
+ if (extension) input.extension = extension;
64
+ return input;
65
+ }
66
+ function defaultKey(file) {
67
+ const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
68
+ return `${random}/${file.name}`;
69
+ }
70
+ async function readJsonFile(file) {
71
+ try {
72
+ return JSON.parse(await file.text());
73
+ } catch {
74
+ throw new UploadError("VALIDATION_FAILED", "Invalid JSON file.");
75
+ }
76
+ }
77
+
78
+ // src/server.ts
79
+ async function handleUploadRequest(app, req) {
80
+ try {
81
+ if (req.method !== "POST") {
82
+ return json({ error: new UploadError("VALIDATION_FAILED", "Only POST upload requests are supported.") }, 405);
83
+ }
84
+ const routeName = routeNameFromRequest(req);
85
+ const route = app.routes[routeName];
86
+ if (!route) throw new UploadError("VALIDATION_FAILED", `Unknown upload route: ${routeName}`);
87
+ const user = await resolveUser(app, route._def, req);
88
+ const files = await filesFromRequest(req);
89
+ if (files.length === 0) throw new UploadError("VALIDATION_FAILED", "No files were uploaded.");
90
+ if (!route._def.multiple && files.length !== 1) {
91
+ throw new UploadError("VALIDATION_FAILED", "This route accepts exactly one file.");
92
+ }
93
+ if (route._def.multipleLimit !== void 0 && files.length > route._def.multipleLimit) {
94
+ throw new UploadError("VALIDATION_FAILED", `This route accepts at most ${route._def.multipleLimit} files.`);
95
+ }
96
+ const stored = [];
97
+ for (const item of files) {
98
+ const meta = await deriveMeta(route._def, req, item.input, user);
99
+ await validateFile(route._def, req, item, user, meta);
100
+ const key = route._def.key ? await route._def.key({ req, file: item.input, user, meta }) : defaultKey(item.input);
101
+ assertSafeStorageKey(key);
102
+ stored.push({
103
+ file: await app.storage.put({ key, file: item.input, body: item.body }),
104
+ meta
105
+ });
106
+ }
107
+ const uploaded = stored.map((item) => item.file);
108
+ const metas = stored.map((item) => item.meta);
109
+ const firstUpload = uploaded[0];
110
+ if (!firstUpload) throw new UploadError("UPLOAD_FAILED", "Upload did not produce a result.");
111
+ const result = route._def.multiple ? uploaded : firstUpload;
112
+ if (route._def.done) {
113
+ if (route._def.multiple) {
114
+ await route._def.done({ req, files: uploaded, user, meta: metas });
115
+ } else {
116
+ await route._def.done({ req, file: firstUpload, user, meta: metas[0] });
117
+ }
118
+ }
119
+ if (app.onUploadComplete) await app.onUploadComplete({ route: routeName, result, user });
120
+ return json({ result }, 200);
121
+ } catch (error) {
122
+ const uploadError = normalizeError(error);
123
+ return json({ error: uploadError }, statusFor(uploadError));
124
+ }
125
+ }
126
+ function assertSafeStorageKey(key) {
127
+ if (key.length === 0) {
128
+ throw new UploadError("VALIDATION_FAILED", "Storage key cannot be empty.");
129
+ }
130
+ if (key.includes("\0") || key.includes("\\") || key.includes("://") || key.split("/").includes("..") || /^([a-zA-Z]:)?\//.test(key)) {
131
+ throw new UploadError("VALIDATION_FAILED", "Storage key must be a relative object key.");
132
+ }
133
+ }
134
+ function routeNameFromRequest(req) {
135
+ const url = new URL(req.url);
136
+ const explicit = url.searchParams.get("route");
137
+ if (explicit) return explicit;
138
+ const parts = url.pathname.split("/").filter(Boolean);
139
+ return parts[parts.length - 1] ?? "";
140
+ }
141
+ async function resolveUser(app, route, req) {
142
+ if (route.overrideAuth) return void 0;
143
+ const middleware = route.auth ?? app.middleware;
144
+ if (!middleware) return void 0;
145
+ try {
146
+ return await middleware({ req });
147
+ } catch (error) {
148
+ const message = error instanceof Error ? error.message : "Authentication failed.";
149
+ throw new UploadError("AUTH_FAILED", message);
150
+ }
151
+ }
152
+ async function filesFromRequest(req) {
153
+ const contentType = req.headers.get("content-type") ?? "";
154
+ if (!contentType.includes("multipart/form-data")) {
155
+ throw new UploadError("VALIDATION_FAILED", "Upload requests must be multipart/form-data.");
156
+ }
157
+ try {
158
+ const form = await req.formData();
159
+ const files = [];
160
+ for (const value of form.values()) {
161
+ if (!(value instanceof File)) continue;
162
+ files.push({ input: toInputFile(value), body: value });
163
+ }
164
+ return files;
165
+ } catch (error) {
166
+ if (error instanceof UploadError) throw error;
167
+ const message = error instanceof Error ? error.message : "Upload request body could not be parsed.";
168
+ throw new UploadError("VALIDATION_FAILED", message);
169
+ }
170
+ }
171
+ async function deriveMeta(route, req, file, user) {
172
+ if (!route.meta) return void 0;
173
+ return route.meta({ req, file, user });
174
+ }
175
+ async function validateFile(route, req, item, user, meta) {
176
+ if (route.maxBytes !== void 0 && item.input.size > route.maxBytes) {
177
+ throw new UploadError("FILE_TOO_LARGE", "File is larger than the route allows.");
178
+ }
179
+ if (route.minBytes !== void 0 && item.input.size < route.minBytes) {
180
+ throw new UploadError("FILE_TOO_SMALL", "File is smaller than the route allows.");
181
+ }
182
+ if (route.kind !== "any" && route.extensions && item.input.extension && !route.extensions.includes(item.input.extension)) {
183
+ throw new UploadError("INVALID_TYPE", "File type is not allowed.");
184
+ }
185
+ if (route.kind !== "any" && route.mimeTypes && route.mimeTypes.length > 0) {
186
+ const matches = route.mimeTypes.some((mime) => mime.endsWith("/") ? item.input.type.startsWith(mime) : item.input.type === mime);
187
+ if (!matches) throw new UploadError("INVALID_TYPE", "File MIME type is not allowed.");
188
+ }
189
+ if (route.schema) {
190
+ try {
191
+ route.schema.parse(await readJsonFile(item.body));
192
+ } catch (error) {
193
+ if (error instanceof UploadError) throw error;
194
+ const message = error instanceof Error ? error.message : "Schema validation failed.";
195
+ throw new UploadError("VALIDATION_FAILED", message);
196
+ }
197
+ }
198
+ await validateConfiguredInspection(route, item.body);
199
+ if (route.validate) {
200
+ const result = await route.validate({ req, file: item.input, user, meta });
201
+ if (result !== true) throw new UploadError("VALIDATION_FAILED", result);
202
+ }
203
+ }
204
+ async function validateConfiguredInspection(route, file) {
205
+ if (route.dimensionRule || route.requireSquare || route.aspectRatio) {
206
+ throw new UploadError("VALIDATION_FAILED", "Image dimension validation requires an image inspector and is not enabled in core.");
207
+ }
208
+ if (route.encoding) {
209
+ const text = await file.text();
210
+ if (route.encoding === "ascii" && /[^\x00-\x7F]/.test(text)) {
211
+ throw new UploadError("VALIDATION_FAILED", "Text file is not ASCII encoded.");
212
+ }
213
+ }
214
+ if (route.headers) {
215
+ const [headerLine = ""] = (await file.text()).split(/\r?\n/, 1);
216
+ const delimiter = route.delimiter ?? ",";
217
+ const actualHeaders = headerLine.split(delimiter).map((header) => header.trim());
218
+ if (route.headers.some((header, index) => actualHeaders[index] !== header)) {
219
+ throw new UploadError("VALIDATION_FAILED", "CSV headers do not match the route definition.");
220
+ }
221
+ }
222
+ if (route.pageRule || route.encrypted !== void 0 || route.durationRule) {
223
+ throw new UploadError("VALIDATION_FAILED", "Rich inspection is configured but no runtime inspector has been installed for this route.");
224
+ }
225
+ }
226
+ function normalizeError(error) {
227
+ if (error instanceof UploadError) return error;
228
+ if (error instanceof Error) return new UploadError("UNKNOWN", error.message);
229
+ return new UploadError("UNKNOWN", "Unknown upload failure.");
230
+ }
231
+ function statusFor(error) {
232
+ if (error.code === "AUTH_FAILED") return 401;
233
+ if (error.code === "UPLOAD_FAILED" || error.code === "UNKNOWN") return 500;
234
+ return 400;
235
+ }
236
+ function json(body, status) {
237
+ return Response.json(body, { status });
238
+ }
239
+ // Annotate the CommonJS export names for ESM import in node:
240
+ 0 && (module.exports = {
241
+ handleUploadRequest
242
+ });
243
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/types.ts","../src/utils.ts"],"sourcesContent":["import { UploadError, type UpliftApp, type UploadedFile, type UploadInputFile } from \"./types\";\r\nimport { defaultKey, readJsonFile, toInputFile } from \"./utils\";\r\n\r\ntype FileWithInput = {\r\n input: UploadInputFile;\r\n body: File;\r\n};\r\n\r\ntype StoredFile = {\r\n file: UploadedFile;\r\n meta: unknown;\r\n};\r\n\r\nexport async function handleUploadRequest(app: UpliftApp, req: Request): Promise<Response> {\r\n try {\r\n if (req.method !== \"POST\") {\r\n return json({ error: new UploadError(\"VALIDATION_FAILED\", \"Only POST upload requests are supported.\") }, 405);\r\n }\r\n\r\n const routeName = routeNameFromRequest(req);\r\n const route = app.routes[routeName];\r\n if (!route) throw new UploadError(\"VALIDATION_FAILED\", `Unknown upload route: ${routeName}`);\r\n\r\n const user = await resolveUser(app, route._def, req);\r\n const files = await filesFromRequest(req);\r\n if (files.length === 0) throw new UploadError(\"VALIDATION_FAILED\", \"No files were uploaded.\");\r\n if (!route._def.multiple && files.length !== 1) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"This route accepts exactly one file.\");\r\n }\r\n if (route._def.multipleLimit !== undefined && files.length > route._def.multipleLimit) {\r\n throw new UploadError(\"VALIDATION_FAILED\", `This route accepts at most ${route._def.multipleLimit} files.`);\r\n }\r\n\r\n const stored: StoredFile[] = [];\r\n for (const item of files) {\r\n const meta = await deriveMeta(route._def, req, item.input, user);\r\n await validateFile(route._def, req, item, user, meta);\r\n const key = route._def.key ? await route._def.key({ req, file: item.input, user, meta }) : defaultKey(item.input);\r\n assertSafeStorageKey(key);\r\n stored.push({\r\n file: await app.storage.put({ key, file: item.input, body: item.body }),\r\n meta\r\n });\r\n }\r\n\r\n const uploaded = stored.map((item) => item.file);\r\n const metas = stored.map((item) => item.meta);\r\n const firstUpload = uploaded[0];\r\n if (!firstUpload) throw new UploadError(\"UPLOAD_FAILED\", \"Upload did not produce a result.\");\r\n const result: UploadedFile | UploadedFile[] = route._def.multiple ? uploaded : firstUpload;\r\n\r\n if (route._def.done) {\r\n if (route._def.multiple) {\r\n await route._def.done({ req, files: uploaded, user, meta: metas });\r\n } else {\r\n await route._def.done({ req, file: firstUpload, user, meta: metas[0] });\r\n }\r\n }\r\n\r\n if (app.onUploadComplete) await app.onUploadComplete({ route: routeName, result, user });\r\n return json({ result }, 200);\r\n } catch (error) {\r\n const uploadError = normalizeError(error);\r\n return json({ error: uploadError }, statusFor(uploadError));\r\n }\r\n}\r\n\r\nfunction assertSafeStorageKey(key: string): void {\r\n if (key.length === 0) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Storage key cannot be empty.\");\r\n }\r\n if (\r\n key.includes(\"\\0\") ||\r\n key.includes(\"\\\\\") ||\r\n key.includes(\"://\") ||\r\n key.split(\"/\").includes(\"..\") ||\r\n /^([a-zA-Z]:)?\\//.test(key)\r\n ) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Storage key must be a relative object key.\");\r\n }\r\n}\r\n\r\nfunction routeNameFromRequest(req: Request): string {\r\n const url = new URL(req.url);\r\n const explicit = url.searchParams.get(\"route\");\r\n if (explicit) return explicit;\r\n const parts = url.pathname.split(\"/\").filter(Boolean);\r\n return parts[parts.length - 1] ?? \"\";\r\n}\r\n\r\nasync function resolveUser(app: UpliftApp, route: UpliftApp[\"routes\"][string][\"_def\"], req: Request): Promise<unknown> {\r\n if (route.overrideAuth) return undefined;\r\n const middleware = route.auth ?? app.middleware;\r\n if (!middleware) return undefined;\r\n try {\r\n return await middleware({ req });\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : \"Authentication failed.\";\r\n throw new UploadError(\"AUTH_FAILED\", message);\r\n }\r\n}\r\n\r\nasync function filesFromRequest(req: Request): Promise<FileWithInput[]> {\r\n const contentType = req.headers.get(\"content-type\") ?? \"\";\r\n if (!contentType.includes(\"multipart/form-data\")) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Upload requests must be multipart/form-data.\");\r\n }\r\n\r\n try {\r\n const form = await req.formData();\r\n const files: FileWithInput[] = [];\r\n for (const value of form.values()) {\r\n if (!(value instanceof File)) continue;\r\n files.push({ input: toInputFile(value), body: value });\r\n }\r\n return files;\r\n } catch (error) {\r\n if (error instanceof UploadError) throw error;\r\n const message = error instanceof Error ? error.message : \"Upload request body could not be parsed.\";\r\n throw new UploadError(\"VALIDATION_FAILED\", message);\r\n }\r\n}\r\n\r\nasync function deriveMeta(\r\n route: UpliftApp[\"routes\"][string][\"_def\"],\r\n req: Request,\r\n file: UploadInputFile,\r\n user: unknown\r\n): Promise<unknown> {\r\n if (!route.meta) return undefined;\r\n return route.meta({ req, file, user });\r\n}\r\n\r\nasync function validateFile(\r\n route: UpliftApp[\"routes\"][string][\"_def\"],\r\n req: Request,\r\n item: FileWithInput,\r\n user: unknown,\r\n meta: unknown\r\n) {\r\n if (route.maxBytes !== undefined && item.input.size > route.maxBytes) {\r\n throw new UploadError(\"FILE_TOO_LARGE\", \"File is larger than the route allows.\");\r\n }\r\n if (route.minBytes !== undefined && item.input.size < route.minBytes) {\r\n throw new UploadError(\"FILE_TOO_SMALL\", \"File is smaller than the route allows.\");\r\n }\r\n if (route.kind !== \"any\" && route.extensions && item.input.extension && !route.extensions.includes(item.input.extension)) {\r\n throw new UploadError(\"INVALID_TYPE\", \"File type is not allowed.\");\r\n }\r\n if (route.kind !== \"any\" && route.mimeTypes && route.mimeTypes.length > 0) {\r\n const matches = route.mimeTypes.some((mime) => mime.endsWith(\"/\") ? item.input.type.startsWith(mime) : item.input.type === mime);\r\n if (!matches) throw new UploadError(\"INVALID_TYPE\", \"File MIME type is not allowed.\");\r\n }\r\n if (route.schema) {\r\n try {\r\n route.schema.parse(await readJsonFile(item.body));\r\n } catch (error) {\r\n if (error instanceof UploadError) throw error;\r\n const message = error instanceof Error ? error.message : \"Schema validation failed.\";\r\n throw new UploadError(\"VALIDATION_FAILED\", message);\r\n }\r\n }\r\n await validateConfiguredInspection(route, item.body);\r\n if (route.validate) {\r\n const result = await route.validate({ req, file: item.input, user, meta });\r\n if (result !== true) throw new UploadError(\"VALIDATION_FAILED\", result);\r\n }\r\n}\r\n\r\nasync function validateConfiguredInspection(route: UpliftApp[\"routes\"][string][\"_def\"], file: File): Promise<void> {\r\n if (route.dimensionRule || route.requireSquare || route.aspectRatio) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Image dimension validation requires an image inspector and is not enabled in core.\");\r\n }\r\n if (route.encoding) {\r\n const text = await file.text();\r\n if (route.encoding === \"ascii\" && /[^\\x00-\\x7F]/.test(text)) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Text file is not ASCII encoded.\");\r\n }\r\n }\r\n if (route.headers) {\r\n const [headerLine = \"\"] = (await file.text()).split(/\\r?\\n/, 1);\r\n const delimiter = route.delimiter ?? \",\";\r\n const actualHeaders = headerLine.split(delimiter).map((header) => header.trim());\r\n if (route.headers.some((header, index) => actualHeaders[index] !== header)) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"CSV headers do not match the route definition.\");\r\n }\r\n }\r\n if (route.pageRule || route.encrypted !== undefined || route.durationRule) {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Rich inspection is configured but no runtime inspector has been installed for this route.\");\r\n }\r\n}\r\n\r\nfunction normalizeError(error: unknown): UploadError {\r\n if (error instanceof UploadError) return error;\r\n if (error instanceof Error) return new UploadError(\"UNKNOWN\", error.message);\r\n return new UploadError(\"UNKNOWN\", \"Unknown upload failure.\");\r\n}\r\n\r\nfunction statusFor(error: UploadError): number {\r\n if (error.code === \"AUTH_FAILED\") return 401;\r\n if (error.code === \"UPLOAD_FAILED\" || error.code === \"UNKNOWN\") return 500;\r\n return 400;\r\n}\r\n\r\nfunction json(body: unknown, status: number): Response {\r\n return Response.json(body, { status });\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","import { UploadError, type SizeValue, type UploadInputFile } from \"./types\";\r\n\r\nconst sizeUnits = {\r\n b: 1,\r\n kb: 1024,\r\n mb: 1024 * 1024,\r\n gb: 1024 * 1024 * 1024\r\n} as const;\r\n\r\nexport function parseSize(value: SizeValue): number {\r\n const match = /^(\\d+(?:\\.\\d+)?)(b|kb|mb|gb)$/.exec(value);\r\n if (!match) throw new UploadError(\"VALIDATION_FAILED\", `Invalid size value: ${value}`);\r\n const amount = Number(match[1]);\r\n const unit = match[2] as keyof typeof sizeUnits;\r\n return Math.floor(amount * sizeUnits[unit]);\r\n}\r\n\r\nexport function extensionFor(name: string): string | undefined {\r\n const index = name.lastIndexOf(\".\");\r\n if (index < 0 || index === name.length - 1) return undefined;\r\n return name.slice(index + 1).toLowerCase();\r\n}\r\n\r\nexport function toInputFile(file: File): UploadInputFile {\r\n const extension = extensionFor(file.name);\r\n const input: UploadInputFile = {\r\n name: file.name,\r\n type: file.type,\r\n size: file.size,\r\n file\r\n };\r\n if (extension) input.extension = extension;\r\n return input;\r\n}\r\n\r\nexport function defaultKey(file: UploadInputFile): string {\r\n const random = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;\r\n return `${random}/${file.name}`;\r\n}\r\n\r\nexport async function readJsonFile(file: File): Promise<unknown> {\r\n try {\r\n return JSON.parse(await file.text());\r\n } catch {\r\n throw new UploadError(\"VALIDATION_FAILED\", \"Invalid JSON file.\");\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ import { g as UpliftApp } from './types-BdcszAj8.cjs';
2
+
3
+ declare function handleUploadRequest(app: UpliftApp, req: Request): Promise<Response>;
4
+
5
+ export { handleUploadRequest };
@@ -0,0 +1,5 @@
1
+ import { g as UpliftApp } from './types-BdcszAj8.js';
2
+
3
+ declare function handleUploadRequest(app: UpliftApp, req: Request): Promise<Response>;
4
+
5
+ export { handleUploadRequest };