@edkstack/files 0.2.1 → 0.2.3
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/dist/client/index.d.mts +3 -3
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.mjs +1 -1
- package/dist/{server-B1B4vJ67.mjs → server-CVIdVIq6.mjs} +38 -13
- package/dist/server-CVIdVIq6.mjs.map +1 -0
- package/dist/{server-C_G1kBYN.d.mts → server-DT8zI0-g.d.mts} +15 -8
- package/package.json +1 -1
- package/dist/server-B1B4vJ67.mjs.map +0 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as createFilesServer } from "../server-
|
|
1
|
+
import { n as createFilesServer } from "../server-DT8zI0-g.mjs";
|
|
2
2
|
import "../server/index.mjs";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
@@ -31,7 +31,7 @@ declare function useFilesClient(): {
|
|
|
31
31
|
200: {
|
|
32
32
|
id: string;
|
|
33
33
|
name: string | null;
|
|
34
|
-
|
|
34
|
+
purpose: string;
|
|
35
35
|
size: number;
|
|
36
36
|
mimeType: string;
|
|
37
37
|
createdAt: Date;
|
|
@@ -62,7 +62,7 @@ declare function useFilesClient(): {
|
|
|
62
62
|
declare function useUpload(options: InferMutationOptions<FilesClient["api"]["files"]["upload"]["post"]>): _tanstack_react_query0.UseMutationResult<{
|
|
63
63
|
id: string;
|
|
64
64
|
name: string | null;
|
|
65
|
-
|
|
65
|
+
purpose: string;
|
|
66
66
|
size: number;
|
|
67
67
|
mimeType: string;
|
|
68
68
|
createdAt: Date;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as schema_d_exports, i as Visibility, n as createFilesServer, r as PurposePolicy, t as FilesServerOptions } from "./server-
|
|
1
|
+
import { a as schema_d_exports, i as Visibility, n as createFilesServer, r as PurposePolicy, t as FilesServerOptions } from "./server-DT8zI0-g.mjs";
|
|
2
2
|
import "./server/index.mjs";
|
|
3
3
|
export { FilesServerOptions, PurposePolicy, Visibility, createFilesServer, schema_d_exports as schema };
|
package/dist/index.mjs
CHANGED
package/dist/server/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as schema_d_exports, i as Visibility, n as createFilesServer, r as PurposePolicy, t as FilesServerOptions } from "../server-
|
|
1
|
+
import { a as schema_d_exports, i as Visibility, n as createFilesServer, r as PurposePolicy, t as FilesServerOptions } from "../server-DT8zI0-g.mjs";
|
|
2
2
|
export { FilesServerOptions, type PurposePolicy, type Visibility, createFilesServer, schema_d_exports as schema };
|
package/dist/server/index.mjs
CHANGED
|
@@ -37,10 +37,10 @@ function createRouter(options) {
|
|
|
37
37
|
visibility: policy.visibility ?? "private"
|
|
38
38
|
});
|
|
39
39
|
return status(200, {
|
|
40
|
-
url:
|
|
40
|
+
url: uploaded.url,
|
|
41
41
|
id: uploaded.id,
|
|
42
42
|
name: uploaded.name,
|
|
43
|
-
|
|
43
|
+
purpose: uploaded.purpose,
|
|
44
44
|
size: uploaded.size,
|
|
45
45
|
mimeType: uploaded.mimeType,
|
|
46
46
|
createdAt: uploaded.createdAt
|
|
@@ -65,7 +65,7 @@ const FileResponse = t.Object({
|
|
|
65
65
|
url: t.String(),
|
|
66
66
|
id: t.String(),
|
|
67
67
|
name: t.Nullable(t.String()),
|
|
68
|
-
|
|
68
|
+
purpose: t.String(),
|
|
69
69
|
size: t.Number(),
|
|
70
70
|
mimeType: t.String(),
|
|
71
71
|
createdAt: t.Date()
|
|
@@ -79,11 +79,18 @@ function createService(options) {
|
|
|
79
79
|
const s3Client = new S3Client(s3);
|
|
80
80
|
return {
|
|
81
81
|
async listFiles(params) {
|
|
82
|
-
return await db.select().from(files).where(inArray(files.id, params.ids)).limit(params.ids.length)
|
|
82
|
+
return (await db.select().from(files).where(inArray(files.id, params.ids)).limit(params.ids.length)).map((item) => ({
|
|
83
|
+
...item,
|
|
84
|
+
url: this.buildUrl(item)
|
|
85
|
+
}));
|
|
83
86
|
},
|
|
84
87
|
async getFile(params) {
|
|
85
88
|
const [found] = await db.select().from(files).where(eq(files.id, params.id)).limit(1);
|
|
86
|
-
|
|
89
|
+
if (!found) return null;
|
|
90
|
+
return {
|
|
91
|
+
...found,
|
|
92
|
+
url: this.buildUrl(found)
|
|
93
|
+
};
|
|
87
94
|
},
|
|
88
95
|
buildUrl(file) {
|
|
89
96
|
if (file.visibility === "public") return `${endpoint}/${file.key}`;
|
|
@@ -103,9 +110,17 @@ function createService(options) {
|
|
|
103
110
|
key: files.key,
|
|
104
111
|
visibility: files.visibility
|
|
105
112
|
}).from(files).where(inArray(files.id, params.ids)).limit(params.ids.length);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
return params.ids.map((id) => {
|
|
114
|
+
const row = rows.find((row) => row.id === id);
|
|
115
|
+
if (!row) return {
|
|
116
|
+
id,
|
|
117
|
+
url: null
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
id,
|
|
121
|
+
url: this.buildUrl(row)
|
|
122
|
+
};
|
|
123
|
+
});
|
|
109
124
|
},
|
|
110
125
|
async uploadFile(params) {
|
|
111
126
|
const id = nanoid();
|
|
@@ -116,10 +131,11 @@ function createService(options) {
|
|
|
116
131
|
`${id}${ext}`
|
|
117
132
|
].join("/");
|
|
118
133
|
const s3file = s3Client.file(key);
|
|
134
|
+
const filename = params.keepFileName === true ? params.file.name : params.keepFileName;
|
|
119
135
|
await s3file.write(params.file, {
|
|
120
136
|
type: params.file.type,
|
|
121
137
|
acl: params.visibility === "public" ? "public-read" : "private",
|
|
122
|
-
contentDisposition: `attachment; filename="${
|
|
138
|
+
contentDisposition: filename ? `attachment; filename="${filename}"` : void 0
|
|
123
139
|
});
|
|
124
140
|
try {
|
|
125
141
|
const [created] = await db.insert(files).values({
|
|
@@ -131,7 +147,10 @@ function createService(options) {
|
|
|
131
147
|
visibility: params.visibility
|
|
132
148
|
}).returning();
|
|
133
149
|
if (!created) throw new Error("Failed to create file record");
|
|
134
|
-
return
|
|
150
|
+
return {
|
|
151
|
+
...created,
|
|
152
|
+
url: this.buildUrl(created)
|
|
153
|
+
};
|
|
135
154
|
} catch (error) {
|
|
136
155
|
await s3file.delete().catch(() => {});
|
|
137
156
|
throw error;
|
|
@@ -150,10 +169,16 @@ function createService(options) {
|
|
|
150
169
|
async acquireFile(params) {
|
|
151
170
|
const [updated] = await db.update(files).set({ refCount: sql`${files.refCount} + 1` }).where(and(eq(files.id, params.id), params.purpose ? eq(files.purpose, params.purpose) : void 0)).returning();
|
|
152
171
|
if (!updated) throw new Error("File not found");
|
|
153
|
-
return
|
|
172
|
+
return {
|
|
173
|
+
...updated,
|
|
174
|
+
url: this.buildUrl(updated)
|
|
175
|
+
};
|
|
154
176
|
},
|
|
155
177
|
async acquireFiles(params) {
|
|
156
|
-
return await db.update(files).set({ refCount: sql`${files.refCount} + 1` }).where(and(inArray(files.id, params.ids), params.purpose ? eq(files.purpose, params.purpose) : void 0)).returning()
|
|
178
|
+
return (await db.update(files).set({ refCount: sql`${files.refCount} + 1` }).where(and(inArray(files.id, params.ids), params.purpose ? eq(files.purpose, params.purpose) : void 0)).returning()).map((item) => ({
|
|
179
|
+
...item,
|
|
180
|
+
url: this.buildUrl(item)
|
|
181
|
+
}));
|
|
157
182
|
},
|
|
158
183
|
async releaseFile(params) {
|
|
159
184
|
const [updated] = await db.update(files).set({ refCount: sql`${files.refCount} - 1` }).where(eq(files.id, params.id)).returning();
|
|
@@ -192,4 +217,4 @@ function createFilesServer(options) {
|
|
|
192
217
|
|
|
193
218
|
//#endregion
|
|
194
219
|
export { schema_exports as n, createFilesServer as t };
|
|
195
|
-
//# sourceMappingURL=server-
|
|
220
|
+
//# sourceMappingURL=server-CVIdVIq6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-CVIdVIq6.mjs","names":[],"sources":["../src/server/schema.ts","../src/server/router.ts","../src/server/service.ts","../src/server/server.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\r\nimport { index, integer, pgTable, text, timestamp, pgEnum } from \"drizzle-orm/pg-core\";\r\n\r\nexport const files = pgTable(\"files\", {\r\n id: text(\"id\").primaryKey().$defaultFn(() => `file_${nanoid()}`),\r\n purpose: text(\"purpose\").notNull(),\r\n name: text(\"name\"),\r\n key: text(\"key\").notNull().unique(),\r\n size: integer(\"size\").notNull(),\r\n mimeType: text(\"mime_type\").notNull().default(\"application/octet-stream\"),\r\n refCount: integer(\"ref_count\").notNull().default(0),\r\n visibility: pgEnum(\"visibility\", [\"private\", \"public\"])().notNull().default(\"private\"),\r\n createdAt: timestamp(\"created_at\").notNull().defaultNow(),\r\n updatedAt: timestamp(\"updated_at\").notNull().defaultNow(),\r\n}, (table) => [\r\n index(\"files_key_idx\").on(table.key),\r\n index(\"files_created_at_idx\").on(table.createdAt),\r\n]);","import { Elysia, t } from \"elysia\";\r\nimport type { Service } from \"./service\";\r\n\r\nexport type Visibility = \"private\" | \"public\";\r\n\r\nexport interface PurposePolicy {\r\n maxSize?: number;\r\n allowedMimeTypes?: string[];\r\n visibility?: Visibility;\r\n}\r\n\r\nexport function createRouter<const TPurpose extends string>(\r\n options: {\r\n service: Service;\r\n policies: Record<TPurpose, PurposePolicy>\r\n }\r\n) {\r\n \r\n const { \r\n service, \r\n policies,\r\n } = options;\r\n\r\n return new Elysia({\r\n prefix: \"/api\",\r\n }).post(\"/files/upload\", async ({ status, body }) => {\r\n const { file, purpose } = body;\r\n const policy = policies[purpose as TPurpose];\r\n if (!policy) {\r\n return status(400, {\r\n message: \"Purpose not supported\",\r\n });\r\n }\r\n if (policy.maxSize !== undefined && file.size > policy.maxSize) {\r\n return status(400, {\r\n message: \"File size exceeds the maximum allowed size\",\r\n });\r\n }\r\n if (policy.allowedMimeTypes !== undefined && !policy.allowedMimeTypes.includes(file.type)) {\r\n return status(400, {\r\n message: \"File type not allowed\",\r\n });\r\n }\r\n const uploaded = await service.uploadFile({ \r\n file, \r\n purpose, \r\n visibility: policy.visibility ?? \"private\" \r\n });\r\n return status(200, {\r\n url: uploaded.url,\r\n id: uploaded.id,\r\n name: uploaded.name,\r\n purpose: uploaded.purpose,\r\n size: uploaded.size,\r\n mimeType: uploaded.mimeType,\r\n createdAt: uploaded.createdAt,\r\n });\r\n }, {\r\n body: UploadRequest(Object.keys(policies) as TPurpose[]),\r\n response: {\r\n 200: FileResponse,\r\n 400: ErrorResponse,\r\n 500: ErrorResponse,\r\n }\r\n });\r\n}\r\n\r\nfunction UploadRequest<const TPurpose extends string>(\r\n purposes: TPurpose[]\r\n) {\r\n return t.Object({\r\n file: t.File(),\r\n purpose: t.Union(\r\n purposes.map((p) => t.Literal(p)) as [\r\n ReturnType<typeof t.Literal<TPurpose>>,\r\n ...ReturnType<typeof t.Literal<TPurpose>>[],\r\n ]\r\n )\r\n });\r\n}\r\n\r\nconst ErrorResponse = t.Object({\r\n message: t.String(),\r\n});\r\n\r\nconst FileResponse = t.Object({\r\n url: t.String(),\r\n id: t.String(),\r\n name: t.Nullable(t.String()),\r\n purpose: t.String(),\r\n size: t.Number(),\r\n mimeType: t.String(),\r\n createdAt: t.Date(),\r\n});","import { S3Client, type S3Options } from \"bun\";\r\nimport { nanoid } from \"nanoid\";\r\nimport { extname } from \"path\";\r\nimport { eq, sql, and, inArray } from \"drizzle-orm\";\r\nimport type { PgDatabase } from \"drizzle-orm/pg-core\";\r\nimport { files } from \"./schema\";\r\n\r\nexport type Service = ReturnType<typeof createService>;\r\n\r\ntype ById = { id: string };\r\ntype ByIds = { ids: string[] };\r\n\r\nexport type FileWithUrl = typeof files.$inferSelect & { url: string };\r\n\r\nexport function createService(options: {\r\n db: PgDatabase<any, any, any>;\r\n s3: S3Options;\r\n keyPrefix?: string;\r\n presignExpiresIn?: number;\r\n}) {\r\n const { \r\n db, \r\n s3,\r\n keyPrefix = \"files\",\r\n presignExpiresIn = 3600\r\n } = options;\r\n \r\n const { endpoint } = s3;\r\n const s3Client = new S3Client(s3);\r\n\r\n return {\r\n\r\n async listFiles(params: ByIds): Promise<FileWithUrl[]> {\r\n const items = await db\r\n .select()\r\n .from(files)\r\n .where(inArray(files.id, params.ids))\r\n .limit(params.ids.length);\r\n return items.map(item => ({\r\n ...item,\r\n url: this.buildUrl(item),\r\n }));\r\n },\r\n\r\n async getFile(params: ById): Promise<FileWithUrl | null> {\r\n const [found] = await db\r\n .select()\r\n .from(files)\r\n .where(eq(files.id, params.id))\r\n .limit(1);\r\n if (!found) return null;\r\n return {\r\n ...found,\r\n url: this.buildUrl(found),\r\n };\r\n },\r\n\r\n buildUrl(file: Pick<typeof files.$inferSelect, \"key\" | \"visibility\">): string {\r\n if (file.visibility === \"public\") {\r\n return `${endpoint}/${file.key}`;\r\n }\r\n return s3Client.presign(file.key, { \r\n expiresIn: presignExpiresIn,\r\n });\r\n },\r\n\r\n async getUrl(params: ById): Promise<string | null> {\r\n const [found] = await db\r\n .select({ \r\n key: files.key,\r\n visibility: files.visibility,\r\n })\r\n .from(files)\r\n .where(eq(files.id, params.id))\r\n .limit(1);\r\n if (!found) return null;\r\n return this.buildUrl(found);\r\n },\r\n\r\n async getUrls(params: ByIds): Promise<{ id: string; url: string | null }[]> {\r\n const rows = await db\r\n .select({\r\n id: files.id,\r\n key: files.key,\r\n visibility: files.visibility,\r\n })\r\n .from(files)\r\n .where(inArray(files.id, params.ids))\r\n .limit(params.ids.length);\r\n return params.ids.map(id => {\r\n const row = rows.find(row => row.id === id);\r\n if (!row) return { id, url: null };\r\n return { id, url: this.buildUrl(row) };\r\n });\r\n },\r\n\r\n async uploadFile(params: {\r\n file: File;\r\n purpose: string;\r\n visibility: \"private\" | \"public\";\r\n keepFileName?: boolean | string;\r\n }): Promise<FileWithUrl> {\r\n const id = nanoid();\r\n const ext = extname(params.file.name);\r\n const key = [keyPrefix, params.purpose, `${id}${ext}`].join(\"/\");\r\n const s3file = s3Client.file(key);\r\n const filename = params.keepFileName === true ? params.file.name : params.keepFileName;\r\n await s3file.write(params.file, {\r\n type: params.file.type,\r\n acl: params.visibility === \"public\" ? \"public-read\" : \"private\",\r\n contentDisposition: filename ? `attachment; filename=\"${filename}\"` : undefined,\r\n });\r\n try {\r\n const [created] = await db.insert(files)\r\n .values({\r\n purpose: params.purpose,\r\n key,\r\n size: s3file.size,\r\n name: s3file.name ?? null,\r\n mimeType: s3file.type,\r\n visibility: params.visibility,\r\n })\r\n .returning();\r\n if (!created) {\r\n throw new Error(\"Failed to create file record\");\r\n }\r\n return {\r\n ...created,\r\n url: this.buildUrl(created),\r\n };\r\n } catch (error) {\r\n await s3file.delete().catch(() => {});\r\n throw error;\r\n }\r\n },\r\n\r\n async deleteFile(params: { id: string }): Promise<void> {\r\n const [deleted] = await db\r\n .delete(files)\r\n .where(eq(files.id, params.id))\r\n .returning();\r\n if (!deleted) return;\r\n await s3Client.delete(deleted.key);\r\n },\r\n\r\n async deleteFiles(params: ByIds): Promise<void> {\r\n const deleted = await db\r\n .delete(files)\r\n .where(inArray(files.id, params.ids))\r\n .returning({\r\n key: files.key,\r\n });\r\n if (deleted.length === 0) return;\r\n await Promise.all(\r\n deleted.map((file) => s3Client.delete(file.key)),\r\n );\r\n },\r\n\r\n async acquireFile(params: ById & { \r\n purpose?: string \r\n }): Promise<FileWithUrl> {\r\n const [updated] = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} + 1` })\r\n .where(\r\n and(\r\n eq(files.id, params.id),\r\n params.purpose ? eq(files.purpose, params.purpose) : undefined,\r\n )\r\n )\r\n .returning();\r\n if (!updated) {\r\n throw new Error(\"File not found\");\r\n }\r\n return {\r\n ...updated,\r\n url: this.buildUrl(updated),\r\n };\r\n },\r\n\r\n async acquireFiles(\r\n params: ByIds & { purpose?: string },\r\n ): Promise<FileWithUrl[]> {\r\n const updated = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} + 1` })\r\n .where(\r\n and(\r\n inArray(files.id, params.ids),\r\n params.purpose ? eq(files.purpose, params.purpose) : undefined,\r\n ),\r\n )\r\n .returning();\r\n return updated.map(item => ({\r\n ...item,\r\n url: this.buildUrl(item),\r\n }));\r\n },\r\n\r\n async releaseFile(params: ById): Promise<void> {\r\n const [updated] = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} - 1` })\r\n .where(eq(files.id, params.id))\r\n .returning();\r\n if (!updated) {\r\n throw new Error(\"File not found\");\r\n }\r\n if (updated.refCount < 1) {\r\n await this.deleteFile({ id: params.id });\r\n }\r\n },\r\n\r\n async releaseFiles(params: ByIds): Promise<void> {\r\n const updated = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} - 1` })\r\n .where(inArray(files.id, params.ids))\r\n .returning({\r\n id: files.id,\r\n refCount: files.refCount,\r\n });\r\n if (updated.length === 0) return;\r\n const toDelete = updated.filter((file) => file.refCount < 1);\r\n if (toDelete.length === 0) return;\r\n await Promise.all(\r\n toDelete.map((file) => this.deleteFile({ id: file.id })),\r\n );\r\n },\r\n };\r\n}","import type { PgDatabase } from \"drizzle-orm/pg-core\";\r\nimport { createRouter, type PurposePolicy } from \"./router\";\r\nimport { createService, type Service } from \"./service\";\r\nimport type { S3Options } from \"bun\";\r\n\r\nexport interface FilesServerOptions<TPurposes extends string> {\r\n db: PgDatabase<any, any, any>;\r\n s3: S3Options;\r\n policies: Record<TPurposes, PurposePolicy>;\r\n presignExpiresIn?: number;\r\n}\r\n\r\nexport function createFilesServer<const TPurpose extends string>(\r\n options: FilesServerOptions<TPurpose>\r\n): {\r\n readonly router: ReturnType<typeof createRouter<TPurpose>>;\r\n readonly service: Service;\r\n} {\r\n const service = createService({\r\n db: options.db,\r\n s3: options.s3,\r\n presignExpiresIn: options.presignExpiresIn,\r\n });\r\n const router = createRouter({\r\n service,\r\n policies: options.policies,\r\n });\r\n return {\r\n router,\r\n service,\r\n };\r\n}"],"mappings":"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"}
|
|
@@ -193,32 +193,39 @@ type ById = {
|
|
|
193
193
|
type ByIds = {
|
|
194
194
|
ids: string[];
|
|
195
195
|
};
|
|
196
|
+
type FileWithUrl = typeof files.$inferSelect & {
|
|
197
|
+
url: string;
|
|
198
|
+
};
|
|
196
199
|
declare function createService(options: {
|
|
197
200
|
db: PgDatabase<any, any, any>;
|
|
198
201
|
s3: S3Options;
|
|
199
202
|
keyPrefix?: string;
|
|
200
203
|
presignExpiresIn?: number;
|
|
201
204
|
}): {
|
|
202
|
-
listFiles(params: ByIds): Promise<
|
|
203
|
-
getFile(params: ById): Promise<
|
|
205
|
+
listFiles(params: ByIds): Promise<FileWithUrl[]>;
|
|
206
|
+
getFile(params: ById): Promise<FileWithUrl | null>;
|
|
204
207
|
buildUrl(file: Pick<typeof files.$inferSelect, "key" | "visibility">): string;
|
|
205
208
|
getUrl(params: ById): Promise<string | null>;
|
|
206
|
-
getUrls(params: ByIds): Promise<
|
|
209
|
+
getUrls(params: ByIds): Promise<{
|
|
210
|
+
id: string;
|
|
211
|
+
url: string | null;
|
|
212
|
+
}[]>;
|
|
207
213
|
uploadFile(params: {
|
|
208
214
|
file: File;
|
|
209
215
|
purpose: string;
|
|
210
216
|
visibility: "private" | "public";
|
|
211
|
-
|
|
217
|
+
keepFileName?: boolean | string;
|
|
218
|
+
}): Promise<FileWithUrl>;
|
|
212
219
|
deleteFile(params: {
|
|
213
220
|
id: string;
|
|
214
221
|
}): Promise<void>;
|
|
215
222
|
deleteFiles(params: ByIds): Promise<void>;
|
|
216
223
|
acquireFile(params: ById & {
|
|
217
224
|
purpose?: string;
|
|
218
|
-
}): Promise<
|
|
225
|
+
}): Promise<FileWithUrl>;
|
|
219
226
|
acquireFiles(params: ByIds & {
|
|
220
227
|
purpose?: string;
|
|
221
|
-
}): Promise<
|
|
228
|
+
}): Promise<FileWithUrl[]>;
|
|
222
229
|
releaseFile(params: ById): Promise<void>;
|
|
223
230
|
releaseFiles(params: ByIds): Promise<void>;
|
|
224
231
|
};
|
|
@@ -264,7 +271,7 @@ declare function createRouter<const TPurpose extends string>(options: {
|
|
|
264
271
|
200: {
|
|
265
272
|
id: string;
|
|
266
273
|
name: string | null;
|
|
267
|
-
|
|
274
|
+
purpose: string;
|
|
268
275
|
size: number;
|
|
269
276
|
mimeType: string;
|
|
270
277
|
createdAt: Date;
|
|
@@ -317,4 +324,4 @@ declare function createFilesServer<const TPurpose extends string>(options: Files
|
|
|
317
324
|
};
|
|
318
325
|
//#endregion
|
|
319
326
|
export { schema_d_exports as a, Visibility as i, createFilesServer as n, PurposePolicy as r, FilesServerOptions as t };
|
|
320
|
-
//# sourceMappingURL=server-
|
|
327
|
+
//# sourceMappingURL=server-DT8zI0-g.d.mts.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-B1B4vJ67.mjs","names":[],"sources":["../src/server/schema.ts","../src/server/router.ts","../src/server/service.ts","../src/server/server.ts"],"sourcesContent":["import { nanoid } from \"nanoid\";\r\nimport { index, integer, pgTable, text, timestamp, pgEnum } from \"drizzle-orm/pg-core\";\r\n\r\nexport const files = pgTable(\"files\", {\r\n id: text(\"id\").primaryKey().$defaultFn(() => `file_${nanoid()}`),\r\n purpose: text(\"purpose\").notNull(),\r\n name: text(\"name\"),\r\n key: text(\"key\").notNull().unique(),\r\n size: integer(\"size\").notNull(),\r\n mimeType: text(\"mime_type\").notNull().default(\"application/octet-stream\"),\r\n refCount: integer(\"ref_count\").notNull().default(0),\r\n visibility: pgEnum(\"visibility\", [\"private\", \"public\"])().notNull().default(\"private\"),\r\n createdAt: timestamp(\"created_at\").notNull().defaultNow(),\r\n updatedAt: timestamp(\"updated_at\").notNull().defaultNow(),\r\n}, (table) => [\r\n index(\"files_key_idx\").on(table.key),\r\n index(\"files_created_at_idx\").on(table.createdAt),\r\n]);","import { Elysia, t } from \"elysia\";\r\nimport type { Service } from \"./service\";\r\n\r\nexport type Visibility = \"private\" | \"public\";\r\n\r\nexport interface PurposePolicy {\r\n maxSize?: number;\r\n allowedMimeTypes?: string[];\r\n visibility?: Visibility;\r\n}\r\n\r\nexport function createRouter<const TPurpose extends string>(\r\n options: {\r\n service: Service;\r\n policies: Record<TPurpose, PurposePolicy>\r\n }\r\n) {\r\n \r\n const { \r\n service, \r\n policies,\r\n } = options;\r\n\r\n return new Elysia({\r\n prefix: \"/api\",\r\n }).post(\"/files/upload\", async ({ status, body }) => {\r\n const { file, purpose } = body;\r\n const policy = policies[purpose as TPurpose];\r\n if (!policy) {\r\n return status(400, {\r\n message: \"Purpose not supported\",\r\n });\r\n }\r\n if (policy.maxSize !== undefined && file.size > policy.maxSize) {\r\n return status(400, {\r\n message: \"File size exceeds the maximum allowed size\",\r\n });\r\n }\r\n if (policy.allowedMimeTypes !== undefined && !policy.allowedMimeTypes.includes(file.type)) {\r\n return status(400, {\r\n message: \"File type not allowed\",\r\n });\r\n }\r\n const uploaded = await service.uploadFile({ \r\n file, \r\n purpose, \r\n visibility: policy.visibility ?? \"private\" \r\n });\r\n const url = await service.getUrl({ id: uploaded.id });\r\n return status(200, {\r\n url: url ?? \"\",\r\n id: uploaded.id,\r\n name: uploaded.name,\r\n key: uploaded.key,\r\n size: uploaded.size,\r\n mimeType: uploaded.mimeType,\r\n createdAt: uploaded.createdAt,\r\n });\r\n }, {\r\n body: UploadRequest(Object.keys(policies) as TPurpose[]),\r\n response: {\r\n 200: FileResponse,\r\n 400: ErrorResponse,\r\n 500: ErrorResponse,\r\n }\r\n });\r\n}\r\n\r\nfunction UploadRequest<const TPurpose extends string>(\r\n purposes: TPurpose[]\r\n) {\r\n return t.Object({\r\n file: t.File(),\r\n purpose: t.Union(\r\n purposes.map((p) => t.Literal(p)) as [\r\n ReturnType<typeof t.Literal<TPurpose>>,\r\n ...ReturnType<typeof t.Literal<TPurpose>>[],\r\n ]\r\n )\r\n });\r\n}\r\n\r\nconst ErrorResponse = t.Object({\r\n message: t.String(),\r\n});\r\n\r\nconst FileResponse = t.Object({\r\n url: t.String(),\r\n id: t.String(),\r\n name: t.Nullable(t.String()),\r\n key: t.String(),\r\n size: t.Number(),\r\n mimeType: t.String(),\r\n createdAt: t.Date(),\r\n});","import { S3Client, type S3Options } from \"bun\";\r\nimport { nanoid } from \"nanoid\";\r\nimport { extname } from \"path\";\r\nimport { eq, sql, and, inArray } from \"drizzle-orm\";\r\nimport type { PgDatabase } from \"drizzle-orm/pg-core\";\r\nimport { files } from \"./schema\";\r\n\r\nexport type Service = ReturnType<typeof createService>;\r\n\r\ntype ById = { id: string };\r\ntype ByIds = { ids: string[] };\r\n\r\nexport function createService(options: {\r\n db: PgDatabase<any, any, any>;\r\n s3: S3Options;\r\n keyPrefix?: string;\r\n presignExpiresIn?: number;\r\n}) {\r\n const { \r\n db, \r\n s3,\r\n keyPrefix = \"files\",\r\n presignExpiresIn = 3600\r\n } = options;\r\n \r\n const { endpoint } = s3;\r\n const s3Client = new S3Client(s3);\r\n\r\n return {\r\n\r\n async listFiles(params: ByIds): Promise<typeof files.$inferSelect[]> {\r\n return await db\r\n .select()\r\n .from(files)\r\n .where(inArray(files.id, params.ids))\r\n .limit(params.ids.length);\r\n },\r\n\r\n async getFile(params: ById): Promise<typeof files.$inferSelect | null> {\r\n const [found] = await db\r\n .select()\r\n .from(files)\r\n .where(eq(files.id, params.id))\r\n .limit(1);\r\n return found ?? null;\r\n },\r\n\r\n buildUrl(file: Pick<typeof files.$inferSelect, \"key\" | \"visibility\">): string {\r\n if (file.visibility === \"public\") {\r\n return `${endpoint}/${file.key}`;\r\n }\r\n return s3Client.presign(file.key, { \r\n expiresIn: presignExpiresIn,\r\n });\r\n },\r\n\r\n async getUrl(params: ById): Promise<string | null> {\r\n const [found] = await db\r\n .select({ \r\n key: files.key,\r\n visibility: files.visibility,\r\n })\r\n .from(files)\r\n .where(eq(files.id, params.id))\r\n .limit(1);\r\n if (!found) return null;\r\n return this.buildUrl(found);\r\n },\r\n\r\n async getUrls(params: ByIds): Promise<Record<string, string>> {\r\n const rows = await db\r\n .select({\r\n id: files.id,\r\n key: files.key,\r\n visibility: files.visibility,\r\n })\r\n .from(files)\r\n .where(inArray(files.id, params.ids))\r\n .limit(params.ids.length);\r\n const urls: Record<string, string> = {};\r\n for (const row of rows) {\r\n urls[row.id] = this.buildUrl(row);\r\n }\r\n return urls;\r\n },\r\n\r\n async uploadFile(params: {\r\n file: File;\r\n purpose: string;\r\n visibility: \"private\" | \"public\";\r\n }): Promise<typeof files.$inferSelect> {\r\n const id = nanoid();\r\n const ext = extname(params.file.name);\r\n const key = [keyPrefix, params.purpose, `${id}${ext}`].join(\"/\");\r\n const s3file = s3Client.file(key);\r\n await s3file.write(params.file, {\r\n type: params.file.type,\r\n acl: params.visibility === \"public\" ? \"public-read\" : \"private\",\r\n contentDisposition: `attachment; filename=\"${params.file.name}\"`,\r\n });\r\n try {\r\n const [created] = await db.insert(files)\r\n .values({\r\n purpose: params.purpose,\r\n key,\r\n size: s3file.size,\r\n name: s3file.name ?? null,\r\n mimeType: s3file.type,\r\n visibility: params.visibility,\r\n })\r\n .returning();\r\n if (!created) {\r\n throw new Error(\"Failed to create file record\");\r\n }\r\n return created;\r\n } catch (error) {\r\n await s3file.delete().catch(() => {});\r\n throw error;\r\n }\r\n },\r\n\r\n async deleteFile(params: { id: string }): Promise<void> {\r\n const [deleted] = await db\r\n .delete(files)\r\n .where(eq(files.id, params.id))\r\n .returning();\r\n if (!deleted) return;\r\n await s3Client.delete(deleted.key);\r\n },\r\n\r\n async deleteFiles(params: ByIds): Promise<void> {\r\n const deleted = await db\r\n .delete(files)\r\n .where(inArray(files.id, params.ids))\r\n .returning({\r\n key: files.key,\r\n });\r\n\r\n if (deleted.length === 0) return;\r\n\r\n await Promise.all(\r\n deleted.map((file) => s3Client.delete(file.key)),\r\n );\r\n },\r\n\r\n async acquireFile(params: ById & { \r\n purpose?: string \r\n }): Promise<typeof files.$inferSelect> {\r\n const [updated] = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} + 1` })\r\n .where(\r\n and(\r\n eq(files.id, params.id),\r\n params.purpose ? eq(files.purpose, params.purpose) : undefined,\r\n )\r\n )\r\n .returning();\r\n if (!updated) {\r\n throw new Error(\"File not found\");\r\n }\r\n return updated;\r\n },\r\n\r\n async acquireFiles(\r\n params: ByIds & { purpose?: string },\r\n ): Promise<typeof files.$inferSelect[]> {\r\n const updated = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} + 1` })\r\n .where(\r\n and(\r\n inArray(files.id, params.ids),\r\n params.purpose ? eq(files.purpose, params.purpose) : undefined,\r\n ),\r\n )\r\n .returning();\r\n return updated;\r\n },\r\n\r\n async releaseFile(params: ById): Promise<void> {\r\n const [updated] = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} - 1` })\r\n .where(eq(files.id, params.id))\r\n .returning();\r\n if (!updated) {\r\n throw new Error(\"File not found\");\r\n }\r\n if (updated.refCount < 1) {\r\n await this.deleteFile({ id: params.id });\r\n }\r\n },\r\n\r\n async releaseFiles(params: ByIds): Promise<void> {\r\n const updated = await db\r\n .update(files)\r\n .set({ refCount: sql`${files.refCount} - 1` })\r\n .where(inArray(files.id, params.ids))\r\n .returning({\r\n id: files.id,\r\n refCount: files.refCount,\r\n });\r\n if (updated.length === 0) return;\r\n const toDelete = updated.filter((file) => file.refCount < 1);\r\n if (toDelete.length === 0) return;\r\n await Promise.all(\r\n toDelete.map((file) => this.deleteFile({ id: file.id })),\r\n );\r\n },\r\n };\r\n}","import type { PgDatabase } from \"drizzle-orm/pg-core\";\r\nimport { createRouter, type PurposePolicy } from \"./router\";\r\nimport { createService, type Service } from \"./service\";\r\nimport type { S3Options } from \"bun\";\r\n\r\nexport interface FilesServerOptions<TPurposes extends string> {\r\n db: PgDatabase<any, any, any>;\r\n s3: S3Options;\r\n policies: Record<TPurposes, PurposePolicy>;\r\n presignExpiresIn?: number;\r\n}\r\n\r\nexport function createFilesServer<const TPurpose extends string>(\r\n options: FilesServerOptions<TPurpose>\r\n): {\r\n readonly router: ReturnType<typeof createRouter<TPurpose>>;\r\n readonly service: Service;\r\n} {\r\n const service = createService({\r\n db: options.db,\r\n s3: options.s3,\r\n presignExpiresIn: options.presignExpiresIn,\r\n });\r\n const router = createRouter({\r\n service,\r\n policies: options.policies,\r\n });\r\n return {\r\n router,\r\n service,\r\n };\r\n}"],"mappings":"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"}
|