@fengsoft/storage-core-adapter-filesystem 0.1.0 → 0.1.1

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,18 @@
1
+ import { type CreatePresignedUploadInput, type DeleteObjectInput, type GetObjectResult, type HeadObjectResult, type ListObjectsInput, type ListObjectsResult, type PresignedUpload, type PutObjectInput, type StorageDriver } from "@fengsoft/storage-core";
2
+ type FilesystemStorageDriverOptions = {
3
+ rootDir: string;
4
+ publicBaseUrl?: string;
5
+ };
6
+ export declare class FilesystemStorageDriver implements StorageDriver {
7
+ #private;
8
+ constructor(options: FilesystemStorageDriverOptions);
9
+ putObject(input: PutObjectInput): Promise<HeadObjectResult>;
10
+ deleteObject(input: DeleteObjectInput): Promise<void>;
11
+ headObject(input: DeleteObjectInput): Promise<HeadObjectResult>;
12
+ getObject(input: DeleteObjectInput): Promise<GetObjectResult>;
13
+ listObjects(input?: ListObjectsInput): Promise<ListObjectsResult>;
14
+ createPresignedUpload(input: CreatePresignedUploadInput): Promise<PresignedUpload>;
15
+ resolvePublicUrl(key: string): string | null;
16
+ }
17
+ export {};
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAEN,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAKlB,MAAM,wBAAwB,CAAC;AAEhC,KAAK,8BAA8B,GAAG;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAWF,qBAAa,uBAAwB,YAAW,aAAa;;gBAIhD,OAAO,EAAE,8BAA8B;IAK7C,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmB3D,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOrD,UAAU,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAK/D,SAAS,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAgB7D,WAAW,CAAC,KAAK,GAAE,gBAAqB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA2BrE,qBAAqB,CAC1B,KAAK,EAAE,0BAA0B,GAC/B,OAAO,CAAC,eAAe,CAAC;IAqB3B,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAqE5C"}
package/dist/index.js ADDED
@@ -0,0 +1,160 @@
1
+ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { buildPublicUrl, decodeCursor, StorageObjectNotFoundError, toBuffer, toCursor, } from "@fengsoft/storage-core";
5
+ export class FilesystemStorageDriver {
6
+ #rootDir;
7
+ #publicBaseUrl;
8
+ constructor(options) {
9
+ this.#rootDir = options.rootDir;
10
+ this.#publicBaseUrl = options.publicBaseUrl ?? null;
11
+ }
12
+ async putObject(input) {
13
+ const body = toBuffer(input.body);
14
+ const dataPath = this.#toDataPath(input.key);
15
+ const metaPath = this.#toMetadataPath(input.key);
16
+ const lastModified = new Date();
17
+ await mkdir(dirname(dataPath), { recursive: true });
18
+ await writeFile(dataPath, body);
19
+ const meta = {
20
+ contentType: input.contentType ?? null,
21
+ cacheControl: input.cacheControl ?? null,
22
+ metadata: { ...(input.metadata ?? {}) },
23
+ etag: `fs-${input.key}-${body.length}-${lastModified.getTime()}`,
24
+ lastModified: lastModified.toISOString(),
25
+ contentLength: body.length,
26
+ };
27
+ await writeFile(metaPath, JSON.stringify(meta, null, 2));
28
+ return this.#toHeadObjectResult(input.key, meta);
29
+ }
30
+ async deleteObject(input) {
31
+ await Promise.all([
32
+ rm(this.#toDataPath(input.key), { force: true }),
33
+ rm(this.#toMetadataPath(input.key), { force: true }),
34
+ ]);
35
+ }
36
+ async headObject(input) {
37
+ const meta = await this.#readMetadata(input.key);
38
+ return this.#toHeadObjectResult(input.key, meta);
39
+ }
40
+ async getObject(input) {
41
+ const [meta, body] = await Promise.all([
42
+ this.#readMetadata(input.key),
43
+ readFile(this.#toDataPath(input.key)),
44
+ ]).catch((error) => {
45
+ if (this.#isMissing(error)) {
46
+ throw new StorageObjectNotFoundError(input.key);
47
+ }
48
+ throw error;
49
+ });
50
+ return {
51
+ ...this.#toHeadObjectResult(input.key, meta),
52
+ body,
53
+ };
54
+ }
55
+ async listObjects(input = {}) {
56
+ const files = await this.#walkDataFiles(this.#rootDir);
57
+ const prefix = input.prefix ?? "";
58
+ const limit = input.limit ?? 1000;
59
+ const offset = decodeCursor(input.cursor);
60
+ const filtered = files
61
+ .map((file) => relative(this.#rootDir, file).split("\\").join("/"))
62
+ .filter((key) => !key.endsWith(".meta.json") && key.startsWith(prefix))
63
+ .sort((left, right) => left.localeCompare(right));
64
+ const page = filtered.slice(offset, offset + limit);
65
+ const items = await Promise.all(page.map(async (key) => {
66
+ const meta = await this.#readMetadata(key);
67
+ return {
68
+ key,
69
+ contentLength: meta.contentLength,
70
+ lastModified: new Date(meta.lastModified),
71
+ etag: meta.etag,
72
+ };
73
+ }));
74
+ return {
75
+ items,
76
+ nextCursor: toCursor(offset + page.length, filtered.length),
77
+ };
78
+ }
79
+ async createPresignedUpload(input) {
80
+ const expiresInSeconds = input.expiresInSeconds ?? 900;
81
+ const headers = {};
82
+ if (input.contentType) {
83
+ headers["content-type"] = input.contentType;
84
+ }
85
+ if (input.cacheControl) {
86
+ headers["cache-control"] = input.cacheControl;
87
+ }
88
+ for (const [key, value] of Object.entries(input.metadata ?? {})) {
89
+ headers[`x-storage-meta-${key}`] = value;
90
+ }
91
+ return {
92
+ key: input.key,
93
+ method: "PUT",
94
+ url: pathToFileURL(this.#toDataPath(input.key)).toString(),
95
+ headers,
96
+ expiresAt: new Date(Date.now() + expiresInSeconds * 1000),
97
+ };
98
+ }
99
+ resolvePublicUrl(key) {
100
+ return buildPublicUrl(this.#publicBaseUrl, key);
101
+ }
102
+ #toDataPath(key) {
103
+ return join(this.#rootDir, key);
104
+ }
105
+ #toMetadataPath(key) {
106
+ return join(this.#rootDir, `${key}.meta.json`);
107
+ }
108
+ async #readMetadata(key) {
109
+ try {
110
+ const raw = await readFile(this.#toMetadataPath(key), "utf8");
111
+ return JSON.parse(raw);
112
+ }
113
+ catch (error) {
114
+ if (this.#isMissing(error)) {
115
+ throw new StorageObjectNotFoundError(key);
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ #toHeadObjectResult(key, meta) {
121
+ return {
122
+ key,
123
+ contentLength: meta.contentLength,
124
+ contentType: meta.contentType,
125
+ cacheControl: meta.cacheControl,
126
+ etag: meta.etag,
127
+ lastModified: new Date(meta.lastModified),
128
+ metadata: { ...meta.metadata },
129
+ };
130
+ }
131
+ async #walkDataFiles(currentDir) {
132
+ try {
133
+ const entries = await readdir(currentDir, {
134
+ withFileTypes: true,
135
+ encoding: "utf8",
136
+ });
137
+ const files = await Promise.all(entries.map(async (entry) => {
138
+ const entryPath = join(currentDir, entry.name);
139
+ if (entry.isDirectory()) {
140
+ return this.#walkDataFiles(entryPath);
141
+ }
142
+ return entryPath;
143
+ }));
144
+ return files.flat();
145
+ }
146
+ catch (error) {
147
+ if (this.#isMissing(error)) {
148
+ return [];
149
+ }
150
+ throw error;
151
+ }
152
+ }
153
+ #isMissing(error) {
154
+ return (typeof error === "object" &&
155
+ error !== null &&
156
+ "code" in error &&
157
+ error.code === "ENOENT");
158
+ }
159
+ }
160
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fengsoft/storage-core-adapter-filesystem",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -31,7 +31,7 @@
31
31
  "development"
32
32
  ],
33
33
  "dependencies": {
34
- "@fengsoft/storage-core": "0.1.0"
34
+ "@fengsoft/storage-core": "0.1.1"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"