ddd test 2

This commit is contained in:
2026-03-23 00:23:56 +01:00
parent fe9c1dd1c0
commit 971a5afb8b
130 changed files with 2493 additions and 822 deletions

View File

@@ -0,0 +1,148 @@
# Attachment Domain
Handles file sharing between peers over WebRTC data channels. Files are announced, chunked into 64 KB pieces, streamed peer-to-peer as base64, and optionally persisted to disk (Electron) or kept in memory (browser).
## Module map
```
attachment/
├── application/
│ ├── attachment.facade.ts Thin entry point, delegates to manager
│ ├── attachment-manager.service.ts Orchestrates lifecycle, auto-download, peer listeners
│ ├── attachment-transfer.service.ts P2P file transfer protocol (announce/request/chunk/cancel)
│ ├── attachment-transfer-transport.service.ts Base64 encode/decode, chunked streaming
│ ├── attachment-persistence.service.ts DB + filesystem persistence, migration from localStorage
│ └── attachment-runtime.store.ts In-memory signal-based state (Maps for attachments, chunks, pending)
├── domain/
│ ├── attachment.models.ts Attachment type extending AttachmentMeta with runtime state
│ ├── attachment.logic.ts isAttachmentMedia, shouldAutoRequestWhenWatched, shouldPersistDownloadedAttachment
│ ├── attachment.constants.ts MAX_AUTO_SAVE_SIZE_BYTES = 10 MB
│ ├── attachment-transfer.models.ts Protocol event types (file-announce, file-chunk, file-request, ...)
│ └── attachment-transfer.constants.ts FILE_CHUNK_SIZE_BYTES = 64 KB, EWMA weights, error messages
├── infrastructure/
│ ├── attachment-storage.service.ts Electron filesystem access (save / read / delete)
│ └── attachment-storage.helpers.ts sanitizeAttachmentRoomName, resolveAttachmentStorageBucket
└── index.ts Barrel exports
```
## Service composition
The facade is a thin pass-through. All real work happens inside the manager, which coordinates the transfer service (protocol), persistence service (DB/disk), and runtime store (signals).
```mermaid
graph TD
Facade[AttachmentFacade]
Manager[AttachmentManagerService]
Transfer[AttachmentTransferService]
Transport[AttachmentTransferTransportService]
Persistence[AttachmentPersistenceService]
Store[AttachmentRuntimeStore]
Storage[AttachmentStorageService]
Logic[attachment.logic]
Facade --> Manager
Manager --> Transfer
Manager --> Persistence
Manager --> Store
Manager --> Logic
Transfer --> Transport
Transfer --> Store
Persistence --> Storage
Persistence --> Store
Storage --> Helpers[attachment-storage.helpers]
click Facade "application/attachment.facade.ts" "Thin entry point"
click Manager "application/attachment-manager.service.ts" "Orchestrates lifecycle"
click Transfer "application/attachment-transfer.service.ts" "P2P file transfer protocol"
click Transport "application/attachment-transfer-transport.service.ts" "Base64 encode/decode, chunked streaming"
click Persistence "application/attachment-persistence.service.ts" "DB + filesystem persistence"
click Store "application/attachment-runtime.store.ts" "In-memory signal-based state"
click Storage "infrastructure/attachment-storage.service.ts" "Electron filesystem access"
click Helpers "infrastructure/attachment-storage.helpers.ts" "Path helpers"
click Logic "domain/attachment.logic.ts" "Pure decision functions"
```
## File transfer protocol
Files move between peers using a request/response pattern over the WebRTC data channel. The sender announces a file, the receiver requests it, and chunks flow back one by one.
```mermaid
sequenceDiagram
participant S as Sender
participant R as Receiver
S->>R: file-announce (id, name, size, mimeType)
Note over R: Store metadata in runtime store
Note over R: shouldAutoRequestWhenWatched?
R->>S: file-request (attachmentId)
Note over S: Look up file in runtime store or on disk
loop Every 64 KB chunk
S->>R: file-chunk (attachmentId, index, data, progress, speed)
Note over R: Append to chunk buffer
Note over R: Update progress + EWMA speed
end
Note over R: All chunks received
Note over R: Reassemble blob
Note over R: shouldPersistDownloadedAttachment? Save to disk
```
### Failure handling
If the sender cannot find the file, it replies with `file-not-found`. The transfer service then tries the next connected peer that has announced the same attachment. Either side can send `file-cancel` to abort a transfer in progress.
```mermaid
sequenceDiagram
participant R as Receiver
participant P1 as Peer A
participant P2 as Peer B
R->>P1: file-request
P1->>R: file-not-found
Note over R: Try next peer
R->>P2: file-request
P2->>R: file-chunk (1/N)
P2->>R: file-chunk (2/N)
P2->>R: file-chunk (N/N)
Note over R: Transfer complete
```
## Auto-download rules
When the user navigates to a room, the manager watches the route and decides which attachments to request automatically based on domain logic:
| Condition | Auto-download? |
|---|---|
| Image or video, size <= 10 MB | Yes |
| Image or video, size > 10 MB | No |
| Non-media file | No |
The decision lives in `shouldAutoRequestWhenWatched()` which calls `isAttachmentMedia()` and checks against `MAX_AUTO_SAVE_SIZE_BYTES`.
## Persistence
On Electron, completed downloads are written to the app-data directory. The storage path is resolved per room and bucket:
```
{appDataPath}/{serverId}/{roomName}/{bucket}/{filename}
```
Room names are sanitised to remove filesystem-unsafe characters. The bucket is either `attachments` or `media` depending on the attachment type.
`AttachmentPersistenceService` handles startup migration from an older localStorage-based format into the database, and restores attachment metadata from the DB on init. On browser builds, files stay in memory only.
## Runtime store
`AttachmentRuntimeStore` is a signal-based in-memory store using `Map` instances for:
- **attachments**: all known attachments keyed by ID
- **chunks**: incoming chunk buffers during active transfers
- **pendingRequests**: outbound requests waiting for a response
- **cancellations**: IDs of transfers the user cancelled
Components read attachment state reactively through the store's signals. The store has no persistence of its own; that responsibility belongs to the persistence service.