jax-hono 1.0.26 → 1.0.28
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/package.json +2 -1
- package/utils/error.ts +5 -0
- package/utils/index.ts +8 -6
- package/utils/upload.ts +194 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jax-hono",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"description": "Lightweight framework layer on top of Hono, built for Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"axios": "^1.18.1",
|
|
102
102
|
"@types/bun": "^1.3.14",
|
|
103
103
|
"@types/jsonwebtoken": "^9.0.10",
|
|
104
|
+
"@types/minimist": "^1.2.5",
|
|
104
105
|
"dayjs": "^1.11.21",
|
|
105
106
|
"deepmerge": "^4.3.1",
|
|
106
107
|
"dotenv": "^17.4.2",
|
package/utils/error.ts
ADDED
package/utils/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from "./array";
|
|
2
2
|
export * from "./async";
|
|
3
|
-
export * from "./crypto";
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
3
|
+
export * from "./crypto";
|
|
4
|
+
export * from "./error";
|
|
5
|
+
export * from "./http";
|
|
6
|
+
export * from "./order";
|
|
7
|
+
export * from "./regexp";
|
|
8
|
+
export * from "./transform";
|
|
9
|
+
export * from "./upload";
|
|
10
|
+
export * from "./validate";
|
package/utils/upload.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export type SaveUploadFileOptions = {
|
|
5
|
+
dir?: string;
|
|
6
|
+
filename?: string;
|
|
7
|
+
uploadDir?: string;
|
|
8
|
+
urlPrefix?: string;
|
|
9
|
+
serverUrl?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SaveUploadFileResult = {
|
|
13
|
+
url: string;
|
|
14
|
+
originalName: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type SaveBase64ImageOptions = {
|
|
19
|
+
dir?: string;
|
|
20
|
+
filename?: string;
|
|
21
|
+
uploadDir?: string;
|
|
22
|
+
urlPrefix?: string;
|
|
23
|
+
serverUrl?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ParsedBase64Image = {
|
|
27
|
+
ext: string;
|
|
28
|
+
base64: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type UploadConfig = {
|
|
32
|
+
uploadDir?: string;
|
|
33
|
+
serverUrl?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let runtimeConfig: UploadConfig | undefined;
|
|
37
|
+
|
|
38
|
+
export async function saveUploadFile(
|
|
39
|
+
file: File,
|
|
40
|
+
options: SaveUploadFileOptions = {},
|
|
41
|
+
): Promise<SaveUploadFileResult> {
|
|
42
|
+
const ext = extname(file.name) || extByMimeType(file.type);
|
|
43
|
+
const filename = options.filename || getUploadFilename(ext);
|
|
44
|
+
const dir = options.dir || "open";
|
|
45
|
+
const datePath = todayPath();
|
|
46
|
+
const saveDir = join(
|
|
47
|
+
getUploadDir(options),
|
|
48
|
+
...getRelativeParts(dir, datePath),
|
|
49
|
+
);
|
|
50
|
+
const filePath = join(saveDir, filename);
|
|
51
|
+
|
|
52
|
+
await mkdir(saveDir, { recursive: true });
|
|
53
|
+
await Bun.write(filePath, file);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
url: getUploadUrl({ ...options, dir, datePath, filename }),
|
|
57
|
+
originalName: file.name,
|
|
58
|
+
mimeType: file.type,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function saveBase64Image(
|
|
63
|
+
value: string,
|
|
64
|
+
dirOrOptions: string | SaveBase64ImageOptions = "",
|
|
65
|
+
): Promise<string> {
|
|
66
|
+
const options =
|
|
67
|
+
typeof dirOrOptions === "string" ? { dir: dirOrOptions } : dirOrOptions;
|
|
68
|
+
const image = parseBase64Image(value);
|
|
69
|
+
const filename = options.filename || getUploadFilename(image.ext);
|
|
70
|
+
const datePath = todayPath();
|
|
71
|
+
const saveDir = join(
|
|
72
|
+
getUploadDir(options),
|
|
73
|
+
...getRelativeParts(options.dir, datePath),
|
|
74
|
+
);
|
|
75
|
+
const filePath = join(saveDir, filename);
|
|
76
|
+
|
|
77
|
+
await mkdir(saveDir, { recursive: true });
|
|
78
|
+
await writeFile(filePath, Buffer.from(image.base64, "base64"));
|
|
79
|
+
|
|
80
|
+
return getUploadUrl({ ...options, datePath, filename });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isBase64Image(value: string) {
|
|
84
|
+
return /^data:image\/[a-zA-Z0-9+.-]+;base64,/.test(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isUploadedUrl(value: string, urlPrefix = "/uploads") {
|
|
88
|
+
return /^(https?:)?\/\//.test(value) || value.startsWith(`${urlPrefix}/`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getUploadFilename(ext: string) {
|
|
92
|
+
const random = Math.random().toString().slice(2, 7).padEnd(5, "0");
|
|
93
|
+
const now = new Date();
|
|
94
|
+
const time = [
|
|
95
|
+
now.getHours().toString().padStart(2, "0"),
|
|
96
|
+
now.getMinutes().toString().padStart(2, "0"),
|
|
97
|
+
now.getSeconds().toString().padStart(2, "0"),
|
|
98
|
+
].join("");
|
|
99
|
+
|
|
100
|
+
return `${time}${random}${ext}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseBase64Image(value: string): ParsedBase64Image {
|
|
104
|
+
const match = value.match(/^data:image\/([a-zA-Z0-9+.-]+);base64,(.*)$/);
|
|
105
|
+
|
|
106
|
+
if (!match) {
|
|
107
|
+
throw new Error("Invalid image format");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
ext: extByMimeType(`image/${match[1]}`),
|
|
112
|
+
base64: match[2],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getUploadDir(options: SaveUploadFileOptions | SaveBase64ImageOptions) {
|
|
117
|
+
return String(
|
|
118
|
+
options.uploadDir ||
|
|
119
|
+
getRuntimeConfig().uploadDir ||
|
|
120
|
+
join(process.cwd(), "storage/public"),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getUploadUrl(
|
|
125
|
+
options: (SaveUploadFileOptions | SaveBase64ImageOptions) & {
|
|
126
|
+
datePath: string;
|
|
127
|
+
filename: string;
|
|
128
|
+
},
|
|
129
|
+
) {
|
|
130
|
+
const serverUrl = options.serverUrl ?? getRuntimeConfig().serverUrl ?? "";
|
|
131
|
+
const urlPrefix = options.urlPrefix || "/uploads";
|
|
132
|
+
const path = joinUrlPath(
|
|
133
|
+
urlPrefix,
|
|
134
|
+
options.dir,
|
|
135
|
+
options.datePath,
|
|
136
|
+
options.filename,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return `${serverUrl}${path}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getRuntimeConfig() {
|
|
143
|
+
runtimeConfig ??= (require("../config") as { default: UploadConfig }).default;
|
|
144
|
+
|
|
145
|
+
return runtimeConfig;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function todayPath() {
|
|
149
|
+
const now = new Date();
|
|
150
|
+
const year = now.getFullYear().toString().slice(-2);
|
|
151
|
+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
152
|
+
const day = now.getDate().toString().padStart(2, "0");
|
|
153
|
+
|
|
154
|
+
return `${year}${month}${day}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getRelativeParts(...values: Array<string | undefined>) {
|
|
158
|
+
return values
|
|
159
|
+
.flatMap((value) => (value || "").split(/[\\/]+/))
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.map((part) => {
|
|
162
|
+
if (part === "." || part === "..") {
|
|
163
|
+
throw new Error("Upload dir cannot contain relative path segments");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return part;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function joinUrlPath(...values: Array<string | undefined>) {
|
|
171
|
+
const [first = "", ...rest] = values;
|
|
172
|
+
const prefix = first.startsWith("/") ? "/" : "";
|
|
173
|
+
const parts = [
|
|
174
|
+
first.replace(/^\/+|\/+$/g, ""),
|
|
175
|
+
...rest.map((value) => (value || "").replace(/^\/+|\/+$/g, "")),
|
|
176
|
+
].filter(Boolean);
|
|
177
|
+
|
|
178
|
+
return `${prefix}${parts.join("/")}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function extByMimeType(mimeType: string) {
|
|
182
|
+
return (
|
|
183
|
+
(
|
|
184
|
+
{
|
|
185
|
+
"image/jpeg": ".jpg",
|
|
186
|
+
"image/jpg": ".jpg",
|
|
187
|
+
"image/png": ".png",
|
|
188
|
+
"image/gif": ".gif",
|
|
189
|
+
"image/webp": ".webp",
|
|
190
|
+
"image/svg+xml": ".svg",
|
|
191
|
+
} as Record<string, string>
|
|
192
|
+
)[mimeType] || ".jpg"
|
|
193
|
+
);
|
|
194
|
+
}
|