@lenne.tech/nest-server 11.42.12 → 11.42.13

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 (32) hide show
  1. package/.claude/rules/testing.md +3 -3
  2. package/FRAMEWORK-API.md +1 -1
  3. package/dist/core/modules/error-code/error-codes.d.ts +45 -0
  4. package/dist/core/modules/error-code/error-codes.js +40 -0
  5. package/dist/core/modules/error-code/error-codes.js.map +1 -1
  6. package/dist/core/modules/file/core-file.service.d.ts +1 -0
  7. package/dist/core/modules/file/core-file.service.js +27 -0
  8. package/dist/core/modules/file/core-file.service.js.map +1 -1
  9. package/dist/core/modules/tenant/core-tenant.guard.js +7 -7
  10. package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
  11. package/dist/core/modules/tus/core-tus.service.d.ts +1 -0
  12. package/dist/core/modules/tus/core-tus.service.js +10 -4
  13. package/dist/core/modules/tus/core-tus.service.js.map +1 -1
  14. package/dist/core/modules/tus/tus.constants.d.ts +1 -0
  15. package/dist/core/modules/tus/tus.constants.js +2 -1
  16. package/dist/core/modules/tus/tus.constants.js.map +1 -1
  17. package/dist/server/modules/error-code/error-codes.d.ts +5 -0
  18. package/dist/tsconfig.build.tsbuildinfo +1 -1
  19. package/docs/REQUEST-LIFECYCLE.md +1 -1
  20. package/docs/error-codes.md +70 -0
  21. package/migration-guides/11.42.12-to-11.42.13.md +154 -0
  22. package/package.json +2 -2
  23. package/src/core/modules/error-code/error-codes.ts +49 -0
  24. package/src/core/modules/file/README.md +1 -0
  25. package/src/core/modules/file/core-file.service.ts +39 -0
  26. package/src/core/modules/tenant/INTEGRATION-CHECKLIST.md +2 -2
  27. package/src/core/modules/tenant/README.md +17 -1
  28. package/src/core/modules/tenant/core-tenant.guard.ts +7 -7
  29. package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +2 -1
  30. package/src/core/modules/tus/README.md +42 -3
  31. package/src/core/modules/tus/core-tus.service.ts +32 -6
  32. package/src/core/modules/tus/tus.constants.ts +11 -0
@@ -21,7 +21,7 @@ import {
21
21
  FilesystemFileHelper,
22
22
  } from '../file/filesystem-file.helper';
23
23
  import { S3_FILES_COLLECTION, S3FileHelper } from '../file/s3-file.helper';
24
- import { TUS_OWNER_METADATA_KEY, TUS_TENANT_METADATA_KEY } from './tus.constants';
24
+ import { TUS_FILE_ID_HEADER, TUS_OWNER_METADATA_KEY, TUS_TENANT_METADATA_KEY } from './tus.constants';
25
25
  import { TusRedisLocker } from './tus-redis-locker';
26
26
  import {
27
27
  DEFAULT_TUS_ALLOWED_HEADERS,
@@ -86,6 +86,17 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
86
86
  private files: mongo.GridFSBucket;
87
87
  private cleanupInterval: NodeJS.Timeout | null = null;
88
88
 
89
+ /**
90
+ * The ID of the stored file per finished upload, recorded by {@link CoreTusService.onUploadComplete}
91
+ * and read by `onUploadFinish` for the `X-File-Id` response header.
92
+ *
93
+ * Recorded on the side rather than returned, so the hook keeps its `Promise<void>` signature: a
94
+ * project override in either documented shape — `await super.onUploadComplete(upload)` or
95
+ * `return super.onUploadComplete(upload)` — still compiles AND still yields the header. Keyed by
96
+ * the `Upload` object `onUploadFinish` hands to the hook, so nothing outlives the request.
97
+ */
98
+ private readonly storedFileIds = new WeakMap<Upload, string>();
99
+
89
100
  /** Datastore of the TUS server; set when uploads are staged in S3 */
90
101
  protected s3Store: any = null;
91
102
 
@@ -183,10 +194,15 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
183
194
  }
184
195
 
185
196
  /**
186
- * Handle upload completion - migrate to GridFS
197
+ * Handle upload completion - migrate the finished upload into the configured file storage
187
198
  *
188
199
  * This method can be overridden in extending services to customize
189
200
  * what happens after an upload completes.
201
+ *
202
+ * Records the ID of the stored file, which the completing response sends to the client as
203
+ * `X-File-Id` (`TUS_FILE_ID_HEADER`). An override must call `super.onUploadComplete(upload)` with
204
+ * the `upload` it received; one that does not stores nothing here and sends no header — the client
205
+ * then knows the TUS URL alone, whose last segment is the upload id, not the file id.
190
206
  */
191
207
  protected async onUploadComplete(upload: Upload): Promise<void> {
192
208
  try {
@@ -231,6 +247,7 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
231
247
  `Upload ${upload.id} copied inside S3 to ${fileInfo._id} (filename: ${filename}) — no bytes through this process`,
232
248
  );
233
249
  await this.deleteStagedUpload(upload.id);
250
+ this.storedFileIds.set(upload, String(fileInfo._id));
234
251
  return;
235
252
  }
236
253
 
@@ -239,15 +256,16 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
239
256
  return;
240
257
  }
241
258
 
259
+ let fileInfo: { _id: unknown };
242
260
  if (this.fileStorageDriver === 'filesystem') {
243
- const fileInfo = await FilesystemFileHelper.writeFile(
261
+ fileInfo = await FilesystemFileHelper.writeFile(
244
262
  this.fileStorageDir,
245
263
  this.connection.db.collection(FILESYSTEM_FILES_COLLECTION),
246
264
  { body: readStream, contentType, filename, metadata: fileMetadata },
247
265
  );
248
266
  this.logger.debug(`Upload ${upload.id} migrated to the filesystem as ${fileInfo._id} (filename: ${filename})`);
249
267
  } else if (this.s3FileStorage) {
250
- const fileInfo = await S3FileHelper.writeFile(
268
+ fileInfo = await S3FileHelper.writeFile(
251
269
  this.options.s3Service,
252
270
  this.connection.db.collection(S3_FILES_COLLECTION),
253
271
  // Streamed with the length TUS already knows, so a multi-GB resumable upload is never
@@ -256,7 +274,7 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
256
274
  );
257
275
  this.logger.debug(`Upload ${upload.id} migrated to S3 as ${fileInfo._id} (filename: ${filename})`);
258
276
  } else {
259
- const fileInfo = await GridFSHelper.writeFileFromStream(this.files, readStream, {
277
+ fileInfo = await GridFSHelper.writeFileFromStream(this.files, readStream, {
260
278
  contentType,
261
279
  filename,
262
280
  metadata: fileMetadata,
@@ -266,6 +284,7 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
266
284
 
267
285
  // Clean up the staged upload
268
286
  await this.deleteStagedUpload(upload.id);
287
+ this.storedFileIds.set(upload, String(fileInfo._id));
269
288
  } catch (error) {
270
289
  this.logger.error(`Failed to migrate upload ${upload.id}: ${error.message}`);
271
290
  throw error;
@@ -837,6 +856,9 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
837
856
  // which a browser refuses on a credentialed (cookie) request — see resolveCorsOrigins().
838
857
  ...(cors ? { allowedCredentials: true, allowedOrigins: cors } : {}),
839
858
  datastore,
859
+ // Appended to the tus protocol headers in `Access-Control-Expose-Headers`: a browser on a
860
+ // separate app origin cannot read a response header that is not listed there.
861
+ exposedHeaders: [TUS_FILE_ID_HEADER],
840
862
  ...(locker ? { locker } : {}),
841
863
  maxSize: this.config.maxSize,
842
864
  // Every request that names an upload — HEAD (offset), PATCH (append bytes), DELETE
@@ -864,10 +886,14 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
864
886
  const relative = this.config.relativeLocation === true || (server as any)?.options?.relativeLocation === true;
865
887
  return relative ? url : `${proto}://${host}${url}`;
866
888
  },
889
+ // The response that completes the upload names the stored file. Without it the client knows
890
+ // only the TUS URL, whose last segment is the upload id — a staging key `/files/id/` answers
891
+ // with 404. An override of onUploadComplete() that never calls super records nothing: no header.
867
892
  onUploadFinish: async (_req, upload) => {
868
893
  try {
869
894
  await this.onUploadComplete(upload);
870
- return {};
895
+ const fileId = this.storedFileIds.get(upload);
896
+ return fileId ? { headers: { [TUS_FILE_ID_HEADER]: fileId } } : {};
871
897
  } catch (error) {
872
898
  // Do NOT swallow this. `onUploadComplete` is what moves the finished upload into its
873
899
  // permanent store; returning `{}` after it failed answers the client with a 204 that
@@ -51,3 +51,14 @@ export const TUS_OWNER_METADATA_KEY = 'ltOwnerId';
51
51
  * resolves to a tenant they are an active member of; otherwise it is `null`.
52
52
  */
53
53
  export const TUS_TENANT_METADATA_KEY = 'ltTenantId';
54
+
55
+ /**
56
+ * Response header naming the ID of the stored file, sent on the request that COMPLETES a tus upload
57
+ * (the last PATCH, or the POST of a creation-with-upload).
58
+ *
59
+ * Without it a client knows only the TUS URL, whose last segment is the tus UPLOAD id — a key of the
60
+ * staging store, which `/files/id/<id>` answers with 404. The header is also listed in
61
+ * `Access-Control-Expose-Headers`, because a browser on a separate app origin cannot read it
62
+ * otherwise.
63
+ */
64
+ export const TUS_FILE_ID_HEADER = 'X-File-Id';