@opengis/fastify-table 2.3.0 → 2.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AA4FA,iBAAe,MAAM,CAAC,OAAO,EAAE,GAAG,iBAgLjC;;AACD,wBAEG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AA6FA,iBAAe,MAAM,CAAC,OAAO,EAAE,GAAG,iBAiLjC;;AACD,wBAEG"}
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ import metricPlugin from "./server/plugins/metric/index.js";
34
34
  import redisPlugin from "./server/plugins/redis/index.js";
35
35
  import loggerPlugin from "./server/plugins/logger/index.js";
36
36
  import authPlugin from "./server/plugins/auth/index.js";
37
+ import chunkedUploadPlugin from "./server/plugins/upload/index.js";
37
38
  // utils
38
39
  import execMigrations from "./server/plugins/migration/exec.migrations.js";
39
40
  import pgClients from "./server/plugins/pg/pgClients.js";
@@ -113,6 +114,7 @@ async function plugin(fastify) {
113
114
  execMigrations(path.join(cwd, "server/migrations"), pgClients.client, true).catch((err) => console.warn(err.toString()));
114
115
  // plugins / utils / funcs
115
116
  authPlugin(fastify); // fastify-auth api + hooks integrated to core
117
+ chunkedUploadPlugin(fastify);
116
118
  policyPlugin(fastify);
117
119
  metricPlugin();
118
120
  redisPlugin(fastify);
@@ -1,6 +1,9 @@
1
+ import { FastifyInstance } from "fastify";
1
2
  export declare const fetchTimeoutMs: number;
2
3
  export declare const prefix = "file/upload2";
3
4
  export declare const uploadChunkDirectory = "/files/uploads";
4
5
  export declare const fileDir: string;
5
6
  export declare const metaDir: string;
7
+ declare function chunkedUploadPlugin(app: FastifyInstance): void;
8
+ export default chunkedUploadPlugin;
6
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAW1C,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;AAKjD,iBAAS,mBAAmB,CAAC,GAAG,EAAE,eAAe,QAIhD;AAED,eAAe,mBAAmB,CAAC"}
@@ -10,3 +10,11 @@ export const fileDir = path
10
10
  .join(rootDir, uploadChunkDirectory)
11
11
  .replace(/\\/g, "/");
12
12
  export const metaDir = path.join(fileDir, "tmp");
13
+ import { cleanupNonRedisUploads } from "./s3.js";
14
+ // w/ out redis - chunked uploads are non-resumable in order to avoid unclosed multiparts (non-discoverable via s3 commands with current version of minio)
15
+ function chunkedUploadPlugin(app) {
16
+ app.addHook("onClose", async () => {
17
+ await cleanupNonRedisUploads();
18
+ });
19
+ }
20
+ export default chunkedUploadPlugin;
@@ -1,3 +1,10 @@
1
+ export declare function cleanupNonRedisUploads(s3?: any): Promise<void>;
2
+ export declare function saveUploadMeta(id: string, meta: any, ttl?: number): Promise<void>;
3
+ export declare function refreshUploadTTL(id: string, ttl?: number, s3?: any): Promise<void>;
4
+ export declare function deleteUploadMeta(id: string, key?: string): Promise<void>;
5
+ export declare function getUploadMeta(id: string): Promise<any>;
6
+ export declare function getResumableUploadId(key: string): Promise<any>;
7
+ export declare function cleanupStaleUploads(s3?: any): Promise<void>;
1
8
  export declare function findUpload({ id }: {
2
9
  id: string;
3
10
  }, s3?: any): Promise<{
@@ -9,16 +16,13 @@ export declare function findUpload({ id }: {
9
16
  uploaded: any;
10
17
  parts: any;
11
18
  }>;
12
- export declare function listUploads({ Bucket }: {
13
- Bucket: string;
14
- }, s3?: any): Promise<any>;
15
19
  export declare function startUpload({ Bucket, Key, ContentType, fileSize, }: {
16
20
  Bucket: string;
17
21
  Key: string;
18
22
  ContentType: string;
19
23
  fileSize: number;
20
24
  }, s3?: any): Promise<{
21
- Key: any;
25
+ Key: string;
22
26
  Bucket: any;
23
27
  UploadId: any;
24
28
  fileSize: any;
@@ -52,6 +56,11 @@ export declare function finishUpload({ id, }: {
52
56
  }, s3?: any): Promise<{
53
57
  uploaded: any;
54
58
  }>;
59
+ export declare function abortUpload({ id, }: {
60
+ id: string;
61
+ }, s3?: any): Promise<{
62
+ aborted: boolean;
63
+ }>;
55
64
  export declare function getUploadStatus({ id, Key, Bucket, }: {
56
65
  id: string;
57
66
  Key?: string;
@@ -1 +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"}
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/s3.ts"],"names":[],"mappings":"AAgGA,wBAAsB,sBAAsB,CAAC,EAAE,MAAW,iBAuCzD;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,GAAG,EACT,GAAG,SAAqB,iBAazB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,GAAG,SAAqB,EACxB,EAAE,MAAW,iBAqBd;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,iBA0B9D;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,gBAQ7C;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,gBAMrD;AAED,wBAAsB,mBAAmB,CAAC,EAAE,MAAW,iBAwEtD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAW;;;;;;;;GAqCrE;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;;;;;;;;;;;;;;;;GAqDd;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;;;;;;;;GAyBd;AAED,wBAAsB,YAAY,CAChC,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;GAoCd;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;GAuBd;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;;;;;;;;;;;;;;GAmEd"}
@@ -1,57 +1,257 @@
1
1
  import path from "node:path";
2
- import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, ListMultipartUploadsCommand, HeadObjectCommand, ListPartsCommand, } from "@aws-sdk/client-s3";
2
+ import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, HeadObjectCommand, ListPartsCommand, AbortMultipartUploadCommand, } from "@aws-sdk/client-s3";
3
3
  import config from "../../../config.js";
4
4
  import s3Client from "../file/providers/s3/client.js";
5
5
  import rclient from "../redis/client.js";
6
+ import logger from "../logger/getLogger.js";
6
7
  const uploads = new Map();
8
+ const resumableUploads = new Map();
9
+ // non-redis upload timeout handlers
10
+ const uploadTimeouts = new Map();
11
+ const UPLOAD_TTL_SECONDS = 60 * 60;
12
+ // expire gracefully to give cleanup worker time to abort stale uploads
13
+ const CLEANUP_GRACE_SECONDS = 60 * 60 * 24;
14
+ const REDIS_TTL = UPLOAD_TTL_SECONDS + CLEANUP_GRACE_SECONDS;
15
+ const uploadMetaKey = (id) => `upload:${id}:meta`;
16
+ const uploadExpireKey = (id) => `upload:${id}:expires`;
17
+ const resumableKey = (key) => `resumable:${key}`;
18
+ function clearUploadTimeout(id) {
19
+ const timeout = uploadTimeouts.get(id);
20
+ if (timeout) {
21
+ clearTimeout(timeout);
22
+ uploadTimeouts.delete(id);
23
+ }
24
+ }
25
+ async function scheduleNonRedisCleanup(id, s3 = s3Client) {
26
+ if (config.redis)
27
+ return;
28
+ clearUploadTimeout(id);
29
+ const timeout = setTimeout(async () => {
30
+ try {
31
+ const meta = uploads.get(id);
32
+ if (!meta)
33
+ return;
34
+ try {
35
+ await s3.send(new AbortMultipartUploadCommand({
36
+ Bucket: meta.Bucket,
37
+ Key: meta.Key,
38
+ UploadId: id,
39
+ }));
40
+ }
41
+ catch (err) {
42
+ console.error("AbortMultipartUpload failed", {
43
+ uploadId: id,
44
+ Bucket: meta.Bucket,
45
+ Key: meta.Key,
46
+ error: err?.message,
47
+ });
48
+ logger.file("upload/abort/error", {
49
+ uploadId: id,
50
+ Bucket: meta.Bucket,
51
+ Key: meta.Key,
52
+ error: err?.message,
53
+ });
54
+ }
55
+ uploads.delete(id);
56
+ if (resumableUploads.get(meta.Key) === id) {
57
+ resumableUploads.delete(meta.Key);
58
+ }
59
+ }
60
+ catch (err) {
61
+ console.error(err);
62
+ logger.error(err);
63
+ }
64
+ finally {
65
+ clearUploadTimeout(id);
66
+ }
67
+ }, UPLOAD_TTL_SECONDS * 1000);
68
+ // don't keep node process alive
69
+ timeout.unref?.();
70
+ uploadTimeouts.set(id, timeout);
71
+ }
72
+ // used via onClose hook to avoid memory leaks
73
+ export async function cleanupNonRedisUploads(s3 = s3Client) {
74
+ if (config.redis)
75
+ return;
76
+ console.log("cleanupNonRedisUploads start");
77
+ const entries = [...uploads.entries()];
78
+ for (const [id, meta] of entries) {
79
+ try {
80
+ await s3.send(new AbortMultipartUploadCommand({
81
+ Bucket: meta.Bucket,
82
+ Key: meta.Key,
83
+ UploadId: id,
84
+ }));
85
+ }
86
+ catch (err) {
87
+ console.error("AbortMultipartUpload failed", {
88
+ uploadId: id,
89
+ Bucket: meta.Bucket,
90
+ Key: meta.Key,
91
+ error: err?.message,
92
+ });
93
+ logger.file("upload/abort/error", {
94
+ uploadId: id,
95
+ Bucket: meta.Bucket,
96
+ Key: meta.Key,
97
+ error: err?.message,
98
+ });
99
+ }
100
+ clearUploadTimeout(id);
101
+ uploads.delete(id);
102
+ if (resumableUploads.get(meta.Key) === id) {
103
+ resumableUploads.delete(meta.Key);
104
+ }
105
+ }
106
+ console.log("open unfinished multipart uploads closed", entries.length);
107
+ }
108
+ export async function saveUploadMeta(id, meta, ttl = UPLOAD_TTL_SECONDS) {
109
+ if (!config.redis)
110
+ return;
111
+ const expiresAt = Date.now() + UPLOAD_TTL_SECONDS * 1000;
112
+ await Promise.all([
113
+ rclient.set(uploadMetaKey(id), JSON.stringify(meta), "EX", REDIS_TTL),
114
+ rclient.set(uploadExpireKey(id), String(expiresAt), "EX", ttl),
115
+ rclient.set(resumableKey(meta.Key), id, "EX", ttl),
116
+ ]);
117
+ }
118
+ export async function refreshUploadTTL(id, ttl = UPLOAD_TTL_SECONDS, s3 = s3Client) {
119
+ if (!config.redis) {
120
+ await scheduleNonRedisCleanup(id, s3);
121
+ return;
122
+ }
123
+ const meta = await getUploadMeta(id);
124
+ if (!meta)
125
+ return;
126
+ const expiresAt = Date.now() + ttl * 1000;
127
+ await Promise.all([
128
+ rclient.expire(uploadMetaKey(id), ttl),
129
+ rclient.set(uploadExpireKey(id), String(expiresAt), "EX", ttl),
130
+ rclient.expire(resumableKey(meta.Key), ttl),
131
+ ]);
132
+ }
133
+ export async function deleteUploadMeta(id, key) {
134
+ clearUploadTimeout(id);
135
+ if (!config.redis) {
136
+ const meta = uploads.get(id);
137
+ uploads.delete(id);
138
+ const redisKey = key || meta?.Key;
139
+ if (redisKey && resumableUploads.get(redisKey) === id) {
140
+ resumableUploads.delete(redisKey);
141
+ }
142
+ return;
143
+ }
144
+ const redisKey = key || (await getUploadMeta(id))?.Key;
145
+ const keys = [uploadMetaKey(id), uploadExpireKey(id)];
146
+ if (redisKey) {
147
+ keys.push(resumableKey(redisKey));
148
+ }
149
+ await rclient.del(keys);
150
+ }
151
+ export async function getUploadMeta(id) {
152
+ if (!config.redis) {
153
+ return uploads.get(id);
154
+ }
155
+ const raw = await rclient.get(uploadMetaKey(id));
156
+ return raw ? JSON.parse(raw) : undefined;
157
+ }
158
+ export async function getResumableUploadId(key) {
159
+ if (!config.redis) {
160
+ return resumableUploads.get(key);
161
+ }
162
+ return rclient.get(resumableKey(key));
163
+ }
164
+ export async function cleanupStaleUploads(s3 = s3Client) {
165
+ if (!config.redis) {
166
+ await cleanupNonRedisUploads(s3);
167
+ return;
168
+ }
169
+ let cursor = "0";
170
+ do {
171
+ const [nextCursor, keys] = await rclient.scan(cursor, "MATCH", "upload:*:expires", "COUNT", 100);
172
+ cursor = nextCursor;
173
+ for (const expireKey of keys) {
174
+ try {
175
+ const expiresAt = Number(await rclient.get(expireKey));
176
+ if (!expiresAt || expiresAt > Date.now()) {
177
+ continue;
178
+ }
179
+ const uploadId = expireKey
180
+ .replace("upload:", "")
181
+ .replace(":expires", "");
182
+ const meta = await getUploadMeta(uploadId);
183
+ if (!meta) {
184
+ await rclient.del(expireKey);
185
+ continue;
186
+ }
187
+ const { Bucket, Key } = meta;
188
+ try {
189
+ await s3.send(new AbortMultipartUploadCommand({
190
+ Bucket,
191
+ Key,
192
+ UploadId: uploadId,
193
+ }));
194
+ }
195
+ catch (err) {
196
+ console.error("AbortMultipartUpload failed", {
197
+ uploadId,
198
+ Bucket,
199
+ Key,
200
+ error: err?.message,
201
+ });
202
+ logger.file("upload/abort/error", {
203
+ uploadId,
204
+ Bucket,
205
+ Key,
206
+ error: err?.message,
207
+ });
208
+ }
209
+ await deleteUploadMeta(uploadId, Key);
210
+ }
211
+ catch (err) {
212
+ console.error(err);
213
+ logger.error(err);
214
+ }
215
+ }
216
+ } while (cursor !== "0");
217
+ }
7
218
  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) || {};
219
+ const cachedInfo = await getUploadMeta(id);
220
+ const { Key, Bucket, fileSize: cacheSize, ContentType: cacheContentType, } = cachedInfo || {};
10
221
  const parts = await s3
11
222
  .send(new ListPartsCommand({
12
223
  Bucket,
13
224
  Key,
14
225
  UploadId: id,
15
226
  }))
16
- .catch((err) => null);
227
+ .catch(() => null);
17
228
  return {
18
229
  UploadId: parts ? id : undefined,
19
230
  Key,
20
231
  Bucket,
21
232
  fileSize: cacheSize,
22
233
  ContentType: cacheContentType,
23
- uploaded: parts.Parts?.reduce((sum, part) => sum + (part.Size || 0), 0) ||
24
- 0,
25
- parts: parts.Parts?.map((p) => ({
234
+ uploaded: parts?.Parts?.reduce((sum, part) => sum + (part.Size || 0), 0) || 0,
235
+ parts: parts?.Parts?.map((p) => ({
26
236
  ETag: p.ETag,
27
237
  PartNumber: p.PartNumber,
28
238
  })) || [],
29
239
  };
30
240
  }
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
241
  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) {
242
+ const cachedUploadId = await getResumableUploadId(Key);
243
+ const cachedMeta = cachedUploadId
244
+ ? await getUploadMeta(cachedUploadId)
245
+ : undefined;
246
+ if (cachedUploadId && cachedMeta) {
247
+ await refreshUploadTTL(cachedUploadId, UPLOAD_TTL_SECONDS, s3);
48
248
  return {
49
- Key: cacheKey,
50
- Bucket: cacheBucket,
51
- UploadId,
52
- fileSize: cacheSize,
53
- extension: cacheExtension,
54
- ContentType: cacheContentType,
249
+ Key,
250
+ Bucket: cachedMeta.Bucket,
251
+ UploadId: cachedUploadId,
252
+ fileSize: cachedMeta.fileSize,
253
+ extension: cachedMeta.extension,
254
+ ContentType: cachedMeta.ContentType,
55
255
  cache: true,
56
256
  };
57
257
  }
@@ -61,22 +261,27 @@ export async function startUpload({ Bucket, Key, ContentType, fileSize, }, s3 =
61
261
  ContentType,
62
262
  }));
63
263
  const extension = path.extname(Key).substring(1);
264
+ const meta = {
265
+ Key,
266
+ Bucket,
267
+ fileSize,
268
+ extension,
269
+ ContentType,
270
+ };
64
271
  if (config.redis) {
65
- await rclient.set(res.UploadId, JSON.stringify({ Key, Bucket, fileSize, extension, ContentType }), "EX", 60 * 60);
272
+ await saveUploadMeta(res.UploadId, meta);
66
273
  }
67
274
  else {
68
- uploads.set(res.UploadId, {
69
- Key,
70
- Bucket,
71
- fileSize,
72
- extension,
73
- ContentType,
74
- });
275
+ uploads.set(res.UploadId, meta);
276
+ resumableUploads.set(Key, res.UploadId);
277
+ await scheduleNonRedisCleanup(res.UploadId, s3);
75
278
  }
76
- return { UploadId: res.UploadId };
279
+ return {
280
+ UploadId: res.UploadId,
281
+ };
77
282
  }
78
283
  export async function uploadChunk({ id, chunk, partNumber, }, s3 = s3Client) {
79
- let upload = await findUpload({ id }, s3);
284
+ const upload = await findUpload({ id }, s3);
80
285
  if (!upload?.UploadId) {
81
286
  throw new Error("upload not found");
82
287
  }
@@ -90,17 +295,18 @@ export async function uploadChunk({ id, chunk, partNumber, }, s3 = s3Client) {
90
295
  PartNumber: partNumber,
91
296
  Body: chunk,
92
297
  }));
298
+ await refreshUploadTTL(id, UPLOAD_TTL_SECONDS, s3);
93
299
  return upload;
94
300
  }
95
301
  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) || {};
302
+ const redisCache = await getUploadMeta(id);
303
+ const { fileSize: cachedSize, Key, Bucket } = redisCache || {};
98
304
  const size = cachedSize ? Number(cachedSize) : undefined;
99
305
  const upload = await findUpload({ id }, s3);
100
306
  if (!upload?.uploaded) {
101
307
  throw new Error("File not uploaded");
102
308
  }
103
- if (size && size != upload.uploaded) {
309
+ if (size && size !== upload.uploaded) {
104
310
  throw new Error("File size mismatch");
105
311
  }
106
312
  await s3.send(new CompleteMultipartUploadCommand({
@@ -111,21 +317,42 @@ export async function finishUpload({ id, }, s3 = s3Client) {
111
317
  Parts: upload.parts.sort((a, b) => a.PartNumber - b.PartNumber),
112
318
  },
113
319
  }));
114
- // if (config.redis) await rclient.del(Key);
115
- return { uploaded: upload.uploaded };
320
+ await deleteUploadMeta(id, Key);
321
+ return {
322
+ uploaded: upload.uploaded,
323
+ };
324
+ }
325
+ export async function abortUpload({ id, }, s3 = s3Client) {
326
+ const meta = await getUploadMeta(id);
327
+ if (!meta) {
328
+ return {
329
+ aborted: false,
330
+ };
331
+ }
332
+ await s3.send(new AbortMultipartUploadCommand({
333
+ Bucket: meta.Bucket,
334
+ Key: meta.Key,
335
+ UploadId: id,
336
+ }));
337
+ await deleteUploadMeta(id, meta.Key);
338
+ return {
339
+ aborted: true,
340
+ };
116
341
  }
117
342
  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) || {};
343
+ const redisCache = id ? await getUploadMeta(id) : undefined;
344
+ const { fileSize: cachedSize, Key: redisKey, Bucket: redisBucket, } = redisCache || {};
121
345
  if (!redisKey && Key && Bucket) {
122
346
  const res = await s3
123
347
  .send(new HeadObjectCommand({
124
348
  Bucket,
125
349
  Key,
126
350
  }))
127
- .catch((err) => false);
128
- return { exists: !!res, uploaded: res?.ContentLength };
351
+ .catch(() => false);
352
+ return {
353
+ exists: !!res,
354
+ uploaded: res?.ContentLength,
355
+ };
129
356
  }
130
357
  const parts = await s3
131
358
  .send(new ListPartsCommand({
@@ -133,7 +360,7 @@ export async function getUploadStatus({ id, Key, Bucket, }, s3 = s3Client) {
133
360
  Key: redisKey,
134
361
  UploadId: id,
135
362
  }))
136
- .catch((err) => ({
363
+ .catch(() => ({
137
364
  Parts: [],
138
365
  }));
139
366
  const res = id && !parts.Parts?.length
@@ -142,17 +369,17 @@ export async function getUploadStatus({ id, Key, Bucket, }, s3 = s3Client) {
142
369
  Bucket: redisBucket,
143
370
  Key: redisKey,
144
371
  }))
145
- .catch((err) => false)
372
+ .catch(() => false)
146
373
  : false;
147
374
  return {
148
- exists: !!res || !!parts.Parts?.length,
375
+ exists: !!res || !!parts?.Parts?.length,
149
376
  finished: !!res,
150
377
  uploaded: res
151
378
  ? res.ContentLength
152
- : parts.Parts?.reduce((acc, curr) => acc + (curr.Size || 0), 0) || 0,
379
+ : parts?.Parts?.reduce((acc, curr) => acc + (curr.Size || 0), 0) || 0,
153
380
  id,
154
381
  size: cachedSize ? Number(cachedSize) : undefined,
155
- parts: parts.Parts?.map((p) => ({
382
+ parts: parts?.Parts?.map((p) => ({
156
383
  ETag: p.ETag,
157
384
  PartNumber: p.PartNumber,
158
385
  })) || [],
@@ -1 +1 @@
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
+ {"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,gBA+EA"}
@@ -20,18 +20,19 @@ export default async function startUpload({ host, id, fileName, size, subdir, })
20
20
  .join(uploadChunkDirectory, subdir || "")
21
21
  .replace(/\\/g, "/");
22
22
  if (config.s3) {
23
- const filepath = path.posix.join(relativeDirpath, `${id1}.${extension}`);
23
+ const filepath = path.posix.join(config.folder || '', relativeDirpath, `${id1}.${extension}`);
24
24
  const { UploadId } = await startUploadS3({
25
25
  Bucket: config.s3?.containerName || "work",
26
26
  Key: filepath[0] === "/" ? filepath?.slice(1) : filepath,
27
27
  ContentType: mimes[extension],
28
28
  fileSize: size,
29
29
  });
30
- return { UploadId, provider: "s3" };
30
+ return { id: UploadId, UploadId, provider: "s3" };
31
31
  }
32
32
  const key = id1.split("-").pop();
33
33
  const meta = {
34
34
  id: id1,
35
+ UploadId: id1,
35
36
  key,
36
37
  fileName,
37
38
  size,
@@ -56,6 +57,7 @@ export default async function startUpload({ host, id, fileName, size, subdir, })
56
57
  subdir,
57
58
  metaPath: undefined,
58
59
  relativeDirpath,
60
+ provider: "fs"
59
61
  };
60
62
  await writeFile(path.join(metaDir, `${id1}.json`), JSON.stringify(metaData, null, 2));
61
63
  return { ...meta, metaPath: undefined };
@@ -1 +1 @@
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"}
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,gBAiIA"}
@@ -78,7 +78,9 @@ export default async function uploadChunk({ host, id, body, offset, end, size, }
78
78
  }
79
79
  const url = `${host}/${prefix}/${id}`;
80
80
  const range = `bytes ${offset || 0}-${end}/${size}`;
81
- console.log("PATCH request", url, range, body.length);
81
+ if (config.trace || process.env.NODE_ENV === "test") {
82
+ console.log("PATCH request", url, range, body.length);
83
+ }
82
84
  const res = await fetch(url, {
83
85
  method: "PATCH",
84
86
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [