@opengis/fastify-table 2.2.12 → 2.3.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.
Files changed (28) hide show
  1. package/dist/helper.d.ts.map +1 -1
  2. package/dist/helper.js +1 -0
  3. package/dist/server/plugins/file/getFileSize.d.ts +2 -0
  4. package/dist/server/plugins/file/getFileSize.d.ts.map +1 -0
  5. package/dist/server/plugins/file/getFileSize.js +12 -0
  6. package/dist/server/plugins/file/providers/s3/funcs/getFileSize.d.ts +3 -0
  7. package/dist/server/plugins/file/providers/s3/funcs/getFileSize.d.ts.map +1 -0
  8. package/dist/server/plugins/file/providers/s3/funcs/getFileSize.js +30 -0
  9. package/dist/server/plugins/file/providers/s3/funcs/uploadFile.d.ts.map +1 -1
  10. package/dist/server/plugins/file/providers/s3/funcs/uploadFile.js +1 -0
  11. package/dist/server/plugins/file/providers/s3/index.d.ts +1 -0
  12. package/dist/server/plugins/file/providers/s3/index.d.ts.map +1 -1
  13. package/dist/server/plugins/file/providers/s3/index.js +2 -0
  14. package/dist/server/plugins/file/uploadFile.d.ts +1 -1
  15. package/dist/server/plugins/file/uploadFile.d.ts.map +1 -1
  16. package/dist/server/plugins/upload/getUploadStatus.d.ts.map +1 -1
  17. package/dist/server/plugins/upload/getUploadStatus.js +5 -0
  18. package/dist/server/plugins/upload/index.d.ts.map +1 -1
  19. package/dist/server/plugins/upload/index.js +2 -2
  20. package/dist/server/plugins/upload/s3.d.ts +74 -0
  21. package/dist/server/plugins/upload/s3.d.ts.map +1 -0
  22. package/dist/server/plugins/upload/s3.js +160 -0
  23. package/dist/server/plugins/upload/startUpload.d.ts +1 -1
  24. package/dist/server/plugins/upload/startUpload.d.ts.map +1 -1
  25. package/dist/server/plugins/upload/startUpload.js +17 -4
  26. package/dist/server/plugins/upload/uploadChunk.d.ts.map +1 -1
  27. package/dist/server/plugins/upload/uploadChunk.js +34 -2
  28. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../helper.ts"],"names":[],"mappings":"AAGA,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AAEvD,QAAA,MAAQ,MAAM,KAAoB,CAAC;AAInC,wBAAsB,KAAK,kBAI1B;AAGD,wBAAsB,QAAQ,kBAW7B;AAED,wBAAsB,KAAK,iBAyB1B;AAED,OAAO,EAEL,MAAM,EACN,MAAM,EACN,SAAS,GACV,CAAC"}
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../helper.ts"],"names":[],"mappings":"AAEA,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AAEvD,QAAA,MAAQ,MAAM,KAAoB,CAAC;AAInC,wBAAsB,KAAK,kBAI1B;AAGD,wBAAsB,QAAQ,kBAW7B;AAED,wBAAsB,KAAK,iBA0B1B;AAED,OAAO,EAEL,MAAM,EACN,MAAM,EACN,SAAS,GACV,CAAC"}
package/dist/helper.js CHANGED
@@ -31,6 +31,7 @@ export async function build() {
31
31
  });
32
32
  await app.ready(); // ? ensure plugins registered, can not add fastify hooks after app is ready
33
33
  }
34
+ console.log("server started", process.env.PORT || 3000);
34
35
  return app;
35
36
  }
36
37
  export {
@@ -0,0 +1,2 @@
1
+ export default function getFileSize(filePath: string, options?: Record<string, any>): Promise<any>;
2
+ //# sourceMappingURL=getFileSize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileSize.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/getFileSize.ts"],"names":[],"mappings":"AAGA,wBAA8B,WAAW,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,gBAclC"}
@@ -0,0 +1,12 @@
1
+ import path from "node:path";
2
+ import providers from "./providers/index.js";
3
+ export default async function getFileSize(filePath, options = {}) {
4
+ const filename = path.basename(filePath);
5
+ // prefix
6
+ const prefix = (options.prefix === "date"
7
+ ? new Date().toISOString().split("T")[0]
8
+ : null) || (options.prefix === "3s" ? filename.substring(0, 3) : "");
9
+ const relativePath = path.join(path.dirname(filePath), prefix, filename);
10
+ const fp = providers(options);
11
+ return fp.getFileSize(relativePath, options);
12
+ }
@@ -0,0 +1,3 @@
1
+ declare const getFileSize: (s3Settings?: any) => (fp: any, options?: Record<string, any>) => Promise<any>;
2
+ export default getFileSize;
3
+ //# sourceMappingURL=getFileSize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileSize.d.ts","sourceRoot":"","sources":["../../../../../../../server/plugins/file/providers/s3/funcs/getFileSize.ts"],"names":[],"mappings":"AAYA,QAAA,MAAM,WAAW,GACd,aAAa,GAAG,MACV,IAAI,GAAG,EAAE,UAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,iBAuBhD,CAAC;AAEJ,eAAe,WAAW,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { HeadObjectCommand } from "@aws-sdk/client-s3";
2
+ import s3Client from "../client.js";
3
+ import config from "../../../../../../config.js";
4
+ import getPath from "../../../utils/getPath.js";
5
+ import getS3FilePath from "./utils/getS3FilePath.js";
6
+ // if not found on s3 => fs
7
+ import fs from "../../fs.js";
8
+ const { getFileSize: getFileSizeFs } = fs();
9
+ const getFileSize = (s3Settings) => async (fp, options = {}) => {
10
+ const filepath = getS3FilePath(fp, s3Settings);
11
+ const bucketParams = {
12
+ Bucket: s3Settings?.containerName || config.s3?.containerName || "work",
13
+ Key: filepath[0] === "/" ? filepath?.slice(1) : filepath,
14
+ };
15
+ try {
16
+ const data = await s3Client.send(new HeadObjectCommand(bucketParams));
17
+ return data.ContentLength;
18
+ }
19
+ catch (err) {
20
+ if (options.fallback === false) {
21
+ throw new Error("file not found");
22
+ }
23
+ const filepath1 = getPath(fp);
24
+ if (!filepath1) {
25
+ throw new Error("file not found");
26
+ }
27
+ return getFileSizeFs(filepath1);
28
+ }
29
+ };
30
+ export default getFileSize;
@@ -1 +1 @@
1
- {"version":3,"file":"uploadFile.d.ts","sourceRoot":"","sources":["../../../../../../../server/plugins/file/providers/s3/funcs/uploadFile.ts"],"names":[],"mappings":"AAaA,QAAA,MAAM,UAAU,GAAI,aAAa,GAAG,MAAY,IAAI,GAAG,EAAE,MAAM,GAAG,+EA6BjE,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"uploadFile.d.ts","sourceRoot":"","sources":["../../../../../../../server/plugins/file/providers/s3/funcs/uploadFile.ts"],"names":[],"mappings":"AAcA,QAAA,MAAM,UAAU,GAAI,aAAa,GAAG,MAAY,IAAI,GAAG,EAAE,MAAM,GAAG,+EA6BjE,CAAC;AAEF,eAAe,UAAU,CAAC"}
@@ -6,6 +6,7 @@ import getDataSize from "../../utils/getDataSize.js";
6
6
  import dataTypes from "../../utils/handlers/dataTypes.js";
7
7
  import getMimeType from "../../mime/index.js";
8
8
  import getS3FilePath from "./utils/getS3FilePath.js";
9
+ // chunks not supported for s3
9
10
  const uploadFile = (s3Settings) => async (fp, data) => {
10
11
  const filepath = getS3FilePath(fp, s3Settings);
11
12
  const validData = await getValidData({ data, types: [dataTypes.stream] });
@@ -3,5 +3,6 @@ export default function s3Storage(opt: any): {
3
3
  downloadFile: (fp: string, options?: Record<string, any>) => Promise<any>;
4
4
  uploadFile: (fp: any, data: any) => Promise<import("@aws-sdk/client-s3").CompleteMultipartUploadCommandOutput>;
5
5
  fileExists: (fp: any, options?: Record<string, any>) => Promise<any>;
6
+ getFileSize: (fp: any, options?: Record<string, any>) => Promise<any>;
6
7
  };
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../server/plugins/file/providers/s3/index.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,GAAG,EAAE,GAAG;;;;;EAOzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../server/plugins/file/providers/s3/index.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,GAAG,EAAE,GAAG;;;;;;EAQzC"}
@@ -1,5 +1,6 @@
1
1
  import downloadFile from "./funcs/downloadFile.js";
2
2
  import fileExists from "./funcs/fileExists.js";
3
+ import getFileSize from "./funcs/getFileSize.js";
3
4
  import uploadFile from "./funcs/uploadFile.js";
4
5
  export default function s3Storage(opt) {
5
6
  return {
@@ -7,5 +8,6 @@ export default function s3Storage(opt) {
7
8
  downloadFile: downloadFile(opt),
8
9
  uploadFile: uploadFile(opt),
9
10
  fileExists: fileExists(opt),
11
+ getFileSize: getFileSize(opt),
10
12
  };
11
13
  }
@@ -1,3 +1,3 @@
1
- declare function uploadFile(filePath: string, data: any, options?: Record<string, string>): Promise<string>;
1
+ declare function uploadFile(filePath: string, data: any, options?: Record<string, any>): Promise<string>;
2
2
  export default uploadFile;
3
3
  //# sourceMappingURL=uploadFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uploadFile.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/uploadFile.ts"],"names":[],"mappings":"AAGA,iBAAe,UAAU,CACvB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,EACT,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,mBAgBrC;AAED,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"uploadFile.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/uploadFile.ts"],"names":[],"mappings":"AAGA,iBAAe,UAAU,CACvB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,GAAG,EACT,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,mBAgBlC;AAED,eAAe,UAAU,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"getUploadStatus.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/getUploadStatus.ts"],"names":[],"mappings":"AAaA,wBAA8B,eAAe,CAAC,EAC5C,IAAI,EACJ,EAAE,GACH,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ,gBA+BA"}
1
+ {"version":3,"file":"getUploadStatus.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/getUploadStatus.ts"],"names":[],"mappings":"AAeA,wBAA8B,eAAe,CAAC,EAC5C,IAAI,EACJ,EAAE,GACH,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ,gBAmCA"}
@@ -2,10 +2,15 @@ import path from "node:path";
2
2
  import { existsSync } from "node:fs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { prefix, metaDir, uploadChunkDirectory, fetchTimeoutMs, } from "./index.js";
5
+ import config from "../../../config.js";
5
6
  import isFileExists from "../file/isFileExists.js";
7
+ import { getUploadStatus as getUploadStatusS3 } from "./s3.js";
6
8
  export default async function getUploadStatus({ host, id, }) {
7
9
  // return local file metadata
8
10
  if (!host) {
11
+ if (config.s3) {
12
+ return getUploadStatusS3({ id });
13
+ }
9
14
  const metaExists = existsSync(path.join(metaDir, `${id}.json`));
10
15
  // check file upload status: finished/inprogress
11
16
  const meta = metaExists
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/index.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,cAAc,QAAmC,CAAC;AAE/D,eAAO,MAAM,MAAM,iBAAiB,CAAC;AAErC,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD,eAAO,MAAM,OAAO,QAEE,CAAC;AAEvB,eAAO,MAAM,OAAO,QAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/index.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,cAAc,QAE1B,CAAC;AAEF,eAAO,MAAM,MAAM,iBAAiB,CAAC;AAErC,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD,eAAO,MAAM,OAAO,QAEE,CAAC;AAEvB,eAAO,MAAM,OAAO,QAA4B,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
2
  import config from "../../../config.js";
3
3
  import getFolder from "../crud/funcs/utils/getFolder.js";
4
- config.chunkSize = +(config.chunkSize || 1048576); // 1 MB per chunk by default
4
+ config.chunkSize = +(config.chunkSize || 5242880); // 5 MB per chunk by default
5
5
  const rootDir = getFolder(config, "local");
6
- export const fetchTimeoutMs = +(config.fetchTimeoutMs || 5000);
6
+ export const fetchTimeoutMs = +(config.fetchTimeoutMs || (process.platform === "win32" ? 50000 : 5000));
7
7
  export const prefix = "file/upload2";
8
8
  export const uploadChunkDirectory = "/files/uploads";
9
9
  export const fileDir = path
@@ -0,0 +1,74 @@
1
+ export declare function findUpload({ id }: {
2
+ id: string;
3
+ }, s3?: any): Promise<{
4
+ UploadId: string | undefined;
5
+ Key: any;
6
+ Bucket: any;
7
+ fileSize: any;
8
+ ContentType: any;
9
+ uploaded: any;
10
+ parts: any;
11
+ }>;
12
+ export declare function listUploads({ Bucket }: {
13
+ Bucket: string;
14
+ }, s3?: any): Promise<any>;
15
+ export declare function startUpload({ Bucket, Key, ContentType, fileSize, }: {
16
+ Bucket: string;
17
+ Key: string;
18
+ ContentType: string;
19
+ fileSize: number;
20
+ }, s3?: any): Promise<{
21
+ Key: any;
22
+ Bucket: any;
23
+ UploadId: any;
24
+ fileSize: any;
25
+ extension: any;
26
+ ContentType: any;
27
+ cache: boolean;
28
+ } | {
29
+ UploadId: any;
30
+ Key?: undefined;
31
+ Bucket?: undefined;
32
+ fileSize?: undefined;
33
+ extension?: undefined;
34
+ ContentType?: undefined;
35
+ cache?: undefined;
36
+ }>;
37
+ export declare function uploadChunk({ id, chunk, partNumber, }: {
38
+ id: string;
39
+ chunk: any;
40
+ partNumber: number;
41
+ }, s3?: any): Promise<{
42
+ UploadId: string | undefined;
43
+ Key: any;
44
+ Bucket: any;
45
+ fileSize: any;
46
+ ContentType: any;
47
+ uploaded: any;
48
+ parts: any;
49
+ }>;
50
+ export declare function finishUpload({ id, }: {
51
+ id: string;
52
+ }, s3?: any): Promise<{
53
+ uploaded: any;
54
+ }>;
55
+ export declare function getUploadStatus({ id, Key, Bucket, }: {
56
+ id: string;
57
+ Key?: string;
58
+ Bucket?: string;
59
+ }, s3?: any): Promise<{
60
+ exists: boolean;
61
+ uploaded: any;
62
+ finished?: undefined;
63
+ id?: undefined;
64
+ size?: undefined;
65
+ parts?: undefined;
66
+ } | {
67
+ exists: boolean;
68
+ finished: boolean;
69
+ uploaded: any;
70
+ id: string;
71
+ size: number | undefined;
72
+ parts: any;
73
+ }>;
74
+ //# sourceMappingURL=s3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/s3.ts"],"names":[],"mappings":"AAiBA,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAW;;;;;;;;GAkCrE;AAED,wBAAsB,WAAW,CAC/B,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC9B,EAAE,MAAW,gBAed;AAED,wBAAsB,WAAW,CAC/B,EACE,MAAM,EACN,GAAG,EACH,WAAW,EACX,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,EACD,EAAE,MAAW;;;;;;;;;;;;;;;;GAoDd;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,EACF,KAAK,EACL,UAAU,GACX,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,EACD,EAAE,MAAW;;;;;;;;GAuBd;AAED,wBAAsB,YAAY,CAChC,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;GAmCd;AAED,wBAAsB,eAAe,CACnC,EACE,EAAE,EACF,GAAG,EACH,MAAM,GACP,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,EACD,EAAE,MAAW;;;;;;;;;;;;;;GAgEd"}
@@ -0,0 +1,160 @@
1
+ import path from "node:path";
2
+ import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, ListMultipartUploadsCommand, HeadObjectCommand, ListPartsCommand, } from "@aws-sdk/client-s3";
3
+ import config from "../../../config.js";
4
+ import s3Client from "../file/providers/s3/client.js";
5
+ import rclient from "../redis/client.js";
6
+ const uploads = new Map();
7
+ export async function findUpload({ id }, s3 = s3Client) {
8
+ const cachedInfo = config.redis ? await rclient.get(id) : undefined;
9
+ const { Key, Bucket, fileSize: cacheSize, ContentType: cacheContentType, } = cachedInfo ? JSON.parse(cachedInfo) : uploads.get(id) || {};
10
+ const parts = await s3
11
+ .send(new ListPartsCommand({
12
+ Bucket,
13
+ Key,
14
+ UploadId: id,
15
+ }))
16
+ .catch((err) => null);
17
+ return {
18
+ UploadId: parts ? id : undefined,
19
+ Key,
20
+ Bucket,
21
+ fileSize: cacheSize,
22
+ ContentType: cacheContentType,
23
+ uploaded: parts.Parts?.reduce((sum, part) => sum + (part.Size || 0), 0) ||
24
+ 0,
25
+ parts: parts.Parts?.map((p) => ({
26
+ ETag: p.ETag,
27
+ PartNumber: p.PartNumber,
28
+ })) || [],
29
+ };
30
+ }
31
+ export async function listUploads({ Bucket }, s3 = s3Client) {
32
+ const res = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
33
+ if (!res.Uploads) {
34
+ return [];
35
+ }
36
+ return res.Uploads.map((upload) => ({
37
+ key: upload.Key,
38
+ uploadId: upload.UploadId,
39
+ initiated: upload.Initiated,
40
+ owner: upload.Owner?.DisplayName,
41
+ storageClass: upload.StorageClass,
42
+ }));
43
+ }
44
+ export async function startUpload({ Bucket, Key, ContentType, fileSize, }, s3 = s3Client) {
45
+ const cachedInfo = config.redis ? await rclient.get(Key) : undefined;
46
+ const { Key: cacheKey, Bucket: cacheBucket, UploadId, fileSize: cacheSize, extension: cacheExtension, ContentType: cacheContentType, } = cachedInfo ? JSON.parse(cachedInfo) : uploads.get(Key) || {};
47
+ if (UploadId) {
48
+ return {
49
+ Key: cacheKey,
50
+ Bucket: cacheBucket,
51
+ UploadId,
52
+ fileSize: cacheSize,
53
+ extension: cacheExtension,
54
+ ContentType: cacheContentType,
55
+ cache: true,
56
+ };
57
+ }
58
+ const res = await s3.send(new CreateMultipartUploadCommand({
59
+ Bucket,
60
+ Key,
61
+ ContentType,
62
+ }));
63
+ const extension = path.extname(Key).substring(1);
64
+ if (config.redis) {
65
+ await rclient.set(res.UploadId, JSON.stringify({ Key, Bucket, fileSize, extension, ContentType }), "EX", 60 * 60);
66
+ }
67
+ else {
68
+ uploads.set(res.UploadId, {
69
+ Key,
70
+ Bucket,
71
+ fileSize,
72
+ extension,
73
+ ContentType,
74
+ });
75
+ }
76
+ return { UploadId: res.UploadId };
77
+ }
78
+ export async function uploadChunk({ id, chunk, partNumber, }, s3 = s3Client) {
79
+ let upload = await findUpload({ id }, s3);
80
+ if (!upload?.UploadId) {
81
+ throw new Error("upload not found");
82
+ }
83
+ if (upload.parts.some((p) => p.PartNumber === partNumber)) {
84
+ throw new Error(`Part ${partNumber} already uploaded`);
85
+ }
86
+ await s3.send(new UploadPartCommand({
87
+ Bucket: upload.Bucket,
88
+ Key: upload.Key,
89
+ UploadId: upload.UploadId,
90
+ PartNumber: partNumber,
91
+ Body: chunk,
92
+ }));
93
+ return upload;
94
+ }
95
+ export async function finishUpload({ id, }, s3 = s3Client) {
96
+ const redisCache = config.redis ? await rclient.get(id) : undefined;
97
+ const { fileSize: cachedSize, Key, Bucket, } = redisCache ? JSON.parse(redisCache) : uploads.get(id) || {};
98
+ const size = cachedSize ? Number(cachedSize) : undefined;
99
+ const upload = await findUpload({ id }, s3);
100
+ if (!upload?.uploaded) {
101
+ throw new Error("File not uploaded");
102
+ }
103
+ if (size && size != upload.uploaded) {
104
+ throw new Error("File size mismatch");
105
+ }
106
+ await s3.send(new CompleteMultipartUploadCommand({
107
+ Bucket,
108
+ Key,
109
+ UploadId: id,
110
+ MultipartUpload: {
111
+ Parts: upload.parts.sort((a, b) => a.PartNumber - b.PartNumber),
112
+ },
113
+ }));
114
+ // if (config.redis) await rclient.del(Key);
115
+ return { uploaded: upload.uploaded };
116
+ }
117
+ export async function getUploadStatus({ id, Key, Bucket, }, s3 = s3Client) {
118
+ const redisCache = config.redis ? await rclient.get(id) : undefined;
119
+ // const redisCache = config.redis ? await rclient.get(Key) : undefined;
120
+ const { fileSize: cachedSize, Key: redisKey, Bucket: redisBucket, } = redisCache ? JSON.parse(redisCache) : uploads.get(id) || {};
121
+ if (!redisKey && Key && Bucket) {
122
+ const res = await s3
123
+ .send(new HeadObjectCommand({
124
+ Bucket,
125
+ Key,
126
+ }))
127
+ .catch((err) => false);
128
+ return { exists: !!res, uploaded: res?.ContentLength };
129
+ }
130
+ const parts = await s3
131
+ .send(new ListPartsCommand({
132
+ Bucket: redisBucket,
133
+ Key: redisKey,
134
+ UploadId: id,
135
+ }))
136
+ .catch((err) => ({
137
+ Parts: [],
138
+ }));
139
+ const res = id && !parts.Parts?.length
140
+ ? await s3
141
+ .send(new HeadObjectCommand({
142
+ Bucket: redisBucket,
143
+ Key: redisKey,
144
+ }))
145
+ .catch((err) => false)
146
+ : false;
147
+ return {
148
+ exists: !!res || !!parts.Parts?.length,
149
+ finished: !!res,
150
+ uploaded: res
151
+ ? res.ContentLength
152
+ : parts.Parts?.reduce((acc, curr) => acc + (curr.Size || 0), 0) || 0,
153
+ id,
154
+ size: cachedSize ? Number(cachedSize) : undefined,
155
+ parts: parts.Parts?.map((p) => ({
156
+ ETag: p.ETag,
157
+ PartNumber: p.PartNumber,
158
+ })) || [],
159
+ };
160
+ }
@@ -2,7 +2,7 @@ export default function startUpload({ host, id, fileName, size, subdir, }: {
2
2
  host?: string;
3
3
  id?: string;
4
4
  fileName: string;
5
- size?: number;
5
+ size: number;
6
6
  subdir?: string;
7
7
  }): Promise<any>;
8
8
  //# sourceMappingURL=startUpload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"startUpload.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/startUpload.ts"],"names":[],"mappings":"AAaA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,MAAM,GACP,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,gBAkEA"}
1
+ {"version":3,"file":"startUpload.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/startUpload.ts"],"names":[],"mappings":"AAiBA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,MAAM,GACP,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,gBA6EA"}
@@ -1,8 +1,11 @@
1
1
  import path from "node:path";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
+ import config from "../../../config.js";
4
5
  import getUploadStatus from "./getUploadStatus.js";
5
6
  import { prefix, fileDir, metaDir, uploadChunkDirectory, fetchTimeoutMs, } from "./index.js";
7
+ import mimes from "../file/providers/mime/mimes.js";
8
+ import { startUpload as startUploadS3 } from "./s3.js";
6
9
  export default async function startUpload({ host, id, fileName, size, subdir, }) {
7
10
  if (subdir && (typeof subdir !== "string" || subdir.includes(".."))) {
8
11
  return {
@@ -11,8 +14,21 @@ export default async function startUpload({ host, id, fileName, size, subdir, })
11
14
  };
12
15
  }
13
16
  if (!host) {
14
- const extension = path.extname(fileName).substring(1);
15
17
  const id1 = id || randomUUID();
18
+ const extension = path.extname(fileName).substring(1);
19
+ const relativeDirpath = path
20
+ .join(uploadChunkDirectory, subdir || "")
21
+ .replace(/\\/g, "/");
22
+ if (config.s3) {
23
+ const filepath = path.posix.join(relativeDirpath, `${id1}.${extension}`);
24
+ const { UploadId } = await startUploadS3({
25
+ Bucket: config.s3?.containerName || "work",
26
+ Key: filepath[0] === "/" ? filepath?.slice(1) : filepath,
27
+ ContentType: mimes[extension],
28
+ fileSize: size,
29
+ });
30
+ return { UploadId, provider: "s3" };
31
+ }
16
32
  const key = id1.split("-").pop();
17
33
  const meta = {
18
34
  id: id1,
@@ -33,9 +49,6 @@ export default async function startUpload({ host, id, fileName, size, subdir, })
33
49
  return meta;
34
50
  }
35
51
  // if not started - start new upload
36
- const relativeDirpath = path
37
- .join(uploadChunkDirectory, subdir || "")
38
- .replace(/\\/g, "/");
39
52
  await mkdir(path.join(fileDir, "tmp"), { recursive: true });
40
53
  // create metadata for resumable upload
41
54
  const metaData = {
@@ -1 +1 @@
1
- {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/uploadChunk.ts"],"names":[],"mappings":"AAaA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,IAAI,EACJ,MAAM,EACN,GAAG,EACH,IAAI,GACL,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,gBA+EA"}
1
+ {"version":3,"file":"uploadChunk.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/uploadChunk.ts"],"names":[],"mappings":"AAqBA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,IAAI,EACJ,MAAM,EACN,GAAG,EACH,IAAI,GACL,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,GAAG,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd,gBA+HA"}
@@ -2,10 +2,28 @@ import path from "node:path";
2
2
  import { existsSync } from "node:fs";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import { prefix, metaDir, fetchTimeoutMs, uploadChunkDirectory, } from "./index.js";
5
+ import config from "../../../config.js";
5
6
  import uploadFile from "../file/uploadFile.js";
6
7
  import applyHook from "../hook/applyHook.js";
8
+ import getFileSize from "../file/getFileSize.js";
9
+ import { finishUpload as finishUploadS3, uploadChunk as uploadChunkS3, getUploadStatus as getUploadStatusS3, findUpload as findUploadS3, } from "./s3.js";
7
10
  export default async function uploadChunk({ host, id, body, offset, end, size, }) {
8
11
  if (!host) {
12
+ if (config.s3) {
13
+ const partNumber = Math.floor(offset / config.chunkSize) + 1;
14
+ const { fileSize } = await findUploadS3({ id });
15
+ await uploadChunkS3({
16
+ id,
17
+ chunk: body,
18
+ partNumber,
19
+ });
20
+ const currentState = await getUploadStatusS3({ id });
21
+ const finished = currentState.uploaded === fileSize;
22
+ if (finished) {
23
+ return finishUploadS3({ id });
24
+ }
25
+ return currentState;
26
+ }
9
27
  const metaExists = existsSync(path.join(metaDir, `${id}.json`));
10
28
  if (!metaExists) {
11
29
  return { error: "File not found", code: 404 };
@@ -20,6 +38,17 @@ export default async function uploadChunk({ host, id, body, offset, end, size, }
20
38
  if (body?.length !== end - offset + 1) {
21
39
  return { error: "Chunk size mismatch", code: 400 };
22
40
  }
41
+ const relpath = path.posix.join(uploadChunkDirectory, `${id}.${meta.extension}`);
42
+ const filepath = path.posix.join(config.folder, uploadChunkDirectory, `${id}.${meta.extension}`);
43
+ const res = await getFileSize(relpath, { fallback: false }).catch((err) => ({
44
+ error: err.toString(),
45
+ }));
46
+ if (res.error && res.error !== "Error: file not found") {
47
+ throw new Error(res.error);
48
+ }
49
+ if (meta.size > res) {
50
+ throw new Error("uploaded size exceeds file size");
51
+ }
23
52
  // append chunk to existing file
24
53
  await uploadFile(path
25
54
  .join(uploadChunkDirectory, `${id}.${meta.extension}`)
@@ -47,10 +76,13 @@ export default async function uploadChunk({ host, id, body, offset, end, size, }
47
76
  if (!end || !size) {
48
77
  return { error: "not enough params: offset/end/size", code: 400 };
49
78
  }
50
- const res = await fetch(`${host}/${prefix}/${id}`, {
79
+ const url = `${host}/${prefix}/${id}`;
80
+ const range = `bytes ${offset || 0}-${end}/${size}`;
81
+ console.log("PATCH request", url, range, body.length);
82
+ const res = await fetch(url, {
51
83
  method: "PATCH",
52
84
  headers: {
53
- "Content-Range": `bytes ${offset || 0}-${end}/${size}`,
85
+ "Content-Range": range,
54
86
  },
55
87
  body,
56
88
  signal: AbortSignal.timeout(fetchTimeoutMs),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "2.2.12",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [