Project: workspace-doctolib · Branch: HEAD · Mode: ` · **Model:**claude-sonnet-4-6`
Stats: 1 user messages, 8 tool calls, tools used: Read, Bash.
Conversation
Turn 1 — User
Analyze type design for PR #241185 in doctolib/doctolib. New types added:
// FrDmpTypes.ts — new interface
export interface DeleteArchivedDocumentRequest extends DmpRequest {
s_sessionId: string
s_ins: string
s_uniqueId: string
s_uuid: string
s_healthCareSettings: string
i_getTransactionFrames: number
}
// Existing for comparison
export interface DeleteDocumentRequest extends DmpRequest {
s_sessionId: string
s_ins: string
s_uniqueId: string
s_uuid: string
s_healthCareSettings: string
}
// DmpWorkflowState extended with
| 'deleteArchivedDocument'
The local files for context are at:
/Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/FrDmpTypes.ts
Please read that file to find the DmpRequest base interface and WithoutAuth type to assess:
1. Is DeleteArchivedDocumentRequest the right way to model this, or should it extend/compose with DeleteDocumentRequest?
2. Is the i_getTransactionFrames: number field properly placed in the interface vs inline in the function body?
3. Are there invariants that the type doesn't enforce?
Return structured feedback on the type design quality.
Turn 1 — Assistant
Let me read the relevant file to get the full context before analyzing.
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/FrDmpTypes.ts
Tool results:
→ result (ok): 1 import type { MedicalFolder } from '@doctolib/ehr-core'
2 import type { DmpResult } from '@doctolib/tpf-french-teleservices'
3 import type { Patient } from 'monolith_DEPRECATED/types/doctor/api/Patient'
4 import type { DmpDocumentToAdd, FrDmpDocument } from '../business/FrDmpConfig'
5
6 export interface FrDmpSessionStorage {
7 sessionId: string
8 practiceIndex: number
9 healthcareSettings: string
10 createdAt: Date
11 }
12
13 // DMP Requests
14 export interface DmpRequest {
15 s_co
…(truncated, 24449 more chars)
Now let me also look at the surrounding usage context — specifically the functions that construct DeleteDocumentRequest and any related WithoutAuth type, and how DmpWorkflowState is used.
Tools used:
Bash:grep -rn "DeleteArchivedDocument\|deleteArchivedDocument\|WithoutAuth\|i_getTransactionFrames" /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/p …(truncated, 64 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/business/frDmpPayloadBuilders.ts /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/business/FrDmpCommunication.ts /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/FrDmpTypes.ts /Users/USER/workspace/doctolib/worktre …(truncated, 280 more chars)
Tools used:
Bash:grep -n "DeleteArchivedDocument\|deleteArchivedDocument\|WithoutAuth\|DeleteDocumentRequest\|DmpWorkflowState" /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and …(truncated, 74 more chars)
Tool results:
→ result (ok): 9: DeleteDocumentRequest, 88:type WithoutAuth<T> = Omit<T, 's_commandName' | 's_sessionId'> 134:}): WithoutAuth<GetAuthentificationDmpStatusForPatientRequest> => ({ 162:}): WithoutAuth<SendDocumentRequest> => { 192: } as WithoutAuth<SendDocumentRequest> 205:}): WithoutAuth<DeleteDocumentRequest> => ({ 231:}: Omit<FindDocumentsParams, 'sessionId'>): WithoutAuth<FindDocumentsRequest> => ({ 266:}: Omit<GetDocumentsContentParams, 'sessionId'>): WithoutAuth<GetDocumentsContentRequest> => ({ 288:}: …(truncated, 279 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/business/frDmpPayloadBuilders.ts
Tool results:
→ result (ok): 1 import { envIsProduction } from '@doctolib/variation-management/environment' 2 import type { Patient } from 'monolith_DEPRECATED/types/doctor/api/Patient' 3 import dayjs from 'dayjs' 4 import type { CdaIdentifierFr } from '@doctolib/tpf-cda' 5 import type { 6 SharingSettings, 7 FindDocumentsRequest, 8 SendDocumentRequest, 9 DeleteDocumentRequest, 10 GetAuthentificationDmpStatusForPatientRequest, 11 GetDocumentsContentRequest, 12 GetDocumentsContentParams, 13 ReferenceId, 14 E …(truncated, 11642 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/business/FrDmpCommunication.ts
Tool results:
→ result (ok): 1 import { sendMonitoringEvent } from 'monolith_DEPRECATED/services/monitoring' 2 import { startPerformanceTracking } from 'monolith_DEPRECATED/services/performance/PerformanceTrackingService' 3 import { envIsProduction } from '@doctolib/variation-management/environment' 4 import { isFeatureEnabled } from '@doctolib/variation-management/legacy' 5 import type { CdaIdentifierFr } from '@doctolib/tpf-cda' 6 import type { 7 ReadCpxCardResponse, 8 DmpRequest, 9 PcscReadersRequest, 10 DmpRespo …(truncated, 21809 more chars)
Now I have a thorough understanding of the full context. Let me also check if DeleteArchivedDocumentRequest is actually referenced anywhere yet, and whether a WithoutAuth usage or a Params type companion was created for it.
Tools used:
Bash:grep -rn "DeleteArchivedDocument\|deleteArchivedDocument" /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/ --include= …(truncated, 24 more chars)
Tool results:
→ result (ok): (Bash completed with no output)
Tools used:
Bash:grep -rn "DeleteArchivedDocument\|deleteArchivedDocument\|i_getTransactionFrames" /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doct …(truncated, 44 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/FrDmpTypes.ts:81: i_getTransactionFrames?: number /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/FrDmpTypes.ts:189: i_getTransactionFrames?: number /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/F …(truncated, 4145 more chars)
I now have complete context. DeleteArchivedDocumentRequest does not yet exist in the file — it is being proposed for this PR. Let me also confirm the exact shape of DmpWorkflowState as it sits today versus what is being added, and check for a Params companion pattern.
Tools used:
Bash:grep -n "DmpWorkflowState\|deleteDocument\|deleteArchived" /Users/USER/workspace/doctolib/worktrees/doctolib/fhir-2216_fix-nhr-date-columns-and-document-type/packages/@doctolib/dmp/src/types/FrDm …(truncated, 9 more chars)
Tool results:
→ result (ok): 422: state: DmpWorkflowState 440:export type DmpWorkflowState = 442: | 'deleteDocument' 529: deleteDocumentFromDmp: (dmpEvent: {
I now have everything needed for a thorough analysis. Here is the structured feedback.
Type: DeleteArchivedDocumentRequest
Invariants Identified
- Must extend
DmpRequest, which mandatess_commandName: string. There is a single valid command name for this operation, but the type does not narrow it. s_sessionId,s_ins,s_uniqueId,s_uuid, ands_healthCareSettingsare all required strings with no empty-string guard — the same structural invariants asDeleteDocumentRequest.i_getTransactionFramesis typednumberbut the entire codebase treats it as a0 | 1boolean flag, not an arbitrary integer.- The new type is structurally identical to
DeleteDocumentRequestplusi_getTransactionFrames, yet it is defined as an independent interface rather than extending the existing one.
Ratings
Encapsulation: 6/10
The interface is a pure data-transfer bag for the Icanopee native connector API, so deep encapsulation is inherently limited here. The file-level WithoutAuth<T> pattern in frDmpPayloadBuilders.ts does a reasonable job of stripping s_commandName and s_sessionId from builder return types, separating construction concerns. The problem is that DeleteArchivedDocumentRequest has no corresponding Params type (compare UpdateDocumentStatusByUniqueIdParams / GetDocumentsContentParams) and no builder function. There is no encapsulation boundary at all yet; callers would have to construct the raw request directly.
Invariant Expression: 3/10 The sharpest weakness. Three issues:
s_commandNameaccepts any string, but only'hl_deleteArchivedDocument'(or whatever the exact vendor command name is) is ever valid. Every other*Requestin the codebase either leaves this open (weaker) or narrows it with a string literal inFindDocumentsRequest(s_commandName: 'hl_findDocuments'),GetDocumentHistoryByUniqueIdRequest,GetInteropCodesRequest,UpdateUserDmpAccessAuthorizationRequest. This is a known inconsistency in the file, but a new type should set the standard, not inherit the worst pattern.i_getTransactionFrames: numberis required and non-optional. Every other occurrence of this field in the file isi_getTransactionFrames?: number. Making it required means the caller must always decide on a debug flag, which is inappropriate for a production-targeted field. Making it non-optional also breaks the established pattern and would force every payload builder to always emit the field, even when it should be absent.i_getTransactionFramesshould be typed0 | 1, notnumber. The entire codebase treats it as a boolean flag serialised as an integer. Usingnumberallows callers to pass42, which would be silently accepted and may confuse the connector.
Invariant Usefulness: 4/10
The invariants that do exist (required fields for session, patient, and document identity) are useful. They match the pattern on DeleteDocumentRequest. However, by duplicating all five fields from DeleteDocumentRequest inline rather than composing with it, the type fails to express the domain relationship: "this is a delete-document operation, with one additional capability." That relationship is a useful invariant for future maintainers — if DeleteDocumentRequest ever gains a field (e.g., s_reason), DeleteArchivedDocumentRequest silently diverges. The distinction between deleting a live document and deleting an archived one is meaningful domain knowledge that the type design should make obvious.
Invariant Enforcement: 3/10
There is no builder function, no Params companion, and no usage site in this branch. The type exists in the types file but is not wired into the established WithoutAuth<T> + builder pattern. This means enforcement at construction time is entirely absent. Compare buildDeleteDocumentPayload which at least centralises construction of DeleteDocumentRequest. Any caller of a hypothetical deleteArchivedDocument function would construct the raw wire struct ad hoc.
Strengths
- The field names correctly follow the
s_/i_Hungarian notation convention used throughout the connector API, which matters because this maps directly to the Icanopee native connector's wire format. - Placing
i_getTransactionFramesin the interface type rather than injecting it inline in a function body (as is done withsendDocumentat line 286 ofFrDmpCommunication.ts) is the right architectural instinct. Inline mutation of a payload after construction is an existing anti-pattern in this file; the new type correctly avoids that. - Extending
DmpRequestinstead of being standalone is consistent with all other request types in the file.
Concerns
1. Full duplication of DeleteDocumentRequest instead of extension or composition.
// Current proposal
export interface DeleteArchivedDocumentRequest extends DmpRequest {
s_sessionId: string
s_ins: string
s_uniqueId: string
s_uuid: string
s_healthCareSettings: string
i_getTransactionFrames: number // added
}
// Problem: diverges silently from DeleteDocumentRequest
If DeleteDocumentRequest acquires a field tomorrow (e.g. a reason code), DeleteArchivedDocumentRequest will not receive it and no type error will occur. This is the most significant structural concern.
2. i_getTransactionFrames is required instead of optional, and typed as number instead of 0 | 1.
The field is a debug/tracing flag. Making it required forces every call site to explicitly pass a value, which increases friction and departs from the pattern on every other request type in this file. All eight other occurrences of i_getTransactionFrames in FrDmpTypes.ts are ?: number. The correct type is 0 | 1 because the connector interprets only those two values; allowing any integer silently accepts invalid states.
3. No Params type companion and no builder function.
Every operation that is integrated into the active workflow follows the layered pattern:
- A Params type (camelCase, domain-friendly, boolean flags rather than integers)
- A WithoutAuth<RequestType> builder in frDmpPayloadBuilders.ts
- A function in FrDmpCommunication.ts that assembles s_commandName + s_sessionId + builder output
DeleteArchivedDocumentRequest has none of these. This is either a PR in progress (intentionally incomplete) or the integration layer was omitted. Either way, the type is not usable in the established architecture without them.
4. DmpWorkflowState | 'deleteArchivedDocument' adds a state with no corresponding handler in DmpWorkflow.
DmpWorkflow.state drives the workflow state machine. Adding a new state without verifying that all exhaustive switches or conditionals over DmpWorkflowState in the codebase are updated is a latent bug. TypeScript union expansion is safe in the type itself, but all consumers doing if (state === 'deleteDocument') or switch without a default exhaustiveness check will silently ignore the new state.
Recommended Improvements
1. Compose with DeleteDocumentRequest instead of repeating fields.
export interface DeleteArchivedDocumentRequest extends DeleteDocumentRequest {
i_getTransactionFrames?: 0 | 1
}
This makes the relationship explicit: archiving is a superset of deleting, sharing the same identity and document coordinates. If DeleteDocumentRequest gains a field, DeleteArchivedDocumentRequest gets it for free. If the PR author has evidence that the archived-delete endpoint uses a different field name for any of the five shared fields (vendor API inconsistency), that deviation should be documented with a comment explaining the mismatch rather than silently duplicating.
2. Make i_getTransactionFrames optional and narrow its type.
i_getTransactionFrames?: 0 | 1
This matches every other occurrence in the file, keeps the debug flag opt-in, and makes illegal values (2, `99
…(truncated, 2392 more chars)