@ddd-ts-kit/kernel 0.8.0 → 0.9.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 (245) hide show
  1. package/README.md +69 -0
  2. package/dist/application/files/accepts-media-type.d.ts +3 -0
  3. package/dist/application/files/accepts-media-type.d.ts.map +1 -0
  4. package/dist/application/files/accepts-media-type.js +11 -0
  5. package/dist/application/files/accepts-media-type.js.map +1 -0
  6. package/dist/application/files/assert-expiry.d.ts +3 -0
  7. package/dist/application/files/assert-expiry.d.ts.map +1 -0
  8. package/dist/application/files/assert-expiry.js +10 -0
  9. package/dist/application/files/assert-expiry.js.map +1 -0
  10. package/dist/application/files/assert-file-key.d.ts +3 -0
  11. package/dist/application/files/assert-file-key.d.ts.map +1 -0
  12. package/dist/application/files/assert-file-key.js +10 -0
  13. package/dist/application/files/assert-file-key.js.map +1 -0
  14. package/dist/application/files/assert-final-key.d.ts +3 -0
  15. package/dist/application/files/assert-final-key.d.ts.map +1 -0
  16. package/dist/application/files/assert-final-key.js +11 -0
  17. package/dist/application/files/assert-final-key.js.map +1 -0
  18. package/dist/application/files/content-disposition.d.ts +3 -0
  19. package/dist/application/files/content-disposition.d.ts.map +1 -0
  20. package/dist/application/files/content-disposition.js +9 -0
  21. package/dist/application/files/content-disposition.js.map +1 -0
  22. package/dist/application/files/content-expectation.d.ts +6 -0
  23. package/dist/application/files/content-expectation.d.ts.map +1 -0
  24. package/dist/application/files/content-expectation.js +2 -0
  25. package/dist/application/files/content-expectation.js.map +1 -0
  26. package/dist/application/files/content-matches.d.ts +7 -0
  27. package/dist/application/files/content-matches.d.ts.map +1 -0
  28. package/dist/application/files/content-matches.js +39 -0
  29. package/dist/application/files/content-matches.js.map +1 -0
  30. package/dist/application/files/download-url-options.d.ts +7 -0
  31. package/dist/application/files/download-url-options.d.ts.map +1 -0
  32. package/dist/application/files/download-url-options.js +2 -0
  33. package/dist/application/files/download-url-options.js.map +1 -0
  34. package/dist/application/files/file-content.d.ts +8 -0
  35. package/dist/application/files/file-content.d.ts.map +1 -0
  36. package/dist/application/files/file-content.js +2 -0
  37. package/dist/application/files/file-content.js.map +1 -0
  38. package/dist/application/files/file-error.d.ts +28 -0
  39. package/dist/application/files/file-error.d.ts.map +1 -0
  40. package/dist/application/files/file-error.js +29 -0
  41. package/dist/application/files/file-error.js.map +1 -0
  42. package/dist/application/files/file-metadata.d.ts +10 -0
  43. package/dist/application/files/file-metadata.d.ts.map +1 -0
  44. package/dist/application/files/file-metadata.js +2 -0
  45. package/dist/application/files/file-metadata.js.map +1 -0
  46. package/dist/application/files/file-source.d.ts +4 -0
  47. package/dist/application/files/file-source.d.ts.map +1 -0
  48. package/dist/application/files/file-source.js +2 -0
  49. package/dist/application/files/file-source.js.map +1 -0
  50. package/dist/application/files/file-upload.d.ts +25 -0
  51. package/dist/application/files/file-upload.d.ts.map +1 -0
  52. package/dist/application/files/file-upload.js +36 -0
  53. package/dist/application/files/file-upload.js.map +1 -0
  54. package/dist/application/files/index.d.ts +31 -0
  55. package/dist/application/files/index.d.ts.map +1 -0
  56. package/dist/application/files/index.js +31 -0
  57. package/dist/application/files/index.js.map +1 -0
  58. package/dist/application/files/inspection-limits.d.ts +8 -0
  59. package/dist/application/files/inspection-limits.d.ts.map +1 -0
  60. package/dist/application/files/inspection-limits.js +2 -0
  61. package/dist/application/files/inspection-limits.js.map +1 -0
  62. package/dist/application/files/is-file-key.d.ts +7 -0
  63. package/dist/application/files/is-file-key.d.ts.map +1 -0
  64. package/dist/application/files/is-file-key.js +11 -0
  65. package/dist/application/files/is-file-key.js.map +1 -0
  66. package/dist/application/files/listed-file.d.ts +7 -0
  67. package/dist/application/files/listed-file.d.ts.map +1 -0
  68. package/dist/application/files/listed-file.js +2 -0
  69. package/dist/application/files/listed-file.js.map +1 -0
  70. package/dist/application/files/max-signed-url-seconds.d.ts +3 -0
  71. package/dist/application/files/max-signed-url-seconds.d.ts.map +1 -0
  72. package/dist/application/files/max-signed-url-seconds.js +3 -0
  73. package/dist/application/files/max-signed-url-seconds.js.map +1 -0
  74. package/dist/application/files/media-type-of.d.ts +3 -0
  75. package/dist/application/files/media-type-of.d.ts.map +1 -0
  76. package/dist/application/files/media-type-of.js +5 -0
  77. package/dist/application/files/media-type-of.js.map +1 -0
  78. package/dist/application/files/pending-upload-link.d.ts +10 -0
  79. package/dist/application/files/pending-upload-link.d.ts.map +1 -0
  80. package/dist/application/files/pending-upload-link.js +2 -0
  81. package/dist/application/files/pending-upload-link.js.map +1 -0
  82. package/dist/application/files/pending-upload.d.ts +15 -0
  83. package/dist/application/files/pending-upload.d.ts.map +1 -0
  84. package/dist/application/files/pending-upload.js +2 -0
  85. package/dist/application/files/pending-upload.js.map +1 -0
  86. package/dist/application/files/put-options.d.ts +7 -0
  87. package/dist/application/files/put-options.d.ts.map +1 -0
  88. package/dist/application/files/put-options.js +2 -0
  89. package/dist/application/files/put-options.js.map +1 -0
  90. package/dist/application/files/received-file.d.ts +15 -0
  91. package/dist/application/files/received-file.d.ts.map +1 -0
  92. package/dist/application/files/received-file.js +2 -0
  93. package/dist/application/files/received-file.js.map +1 -0
  94. package/dist/application/files/scan-verdict.d.ts +8 -0
  95. package/dist/application/files/scan-verdict.d.ts.map +1 -0
  96. package/dist/application/files/scan-verdict.js +2 -0
  97. package/dist/application/files/scan-verdict.js.map +1 -0
  98. package/dist/application/files/signature-of.d.ts +3 -0
  99. package/dist/application/files/signature-of.d.ts.map +1 -0
  100. package/dist/application/files/signature-of.js +28 -0
  101. package/dist/application/files/signature-of.js.map +1 -0
  102. package/dist/application/files/signed-download.d.ts +6 -0
  103. package/dist/application/files/signed-download.d.ts.map +1 -0
  104. package/dist/application/files/signed-download.js +2 -0
  105. package/dist/application/files/signed-download.js.map +1 -0
  106. package/dist/application/files/signed-upload.d.ts +9 -0
  107. package/dist/application/files/signed-upload.d.ts.map +1 -0
  108. package/dist/application/files/signed-upload.js +2 -0
  109. package/dist/application/files/signed-upload.js.map +1 -0
  110. package/dist/application/files/signed-url.d.ts +6 -0
  111. package/dist/application/files/signed-url.d.ts.map +1 -0
  112. package/dist/application/files/signed-url.js +2 -0
  113. package/dist/application/files/signed-url.js.map +1 -0
  114. package/dist/application/files/staged-key-of.d.ts +5 -0
  115. package/dist/application/files/staged-key-of.d.ts.map +1 -0
  116. package/dist/application/files/staged-key-of.js +7 -0
  117. package/dist/application/files/staged-key-of.js.map +1 -0
  118. package/dist/application/files/stored-file.d.ts +16 -0
  119. package/dist/application/files/stored-file.d.ts.map +1 -0
  120. package/dist/application/files/stored-file.js +2 -0
  121. package/dist/application/files/stored-file.js.map +1 -0
  122. package/dist/application/files/upload-url-options.d.ts +12 -0
  123. package/dist/application/files/upload-url-options.d.ts.map +1 -0
  124. package/dist/application/files/upload-url-options.js +2 -0
  125. package/dist/application/files/upload-url-options.js.map +1 -0
  126. package/dist/application/ports/file-keys.d.ts +9 -0
  127. package/dist/application/ports/file-keys.d.ts.map +1 -0
  128. package/dist/application/ports/file-keys.js +10 -0
  129. package/dist/application/ports/file-keys.js.map +1 -0
  130. package/dist/application/ports/file-scanner.d.ts +13 -0
  131. package/dist/application/ports/file-scanner.d.ts.map +1 -0
  132. package/dist/application/ports/file-scanner.js +12 -0
  133. package/dist/application/ports/file-scanner.js.map +1 -0
  134. package/dist/application/ports/file-storage.d.ts +40 -0
  135. package/dist/application/ports/file-storage.d.ts.map +1 -0
  136. package/dist/application/ports/file-storage.js +15 -0
  137. package/dist/application/ports/file-storage.js.map +1 -0
  138. package/dist/application/ports/served-file-storage.d.ts +16 -0
  139. package/dist/application/ports/served-file-storage.d.ts.map +1 -0
  140. package/dist/application/ports/served-file-storage.js +12 -0
  141. package/dist/application/ports/served-file-storage.js.map +1 -0
  142. package/dist/index.d.ts +20 -0
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +20 -0
  145. package/dist/index.js.map +1 -1
  146. package/dist/infrastructure/domain-events/after-commit.d.ts +10 -0
  147. package/dist/infrastructure/domain-events/after-commit.d.ts.map +1 -0
  148. package/dist/infrastructure/domain-events/after-commit.js +19 -0
  149. package/dist/infrastructure/domain-events/after-commit.js.map +1 -0
  150. package/dist/infrastructure/domain-events/at-commit.d.ts +11 -0
  151. package/dist/infrastructure/domain-events/at-commit.d.ts.map +1 -0
  152. package/dist/infrastructure/domain-events/at-commit.js +19 -0
  153. package/dist/infrastructure/domain-events/at-commit.js.map +1 -0
  154. package/dist/infrastructure/domain-events/commit-step.d.ts +10 -0
  155. package/dist/infrastructure/domain-events/commit-step.d.ts.map +1 -0
  156. package/dist/infrastructure/domain-events/commit-step.js +2 -0
  157. package/dist/infrastructure/domain-events/commit-step.js.map +1 -0
  158. package/dist/infrastructure/domain-events/unit-of-work-scope.d.ts +5 -0
  159. package/dist/infrastructure/domain-events/unit-of-work-scope.d.ts.map +1 -1
  160. package/dist/infrastructure/domain-events/unit-of-work-scope.js.map +1 -1
  161. package/dist/infrastructure/domain-events/within-unit-of-work.d.ts +3 -1
  162. package/dist/infrastructure/domain-events/within-unit-of-work.d.ts.map +1 -1
  163. package/dist/infrastructure/domain-events/within-unit-of-work.js +32 -5
  164. package/dist/infrastructure/domain-events/within-unit-of-work.js.map +1 -1
  165. package/dist/infrastructure/files/accept-every-file-scanner.d.ts +7 -0
  166. package/dist/infrastructure/files/accept-every-file-scanner.d.ts.map +1 -0
  167. package/dist/infrastructure/files/accept-every-file-scanner.js +7 -0
  168. package/dist/infrastructure/files/accept-every-file-scanner.js.map +1 -0
  169. package/dist/infrastructure/files/collect.d.ts +4 -0
  170. package/dist/infrastructure/files/collect.d.ts.map +1 -0
  171. package/dist/infrastructure/files/collect.js +11 -0
  172. package/dist/infrastructure/files/collect.js.map +1 -0
  173. package/dist/infrastructure/files/file-grant.d.ts +11 -0
  174. package/dist/infrastructure/files/file-grant.d.ts.map +1 -0
  175. package/dist/infrastructure/files/file-grant.js +2 -0
  176. package/dist/infrastructure/files/file-grant.js.map +1 -0
  177. package/dist/infrastructure/files/file-inspection.d.ts +22 -0
  178. package/dist/infrastructure/files/file-inspection.d.ts.map +1 -0
  179. package/dist/infrastructure/files/file-inspection.js +66 -0
  180. package/dist/infrastructure/files/file-inspection.js.map +1 -0
  181. package/dist/infrastructure/files/file-staging.d.ts +59 -0
  182. package/dist/infrastructure/files/file-staging.d.ts.map +1 -0
  183. package/dist/infrastructure/files/file-staging.js +142 -0
  184. package/dist/infrastructure/files/file-staging.js.map +1 -0
  185. package/dist/infrastructure/files/file-uploads-middleware.d.ts +11 -0
  186. package/dist/infrastructure/files/file-uploads-middleware.d.ts.map +1 -0
  187. package/dist/infrastructure/files/file-uploads-middleware.js +25 -0
  188. package/dist/infrastructure/files/file-uploads-middleware.js.map +1 -0
  189. package/dist/infrastructure/files/in-memory-file-storage.d.ts +25 -0
  190. package/dist/infrastructure/files/in-memory-file-storage.d.ts.map +1 -0
  191. package/dist/infrastructure/files/in-memory-file-storage.js +73 -0
  192. package/dist/infrastructure/files/in-memory-file-storage.js.map +1 -0
  193. package/dist/infrastructure/files/is-file-grant.d.ts +3 -0
  194. package/dist/infrastructure/files/is-file-grant.d.ts.map +1 -0
  195. package/dist/infrastructure/files/is-file-grant.js +16 -0
  196. package/dist/infrastructure/files/is-file-grant.js.map +1 -0
  197. package/dist/infrastructure/files/is-stored-metadata.d.ts +3 -0
  198. package/dist/infrastructure/files/is-stored-metadata.d.ts.map +1 -0
  199. package/dist/infrastructure/files/is-stored-metadata.js +8 -0
  200. package/dist/infrastructure/files/is-stored-metadata.js.map +1 -0
  201. package/dist/infrastructure/files/local-file-storage-options.d.ts +6 -0
  202. package/dist/infrastructure/files/local-file-storage-options.d.ts.map +1 -0
  203. package/dist/infrastructure/files/local-file-storage-options.js +2 -0
  204. package/dist/infrastructure/files/local-file-storage-options.js.map +1 -0
  205. package/dist/infrastructure/files/local-file-storage.d.ts +29 -0
  206. package/dist/infrastructure/files/local-file-storage.d.ts.map +1 -0
  207. package/dist/infrastructure/files/local-file-storage.js +103 -0
  208. package/dist/infrastructure/files/local-file-storage.js.map +1 -0
  209. package/dist/infrastructure/files/local-paths.d.ts +12 -0
  210. package/dist/infrastructure/files/local-paths.d.ts.map +1 -0
  211. package/dist/infrastructure/files/local-paths.js +33 -0
  212. package/dist/infrastructure/files/local-paths.js.map +1 -0
  213. package/dist/infrastructure/files/purge-staged-files.d.ts +16 -0
  214. package/dist/infrastructure/files/purge-staged-files.d.ts.map +1 -0
  215. package/dist/infrastructure/files/purge-staged-files.js +21 -0
  216. package/dist/infrastructure/files/purge-staged-files.js.map +1 -0
  217. package/dist/infrastructure/files/self-signed-file-storage.d.ts +23 -0
  218. package/dist/infrastructure/files/self-signed-file-storage.d.ts.map +1 -0
  219. package/dist/infrastructure/files/self-signed-file-storage.js +57 -0
  220. package/dist/infrastructure/files/self-signed-file-storage.js.map +1 -0
  221. package/dist/infrastructure/files/signed-file-tokens.d.ts +20 -0
  222. package/dist/infrastructure/files/signed-file-tokens.d.ts.map +1 -0
  223. package/dist/infrastructure/files/signed-file-tokens.js +45 -0
  224. package/dist/infrastructure/files/signed-file-tokens.js.map +1 -0
  225. package/dist/infrastructure/files/signed-link-options.d.ts +10 -0
  226. package/dist/infrastructure/files/signed-link-options.d.ts.map +1 -0
  227. package/dist/infrastructure/files/signed-link-options.js +2 -0
  228. package/dist/infrastructure/files/signed-link-options.js.map +1 -0
  229. package/dist/infrastructure/files/stored-metadata.d.ts +6 -0
  230. package/dist/infrastructure/files/stored-metadata.d.ts.map +1 -0
  231. package/dist/infrastructure/files/stored-metadata.js +2 -0
  232. package/dist/infrastructure/files/stored-metadata.js.map +1 -0
  233. package/dist/infrastructure/files/tenant-file-keys.d.ts +20 -0
  234. package/dist/infrastructure/files/tenant-file-keys.d.ts.map +1 -0
  235. package/dist/infrastructure/files/tenant-file-keys.js +27 -0
  236. package/dist/infrastructure/files/tenant-file-keys.js.map +1 -0
  237. package/dist/infrastructure/files/upload-desk.d.ts +8 -0
  238. package/dist/infrastructure/files/upload-desk.d.ts.map +1 -0
  239. package/dist/infrastructure/files/upload-desk.js +2 -0
  240. package/dist/infrastructure/files/upload-desk.js.map +1 -0
  241. package/dist/infrastructure/files/without-trailing-slashes.d.ts +3 -0
  242. package/dist/infrastructure/files/without-trailing-slashes.d.ts.map +1 -0
  243. package/dist/infrastructure/files/without-trailing-slashes.js +9 -0
  244. package/dist/infrastructure/files/without-trailing-slashes.js.map +1 -0
  245. package/package.json +1 -1
package/README.md CHANGED
@@ -735,6 +735,75 @@ the guard of an invariant: what must not happen twice is refused by a database c
735
735
  `Clock`: the fake, and enough for a single instance. The adapter between processes is in
736
736
  `@ddd-ts-kit/redis`. `acquiring(attempt, waitMs, waiting)` is the retry loop an adapter shares.
737
737
 
738
+ ### FileStorage, StoredFile, FileScanner
739
+
740
+ ```ts
741
+ // The command wraps what the controller received: the bus stages it before the transaction opens.
742
+ this.document = new FileUpload(received, 'appointment-documents');
743
+
744
+ // The handler, inside the unit of work:
745
+ const { file, fileName } = command.document; // a StoredFile, staged and scanned
746
+ appointment.attachDocument(unwrap(AttachedDocument.create(file, fileName, now)), now);
747
+ await appointments.save(appointment);
748
+ await staging.promoteOnCommit(file.key); // moved to its key with the commit
749
+ ```
750
+
751
+ `FileStorage` holds the content of files, never the database: `put` (a stream in, measured and
752
+ hashed, refused with `FILE_TOO_LARGE` beyond `maxBytes`, leaving nothing), `get` (a stream out),
753
+ `stat`, `delete`, `copy`, `move`, `list(prefix)`, and short signed links, `signedDownloadUrl` and
754
+ `signedUploadUrl` (at most `MAX_SIGNED_URL_SECONDS`, one hour). No method takes or returns a
755
+ whole file. A key is checked by `isFileKey` (segments of letters, digits, `.`, `-` and `_`, none
756
+ starting with a dot) before anything is touched, and `FileKeys` generates it on the server:
757
+ `TenantFileKeys(ids, clock, scope)` writes `tenants/<tenant>/<area>/<year>/<month>/<id>`, the
758
+ tenant segment only when the work in progress names one (the tenant id as it is: the kernel
759
+ refuses any other than letters, digits, `-` and `_`, so two tenants never share a prefix). The links of
760
+ `signedUploadUrl` carry a `maxBytes`, which the storages the application serves enforce.
761
+
762
+ `StoredFile` is what an aggregate keeps: `key`, `size`, `mediaType`, `checksum` (`sha256:<hex>`),
763
+ never the provider. `FileScanner` is the hook of an antivirus or a content check, called on the
764
+ staged file before it is accepted; `AcceptEveryFileScanner` is the one of a project that has
765
+ none yet. `FileError` carries the refusals: `FILE_TOO_LARGE`, `UNSUPPORTED_MEDIA_TYPE`,
766
+ `FILE_NOT_FOUND`, `INVALID_FILE_KEY`, `FILE_REJECTED`, `INVALID_FILE_URL_EXPIRY`.
767
+
768
+ A write to the storage is not rolled back with a transaction. `FileStaging(storage, scanner,
769
+ reporter)` gives the order that keeps both consistent, each step once:
770
+
771
+ - `stage(key, content, options)` writes under `staging/<key>` and scans; a refused file is
772
+ removed (`FILE_REJECTED`). A file from a client travels in its command as a `FileUpload`
773
+ (the `ReceivedFile` and the area of its key), staged by `fileUploadsMiddleware(desk)`, a bus
774
+ middleware placed after the permission and before the unit of work, so no transaction waits
775
+ on an upload. `upload.file` is the staged `StoredFile`.
776
+ - `promoteOnCommit(key)` moves the file to its key as the last step of the unit of work
777
+ (`atCommit`, a `CommitStep` of `apply` and `undo`): a promotion that fails rolls the command
778
+ back, and a transaction that rolls back after it returns the file to `staging/`. A committed
779
+ row never points into `staging/`.
780
+ - `deleteAfterCommit(key)` deletes, once committed, a file the aggregate no longer references
781
+ (`afterCommit`, at most once: a failure or a crash leaves an unreferenced file, reported).
782
+ - `purge(olderThan)` removes the staged files older than the instant, all orphans; the job
783
+ `PurgeStagedFiles` runs it (the platform registers it as `files.purge-staged`, every hour,
784
+ for files staged a day ago or more).
785
+
786
+ For a large file the client uploads straight to the storage: `uploadUrl(key, { expiresInSeconds,
787
+ mediaType, accepted, maxBytes })` answers the `link` for the client and the `pending` grant
788
+ (`PendingUpload`: key, size cap, media types) that the command records on the aggregate;
789
+ `confirm(pending)`, called by a command that reads the grant back from the aggregate, never
790
+ from the client, checks what arrived against the cap and on its bytes (`contentMatches`), scans
791
+ it, and answers the `StoredFile`. No final key may start with `staging/` (`assertFinalKey`).
792
+
793
+ `FileInspection` is the pass-through every connector streams through: it counts, hashes, and
794
+ with an expectation holds back the first bytes until they prove the declared media type
795
+ (`acceptsMediaType`, `contentMatches`: a PDF starts with `%PDF-`, office documents are zip
796
+ archives, text is UTF-8 without control characters, SVG and unknown binary formats never
797
+ match).
798
+
799
+ Connectors: `InMemoryFileStorage` for the tests (`keys()` lists what it holds) and
800
+ `LocalFileStorage({ directory, baseUrl, secret })` for development, both `ServedFileStorage`:
801
+ their signed links point at the application, which answers them with `signedFileRoutes` of
802
+ `@ddd-ts-kit/http`. The object store connector is `@ddd-ts-kit/s3`. Each passes
803
+ `fileStorageContract` of `@ddd-ts-kit/testing/contracts`. Background import and export (row by
804
+ row validation, an error report, resumption) needs a job queue the kit does not have yet: the
805
+ ports are ready for it, see `docs/concepts/file-storage.md`.
806
+
738
807
  ### JobScheduler
739
808
 
740
809
  ```ts
@@ -0,0 +1,3 @@
1
+ /** Whether a declared media type is in the accepted list, which may hold `type/*`. */
2
+ export declare function acceptsMediaType(accepted: readonly string[], declared: string): boolean;
3
+ //# sourceMappingURL=accepts-media-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accepts-media-type.d.ts","sourceRoot":"","sources":["../../../src/application/files/accepts-media-type.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CASvF"}
@@ -0,0 +1,11 @@
1
+ import { mediaTypeOf } from './media-type-of.js';
2
+ /** Whether a declared media type is in the accepted list, which may hold `type/*`. */
3
+ export function acceptsMediaType(accepted, declared) {
4
+ const essence = mediaTypeOf(declared);
5
+ const [type] = essence.split('/');
6
+ return accepted.some((entry) => {
7
+ const allowed = mediaTypeOf(entry);
8
+ return allowed === essence || allowed === `${type ?? ''}/*`;
9
+ });
10
+ }
11
+ //# sourceMappingURL=accepts-media-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accepts-media-type.js","sourceRoot":"","sources":["../../../src/application/files/accepts-media-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,sFAAsF;AACtF,MAAM,UAAU,gBAAgB,CAAC,QAA2B,EAAE,QAAgB;IAC5E,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAElC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QAC7B,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAEnC,OAAO,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,GAAG,IAAI,IAAI,EAAE,IAAI,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** For a connector: the lifetime of a signed link, refused when it is not a whole number of seconds up to the maximum. */
2
+ export declare function assertExpiry(seconds: number): number;
3
+ //# sourceMappingURL=assert-expiry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-expiry.d.ts","sourceRoot":"","sources":["../../../src/application/files/assert-expiry.ts"],"names":[],"mappings":"AAGA,0HAA0H;AAC1H,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMpD"}
@@ -0,0 +1,10 @@
1
+ import { FileError } from './file-error.js';
2
+ import { MAX_SIGNED_URL_SECONDS } from './max-signed-url-seconds.js';
3
+ /** For a connector: the lifetime of a signed link, refused when it is not a whole number of seconds up to the maximum. */
4
+ export function assertExpiry(seconds) {
5
+ if (!Number.isInteger(seconds) || seconds < 1 || seconds > MAX_SIGNED_URL_SECONDS) {
6
+ throw FileError.invalidExpiry(seconds);
7
+ }
8
+ return seconds;
9
+ }
10
+ //# sourceMappingURL=assert-expiry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-expiry.js","sourceRoot":"","sources":["../../../src/application/files/assert-expiry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,0HAA0H;AAC1H,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,sBAAsB,EAAE,CAAC;QAClF,MAAM,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** For a connector: refuses a key `isFileKey` refuses, before anything is touched. */
2
+ export declare function assertFileKey(key: string): string;
3
+ //# sourceMappingURL=assert-file-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-file-key.d.ts","sourceRoot":"","sources":["../../../src/application/files/assert-file-key.ts"],"names":[],"mappings":"AAGA,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMjD"}
@@ -0,0 +1,10 @@
1
+ import { FileError } from './file-error.js';
2
+ import { isFileKey } from './is-file-key.js';
3
+ /** For a connector: refuses a key `isFileKey` refuses, before anything is touched. */
4
+ export function assertFileKey(key) {
5
+ if (!isFileKey(key)) {
6
+ throw FileError.invalidKey(key);
7
+ }
8
+ return key;
9
+ }
10
+ //# sourceMappingURL=assert-file-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-file-key.js","sourceRoot":"","sources":["../../../src/application/files/assert-file-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,sFAAsF;AACtF,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Refuses a key the storages refuse, and one under `staging/`, which the purge would take for a staged file. */
2
+ export declare function assertFinalKey(key: string): string;
3
+ //# sourceMappingURL=assert-final-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-final-key.d.ts","sourceRoot":"","sources":["../../../src/application/files/assert-final-key.ts"],"names":[],"mappings":"AAIA,iHAAiH;AACjH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMlD"}
@@ -0,0 +1,11 @@
1
+ import { assertFileKey } from './assert-file-key.js';
2
+ import { FileError } from './file-error.js';
3
+ import { STAGING_PREFIX } from './staged-key-of.js';
4
+ /** Refuses a key the storages refuse, and one under `staging/`, which the purge would take for a staged file. */
5
+ export function assertFinalKey(key) {
6
+ if (assertFileKey(key).startsWith(STAGING_PREFIX)) {
7
+ throw FileError.invalidKey(key);
8
+ }
9
+ return key;
10
+ }
11
+ //# sourceMappingURL=assert-final-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert-final-key.js","sourceRoot":"","sources":["../../../src/application/files/assert-final-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,iHAAiH;AACjH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAClD,MAAM,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** `attachment`, with the name when there is one, kept to characters that cannot break the header, and its UTF-8 form. */
2
+ export declare function contentDisposition(fileName?: string): string;
3
+ //# sourceMappingURL=content-disposition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-disposition.d.ts","sourceRoot":"","sources":["../../../src/application/files/content-disposition.ts"],"names":[],"mappings":"AAAA,0HAA0H;AAC1H,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ5D"}
@@ -0,0 +1,9 @@
1
+ /** `attachment`, with the name when there is one, kept to characters that cannot break the header, and its UTF-8 form. */
2
+ export function contentDisposition(fileName) {
3
+ if (fileName === undefined) {
4
+ return 'attachment';
5
+ }
6
+ const plain = fileName.replace(/[^A-Za-z0-9._ -]/g, '_');
7
+ return `attachment; filename="${plain}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
8
+ }
9
+ //# sourceMappingURL=content-disposition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-disposition.js","sourceRoot":"","sources":["../../../src/application/files/content-disposition.ts"],"names":[],"mappings":"AAAA,0HAA0H;AAC1H,MAAM,UAAU,kBAAkB,CAAC,QAAiB;IAClD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IAEzD,OAAO,yBAAyB,KAAK,uBAAuB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,6 @@
1
+ /** What an incoming content must be: one of the accepted media types, the one it declares, verified on its bytes. */
2
+ export interface ContentExpectation {
3
+ readonly mediaType: string;
4
+ readonly accepted: readonly string[];
5
+ }
6
+ //# sourceMappingURL=content-expectation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-expectation.d.ts","sourceRoot":"","sources":["../../../src/application/files/content-expectation.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=content-expectation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-expectation.js","sourceRoot":"","sources":["../../../src/application/files/content-expectation.ts"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether the first bytes of a content are what its declared media type says, read on the bytes
3
+ * and never on a file name: a PDF starts with `%PDF-`, a PNG with its eight bytes. A binary
4
+ * format the kit does not recognise, SVG included, never matches.
5
+ */
6
+ export declare function contentMatches(head: Uint8Array, declared: string): boolean;
7
+ //# sourceMappingURL=content-matches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-matches.d.ts","sourceRoot":"","sources":["../../../src/application/files/content-matches.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAS1E"}
@@ -0,0 +1,39 @@
1
+ import { signatureOf } from './signature-of.js';
2
+ import { mediaTypeOf } from './media-type-of.js';
3
+ /** Office and e-book documents are zip archives: their signature is the one of a zip. */
4
+ const ZIP_CONTAINERS = new Set([
5
+ 'application/zip',
6
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
7
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
8
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
9
+ 'application/vnd.oasis.opendocument.text',
10
+ 'application/vnd.oasis.opendocument.spreadsheet',
11
+ 'application/epub+zip',
12
+ ]);
13
+ /** Text has no signature: it is accepted when its first bytes are UTF-8 without control characters. */
14
+ const TEXT_TYPES = new Set(['text/plain', 'text/csv', 'text/markdown', 'application/json']);
15
+ /** Tab, line feed, form feed and carriage return are text; every other C0 control character is not. */
16
+ const TEXT_CONTROLS = new Set([9, 10, 12, 13]);
17
+ /**
18
+ * Whether the first bytes of a content are what its declared media type says, read on the bytes
19
+ * and never on a file name: a PDF starts with `%PDF-`, a PNG with its eight bytes. A binary
20
+ * format the kit does not recognise, SVG included, never matches.
21
+ */
22
+ export function contentMatches(head, declared) {
23
+ const essence = mediaTypeOf(declared);
24
+ const signature = signatureOf(head);
25
+ if (signature !== undefined) {
26
+ return signature === essence || (signature === 'application/zip' && ZIP_CONTAINERS.has(essence));
27
+ }
28
+ return TEXT_TYPES.has(essence) && looksLikeText(head);
29
+ }
30
+ function looksLikeText(head) {
31
+ try {
32
+ new TextDecoder('utf-8', { fatal: true }).decode(head, { stream: true });
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ return head.every((byte) => byte >= 32 || TEXT_CONTROLS.has(byte));
38
+ }
39
+ //# sourceMappingURL=content-matches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-matches.js","sourceRoot":"","sources":["../../../src/application/files/content-matches.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,yFAAyF;AACzF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,iBAAiB;IACjB,yEAAyE;IACzE,mEAAmE;IACnE,2EAA2E;IAC3E,yCAAyC;IACzC,gDAAgD;IAChD,sBAAsB;CACvB,CAAC,CAAC;AAEH,uGAAuG;AACvG,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAE5F,uGAAuG;AACvG,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AAE/C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAgB,EAAE,QAAgB;IAC/D,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAEpC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,SAAS,KAAK,OAAO,IAAI,CAAC,SAAS,KAAK,iBAAiB,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACnG,CAAC;IAED,OAAO,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,aAAa,CAAC,IAAgB;IACrC,IAAI,CAAC;QACH,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,EAAE,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface DownloadUrlOptions {
2
+ /** Short: at most `MAX_SIGNED_URL_SECONDS`. A link leaks with the page, the log or the mail that holds it. */
3
+ readonly expiresInSeconds: number;
4
+ /** Offered to the browser as the name to save under (`Content-Disposition: attachment`). */
5
+ readonly fileName?: string;
6
+ }
7
+ //# sourceMappingURL=download-url-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-url-options.d.ts","sourceRoot":"","sources":["../../../src/application/files/download-url-options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,8GAA8G;IAC9G,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=download-url-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-url-options.js","sourceRoot":"","sources":["../../../src/application/files/download-url-options.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ import type { Readable } from 'node:stream';
2
+ import type { FileMetadata } from './file-metadata.js';
3
+ /** A file being read: what the storage knows of it, and its content as a stream to consume once. */
4
+ export interface FileContent {
5
+ readonly metadata: FileMetadata;
6
+ readonly stream: Readable;
7
+ }
8
+ //# sourceMappingURL=file-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-content.d.ts","sourceRoot":"","sources":["../../../src/application/files/file-content.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,oGAAoG;AACpG,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC3B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=file-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-content.js","sourceRoot":"","sources":["../../../src/application/files/file-content.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ import { ConceptError } from '../../domain/errors/concept-error.js';
2
+ /**
3
+ * The refusals of a file. `FILE_TOO_LARGE` and `UNSUPPORTED_MEDIA_TYPE` are answered 413 and 415
4
+ * by `@ddd-ts-kit/http`; the key itself never leaves the process.
5
+ */
6
+ export declare class FileError extends ConceptError {
7
+ static tooLarge(maxBytes: number): import("../../index.js").Refusal<FileError, "FILE_TOO_LARGE", {
8
+ readonly maxBytes: number;
9
+ }>;
10
+ static unsupportedMediaType(mediaType: string, accepted: readonly string[]): import("../../index.js").Refusal<FileError, "UNSUPPORTED_MEDIA_TYPE", {
11
+ readonly mediaType: string;
12
+ readonly accepted: string;
13
+ }>;
14
+ static notFound(key: string): import("../../index.js").Refusal<FileError, "FILE_NOT_FOUND", {
15
+ readonly key: string;
16
+ }>;
17
+ static invalidKey(key: string): import("../../index.js").Refusal<FileError, "INVALID_FILE_KEY", {
18
+ readonly key: string;
19
+ }>;
20
+ static rejected(key: string, reason: string): import("../../index.js").Refusal<FileError, "FILE_REJECTED", {
21
+ readonly key: string;
22
+ readonly reason: string;
23
+ }>;
24
+ static invalidExpiry(seconds: number): import("../../index.js").Refusal<FileError, "INVALID_FILE_URL_EXPIRY", {
25
+ readonly seconds: number;
26
+ }>;
27
+ }
28
+ //# sourceMappingURL=file-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-error.d.ts","sourceRoot":"","sources":["../../../src/application/files/file-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AAGpE;;;GAGG;AACH,qBAAa,SAAU,SAAQ,YAAY;IACzC,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM;;OAI/B;IAED,MAAM,CAAC,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE;;;OAMzE;IAED,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM;;OAE1B;IAED,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM;;OAM5B;IAED,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;OAE1C;IAED,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM;;OAMnC;CACF"}
@@ -0,0 +1,29 @@
1
+ import { ConceptError } from '../../domain/errors/concept-error.js';
2
+ import { MAX_SIGNED_URL_SECONDS } from './max-signed-url-seconds.js';
3
+ /**
4
+ * The refusals of a file. `FILE_TOO_LARGE` and `UNSUPPORTED_MEDIA_TYPE` are answered 413 and 415
5
+ * by `@ddd-ts-kit/http`; the key itself never leaves the process.
6
+ */
7
+ export class FileError extends ConceptError {
8
+ static tooLarge(maxBytes) {
9
+ return FileError.refusal('FILE_TOO_LARGE', `The file is larger than ${String(maxBytes)} bytes.`, {
10
+ maxBytes,
11
+ }).exposing('maxBytes');
12
+ }
13
+ static unsupportedMediaType(mediaType, accepted) {
14
+ return FileError.refusal('UNSUPPORTED_MEDIA_TYPE', `The content is not one of ${accepted.join(', ')} (declared ${mediaType}).`, { mediaType, accepted: accepted.join(',') }).exposing('accepted');
15
+ }
16
+ static notFound(key) {
17
+ return FileError.refusal('FILE_NOT_FOUND', 'No file is stored under this key.', { key });
18
+ }
19
+ static invalidKey(key) {
20
+ return FileError.refusal('INVALID_FILE_KEY', 'A file key is made of segments of letters, digits, dots, hyphens and underscores, none starting with a dot.', { key });
21
+ }
22
+ static rejected(key, reason) {
23
+ return FileError.refusal('FILE_REJECTED', `The content was refused: ${reason}.`, { key, reason });
24
+ }
25
+ static invalidExpiry(seconds) {
26
+ return FileError.refusal('INVALID_FILE_URL_EXPIRY', `A signed link lives between 1 and ${String(MAX_SIGNED_URL_SECONDS)} seconds, not ${String(seconds)}.`, { seconds });
27
+ }
28
+ }
29
+ //# sourceMappingURL=file-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-error.js","sourceRoot":"","sources":["../../../src/application/files/file-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,YAAY;IACzC,MAAM,CAAC,QAAQ,CAAC,QAAgB;QAC9B,OAAO,SAAS,CAAC,OAAO,CAAC,gBAAgB,EAAE,2BAA2B,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE;YAC/F,QAAQ;SACT,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,CAAC,oBAAoB,CAAC,SAAiB,EAAE,QAA2B;QACxE,OAAO,SAAS,CAAC,OAAO,CACtB,wBAAwB,EACxB,6BAA6B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,SAAS,IAAI,EAC3E,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC5C,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,GAAW;QACzB,OAAO,SAAS,CAAC,OAAO,CAAC,gBAAgB,EAAE,mCAAmC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,GAAW;QAC3B,OAAO,SAAS,CAAC,OAAO,CACtB,kBAAkB,EAClB,6GAA6G,EAC7G,EAAE,GAAG,EAAE,CACR,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,GAAW,EAAE,MAAc;QACzC,OAAO,SAAS,CAAC,OAAO,CAAC,eAAe,EAAE,4BAA4B,MAAM,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;IACpG,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,OAAe;QAClC,OAAO,SAAS,CAAC,OAAO,CACtB,yBAAyB,EACzB,qCAAqC,MAAM,CAAC,sBAAsB,CAAC,iBAAiB,MAAM,CAAC,OAAO,CAAC,GAAG,EACtG,EAAE,OAAO,EAAE,CACZ,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ /** What a storage knows of a file it holds, without reading the content. */
2
+ export interface FileMetadata {
3
+ readonly key: string;
4
+ readonly size: number;
5
+ readonly mediaType: string;
6
+ readonly lastModified: Date;
7
+ /** `sha256:<hex>`, when the connector keeps it: the S3 connector does not. */
8
+ readonly checksum?: string;
9
+ }
10
+ //# sourceMappingURL=file-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-metadata.d.ts","sourceRoot":"","sources":["../../../src/application/files/file-metadata.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=file-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-metadata.js","sourceRoot":"","sources":["../../../src/application/files/file-metadata.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { Readable } from 'node:stream';
2
+ /** The content handed to a storage: always a stream of chunks, never the whole file in memory. */
3
+ export type FileSource = Readable | AsyncIterable<Uint8Array>;
4
+ //# sourceMappingURL=file-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-source.d.ts","sourceRoot":"","sources":["../../../src/application/files/file-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,kGAAkG;AAClG,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=file-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-source.js","sourceRoot":"","sources":["../../../src/application/files/file-source.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import type { ReceivedFile } from './received-file.js';
2
+ import type { StoredFile } from './stored-file.js';
3
+ /**
4
+ * A file a command carries. The controller hands over the `ReceivedFile`, still streaming, and
5
+ * the `fileUploadsMiddleware` of the bus stages it under a key of `area`, scanned, before the
6
+ * unit of work opens: no transaction waits on a client that uploads slowly. The handler then
7
+ * reads `file`, records it and asks `FileStaging.promoteOnCommit` for it.
8
+ */
9
+ export declare class FileUpload {
10
+ readonly received: ReceivedFile;
11
+ /** A key prefix of the project, such as `appointment-documents`. */
12
+ readonly area: string;
13
+ private stored;
14
+ constructor(received: ReceivedFile,
15
+ /** A key prefix of the project, such as `appointment-documents`. */
16
+ area: string);
17
+ /** The name the client gave: shown back to a person, never a key or a path. */
18
+ get fileName(): string;
19
+ get isStaged(): boolean;
20
+ /** The staged file, under its final key. Read before the middleware staged it, a defect of the wiring. */
21
+ get file(): StoredFile;
22
+ /** Called by whoever staged it, once. */
23
+ stagedAs(file: StoredFile): void;
24
+ }
25
+ //# sourceMappingURL=file-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../../src/application/files/file-upload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;GAKG;AACH,qBAAa,UAAU;IAInB,QAAQ,CAAC,QAAQ,EAAE,YAAY;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM;IALvB,OAAO,CAAC,MAAM,CAAyB;IAEvC,YACW,QAAQ,EAAE,YAAY;IAC/B,oEAAoE;IAC3D,IAAI,EAAE,MAAM,EACnB;IAEJ,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,0GAA0G;IAC1G,IAAI,IAAI,IAAI,UAAU,CAQrB;IAED,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAE/B;CACF"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A file a command carries. The controller hands over the `ReceivedFile`, still streaming, and
3
+ * the `fileUploadsMiddleware` of the bus stages it under a key of `area`, scanned, before the
4
+ * unit of work opens: no transaction waits on a client that uploads slowly. The handler then
5
+ * reads `file`, records it and asks `FileStaging.promoteOnCommit` for it.
6
+ */
7
+ export class FileUpload {
8
+ received;
9
+ area;
10
+ stored;
11
+ constructor(received,
12
+ /** A key prefix of the project, such as `appointment-documents`. */
13
+ area) {
14
+ this.received = received;
15
+ this.area = area;
16
+ }
17
+ /** The name the client gave: shown back to a person, never a key or a path. */
18
+ get fileName() {
19
+ return this.received.fileName;
20
+ }
21
+ get isStaged() {
22
+ return this.stored !== undefined;
23
+ }
24
+ /** The staged file, under its final key. Read before the middleware staged it, a defect of the wiring. */
25
+ get file() {
26
+ if (this.stored === undefined) {
27
+ throw new Error(`The file upload of area "${this.area}" was read before it was staged: install fileUploadsMiddleware on the command bus, before the unit of work.`);
28
+ }
29
+ return this.stored;
30
+ }
31
+ /** Called by whoever staged it, once. */
32
+ stagedAs(file) {
33
+ this.stored = file;
34
+ }
35
+ }
36
+ //# sourceMappingURL=file-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-upload.js","sourceRoot":"","sources":["../../../src/application/files/file-upload.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,OAAO,UAAU;IAIV,QAAQ;IAER,IAAI;IALP,MAAM,CAAyB;IAEvC,YACW,QAAsB;IAC/B,oEAAoE;IAC3D,IAAY;wBAFZ,QAAQ;oBAER,IAAI;IACZ,CAAC;IAEJ,+EAA+E;IAC/E,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;IACnC,CAAC;IAED,0GAA0G;IAC1G,IAAI,IAAI;QACN,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,CAAC,IAAI,6GAA6G,CACnJ,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,yCAAyC;IACzC,QAAQ,CAAC,IAAgB;QACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ export * from './accepts-media-type.js';
2
+ export * from './assert-expiry.js';
3
+ export * from './content-disposition.js';
4
+ export * from './content-expectation.js';
5
+ export * from './content-matches.js';
6
+ export * from './download-url-options.js';
7
+ export * from './file-content.js';
8
+ export * from './file-error.js';
9
+ export * from './assert-file-key.js';
10
+ export * from './assert-final-key.js';
11
+ export * from './is-file-key.js';
12
+ export * from './file-metadata.js';
13
+ export * from './file-source.js';
14
+ export * from './file-upload.js';
15
+ export * from './inspection-limits.js';
16
+ export * from './listed-file.js';
17
+ export * from './max-signed-url-seconds.js';
18
+ export * from './media-type-of.js';
19
+ export * from './pending-upload.js';
20
+ export * from './pending-upload-link.js';
21
+ export * from './put-options.js';
22
+ export * from './received-file.js';
23
+ export * from './scan-verdict.js';
24
+ export * from './signature-of.js';
25
+ export * from './signed-download.js';
26
+ export * from './signed-upload.js';
27
+ export * from './signed-url.js';
28
+ export * from './staged-key-of.js';
29
+ export * from './stored-file.js';
30
+ export * from './upload-url-options.js';
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/application/files/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,31 @@
1
+ export * from './accepts-media-type.js';
2
+ export * from './assert-expiry.js';
3
+ export * from './content-disposition.js';
4
+ export * from './content-expectation.js';
5
+ export * from './content-matches.js';
6
+ export * from './download-url-options.js';
7
+ export * from './file-content.js';
8
+ export * from './file-error.js';
9
+ export * from './assert-file-key.js';
10
+ export * from './assert-final-key.js';
11
+ export * from './is-file-key.js';
12
+ export * from './file-metadata.js';
13
+ export * from './file-source.js';
14
+ export * from './file-upload.js';
15
+ export * from './inspection-limits.js';
16
+ export * from './listed-file.js';
17
+ export * from './max-signed-url-seconds.js';
18
+ export * from './media-type-of.js';
19
+ export * from './pending-upload.js';
20
+ export * from './pending-upload-link.js';
21
+ export * from './put-options.js';
22
+ export * from './received-file.js';
23
+ export * from './scan-verdict.js';
24
+ export * from './signature-of.js';
25
+ export * from './signed-download.js';
26
+ export * from './signed-upload.js';
27
+ export * from './signed-url.js';
28
+ export * from './staged-key-of.js';
29
+ export * from './stored-file.js';
30
+ export * from './upload-url-options.js';
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/application/files/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ContentExpectation } from './content-expectation.js';
2
+ export interface InspectionLimits {
3
+ /** Beyond it the stream fails with `FILE_TOO_LARGE`. */
4
+ readonly maxBytes?: number;
5
+ /** When given, the first bytes are checked against it before any byte goes on. */
6
+ readonly expected?: ContentExpectation;
7
+ }
8
+ //# sourceMappingURL=inspection-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspection-limits.d.ts","sourceRoot":"","sources":["../../../src/application/files/inspection-limits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CACxC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inspection-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspection-limits.js","sourceRoot":"","sources":["../../../src/application/files/inspection-limits.ts"],"names":[],"mappings":""}