diff --git a/dev-docs/TS_MIGRATION_EXECUTION_BOARD.md b/dev-docs/TS_MIGRATION_EXECUTION_BOARD.md new file mode 100644 index 00000000..9ec8ca0e --- /dev/null +++ b/dev-docs/TS_MIGRATION_EXECUTION_BOARD.md @@ -0,0 +1,381 @@ +# EasyPost Node TS Migration Execution Board + +## Goal + +Execute the JS-to-TS migration as one native GitHub stack of dependent pull requests: + +- No integration branch +- No manual merge choreography outside stack semantics +- Each PR targets the branch below it +- Backwards compatible runtime behavior +- Permissive type behavior for SDK consumers + +This board is the operational companion to `TS_MIGRATION_PLAN.md`. + +## GitHub Stacks Reference Model + +Based on GitHub stacked pull requests docs: + +- Bottom PR targets trunk (`master`). +- Every next PR targets the branch below. +- Stack-aware CI/rules apply across layers. +- Rebase and retargeting are handled by stack workflows. +- To land all migration layers together, merge from the top PR (or stack-merge up to top). + +Notes: + +- If only a lower PR is merged, upper PRs remain open and are re-targeted/rebased. +- We will not use a temporary integration branch. + +## Operating Model + +### Tooling + +- GitHub CLI 2.90.0+ and Git 2.20+ +- `gh extension install github/gh-stack` + +Primary commands: + +- `gh stack init` +- `gh stack add` +- `gh stack submit` +- `gh stack sync` +- `gh stack view` + +Draft-first PR command behavior: + +- Use `gh stack submit --auto` to create new stack PRs as drafts by default. +- Use `gh stack submit --open` only when a layer (or the whole stack) is ready for review. + +### Stack Initialization + +1. Start from clean `master`. +2. Initialize migration stack: + - `gh stack init ts-migrate/00-baseline-safety-net` +3. Add each next branch at top with: + - `gh stack add ts-migrate/` +4. Commit layer-specific changes on each branch. +5. Submit/update full stack with: + - `gh stack submit --auto` +6. Keep stack current with: + - `gh stack sync` + +### Merge Policy + +- We will not run merges directly from this execution process. +- Review/approval happens per layer. +- When repository maintainers are ready to land everything, they merge the top migration PR (or equivalent stack merge operation to top). +- New PRs should remain draft until a layer passes its required checks. + +### PR Size Targets + +- Preferred: 150-500 changed lines per PR. +- Hard cap: 800 changed lines unless it is mechanical rename-only work. +- If a PR exceeds cap, split by module boundary into an additional stack layer. + +### Definition of Safe for Every PR + +- Runtime behavior unchanged unless explicitly approved. +- Existing tests pass. +- Node compatibility is not regressed. +- Public API shape remains unchanged. + +## Type-Integration Policy (When We Move Beyond Rename-Only) + +Rename-only conversion is an allowed bootstrap for early foundation layers, not the end-state. + +Policy by phase: + +- TSM-00 through TSM-05: + - mechanical JS->TS conversion is allowed to unblock stack progress + - temporary `@ts-nocheck` is allowed only on files first entering TS +- TSM-06 through TSM-10 (services): + - begin real type integration in every touched file + - no new `@ts-nocheck` allowed + - remove `@ts-nocheck` from files touched in the PR unless explicitly listed as deferred +- TSM-11 through TSM-15 (models): + - continue type integration and remove remaining `@ts-nocheck` in converted model/service files + - treat internal helper/model boundaries as typed seams (prefer `unknown` + narrowing over `any`) +- TSM-90 through TSM-91: + - zero `@ts-nocheck` under `src/` + - migration exceptions removed or justified with explicit follow-up + +Minimum typed-code requirements for TSM-06+ PRs: + +- exported class methods have explicit parameter and return types +- newly introduced `any` is disallowed unless justified inline +- API response boundaries should default to `unknown` and be narrowed where used + +Deferred typing (if needed in a layer) must be explicit: + +- add a short "Deferred Typing" section in the PR body +- list exact file/symbol and why it is deferred +- include the planned catch-up layer (same branch family if possible) + +## Single Stack Topology + +All branches are in one stack and must remain in this order. + +1. `ts-migrate/00-baseline-safety-net` +2. `ts-migrate/01-ts-build-scaffolding` +3. `ts-migrate/02-package-export-readiness` +4. `ts-migrate/03-type-strategy-guardrails` +5. `ts-migrate/04-core-entry-shared-infra` +6. `ts-migrate/05-base-service-hydration` +7. `ts-migrate/06-services-group-a` +8. `ts-migrate/07-services-group-b` +9. `ts-migrate/08-services-group-c` +10. `ts-migrate/09-services-group-d` +11. `ts-migrate/10-services-group-e` +12. `ts-migrate/11-models-group-a` +13. `ts-migrate/12-models-group-b` +14. `ts-migrate/13-models-group-c` +15. `ts-migrate/14-models-group-d` +16. `ts-migrate/15-models-group-e` +17. `ts-migrate/90-cutover-generated-types` +18. `ts-migrate/91-cleanup-calibration` + +## Detailed PR Board + +| PR ID | Branch | Base Branch | Scope | Est. Effort | Required Checks | +| ------ | ---------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------ | ----------- | ------------------------------------------- | +| TSM-00 | `ts-migrate/00-baseline-safety-net` | `master` | baseline scripts, API surface snapshot, migration checklist wiring | S | build, test, lint, types, node-compat smoke | +| TSM-01 | `ts-migrate/01-ts-build-scaffolding` | `ts-migrate/00-baseline-safety-net` | tsconfig split, mixed JS/TS compile setup, lint parser readiness | M | build, test, lint, types | +| TSM-02 | `ts-migrate/02-package-export-readiness` | `ts-migrate/01-ts-build-scaffolding` | package metadata prep, consumer import compatibility tests | S | build, test, package smoke | +| TSM-03 | `ts-migrate/03-type-strategy-guardrails` | `ts-migrate/02-package-export-readiness` | permissive TS policy doc + type tests for compatibility | S | types, test | +| TSM-04 | `ts-migrate/04-core-entry-shared-infra` | `ts-migrate/03-type-strategy-guardrails` | convert core entry/shared files to TS with behavior parity | M | build, test, lint, types | +| TSM-05 | `ts-migrate/05-base-service-hydration` | `ts-migrate/04-core-entry-shared-infra` | convert base service + dynamic hydration layer | M | build, test, types, targeted service tests | +| TSM-06 | `ts-migrate/06-services-group-a` | `ts-migrate/05-base-service-hydration` | convert Group A services + immediate deps | M | group tests, build, types | +| TSM-07 | `ts-migrate/07-services-group-b` | `ts-migrate/06-services-group-a` | convert Group B services + immediate deps | M | group tests, build, types | +| TSM-08 | `ts-migrate/08-services-group-c` | `ts-migrate/07-services-group-b` | convert Group C services + immediate deps | M | group tests, build, types | +| TSM-09 | `ts-migrate/09-services-group-d` | `ts-migrate/08-services-group-c` | convert Group D services + immediate deps | M | group tests, build, types | +| TSM-10 | `ts-migrate/10-services-group-e` | `ts-migrate/09-services-group-d` | convert Group E services + immediate deps | M | group tests, build, types | +| TSM-11 | `ts-migrate/11-models-group-a` | `ts-migrate/10-services-group-e` | convert remaining Group A models | S-M | group tests, build, types | +| TSM-12 | `ts-migrate/12-models-group-b` | `ts-migrate/11-models-group-a` | convert remaining Group B models | S-M | group tests, build, types | +| TSM-13 | `ts-migrate/13-models-group-c` | `ts-migrate/12-models-group-b` | convert remaining Group C models | S-M | group tests, build, types | +| TSM-14 | `ts-migrate/14-models-group-d` | `ts-migrate/13-models-group-c` | convert remaining Group D models | S-M | group tests, build, types | +| TSM-15 | `ts-migrate/15-models-group-e` | `ts-migrate/14-models-group-d` | convert remaining Group E models | S-M | group tests, build, types | +| TSM-90 | `ts-migrate/90-cutover-generated-types` | `ts-migrate/15-models-group-e` | generated declarations from src, remove `types/`, metadata switch | M-L | full CI, package smoke, TS demo compile | +| TSM-91 | `ts-migrate/91-cleanup-calibration` | `ts-migrate/90-cutover-generated-types` | remove migration-only exceptions, docs cleanup, final polish | S-M | full CI | + +## Scope Group Definitions + +Group A: + +- Address, Parcel, Customs, Shipment + +Group B: + +- Batch, Order, Pickup, Rate, SmartRate, ScanForm, Refund + +Group C: + +- CarrierAccount, CarrierType, CarrierMetadata, Billing + +Group D: + +- Tracker, Event, Webhook, Insurance, Claim + +Group E: + +- User, ApiKey, Referral/CustomerPortal, EndShipper, Embeddable, Luma, FedExRegistration + +## File Ownership Boundaries Per Layer + +Each layer must only edit files in its declared scope plus minimal shared typing/config glue needed to compile. + +Foundation layers (TSM-00 through TSM-05) can edit: + +- `package.json` +- `tsconfig*.json` +- `.eslintrc` +- `.github/workflows/ci.yml` +- core runtime files (`src/easypost.*`, `src/constants.*`, base utilities, base service/model primitives) + +Service/model layers (TSM-06 through TSM-15): + +- only module-group service/model files +- related tests for those modules +- minimal local imports/types required by those modules + +Cutover layers (TSM-90 through TSM-91): + +- `types/` deletion and declaration wiring +- docs updates +- removal of migration scaffolding + +## Agent Collaboration Model (Parallel Research, Serial Landing) + +Multiple agents are still useful with a single stack: + +- Stack Maintainer: + - owns branch creation, stack submit/sync, and final PR descriptions +- Worker Agents: + - prepare patch proposals for upcoming layers + - run focused verification on their slice + - hand off patch sets to stack maintainer + +Landing policy: + +- Exactly one active landing branch at a time (current top of stack). +- Accepted worker patches are applied in stack order. +- No separate integration branch. + +## Required Validation Matrix by Stage + +### Foundation Layers + +Run: + +- `npm run build` +- `npm run test` +- `npm run lint` +- `npm run typescript` + +### Service/Model Layers + +Run: + +- `npm run build` +- `npm run test` (targeted subset allowed for per-layer iteration) +- `npm run typescript` + +Additional hardening checks for TSM-06 through TSM-15: + +- `rg "@ts-nocheck" src` must trend downward each layer and never increase +- `rg "\bany\b" src/` results reviewed in PR notes when non-zero + +### Cutover Layers + +Run: + +- `npm run clean && npm run build` +- `npm run test` +- `npm run lint` +- `npm run typescript` +- package smoke install/consume tests for CJS, ESM, TS demo + +## Stack Coordination Playbook + +1. Initialize stack and create TSM-00 branch. +2. Create each next branch with `gh stack add` in strict order. +3. Commit one logical unit per branch layer. +4. Submit/update PR chain with `gh stack submit --auto` (draft by default). +5. Keep stack rebased and synchronized with `gh stack sync`. +6. Review each layer in GitHub stack map. +7. Land entire migration by merging top PR when approved. + +## Post-Migration Cleanup (Expected) + +After TSM-91 and a stabilization period, clean up migration-only scaffolding. + +Likely cleanup candidates: + +- Consolidate migration-specific TypeScript scripts in `package.json`. +- Collapse temporary multi-tsconfig setup if fewer files can represent the final workflow. +- Remove temporary lint overrides that were only needed during mixed JS/TS transition. +- Archive or remove migration process docs that are no longer active runbooks. +- Keep only durable compatibility tests; remove one-off transition tests. + +Cleanup acceptance criteria: + +- No loss of runtime behavior coverage. +- No loss of CJS/ESM import compatibility checks. +- No loss of permissive public type-surface regression coverage. + +## Typing Hardening Schedule for Completed Work + +The first five layers are already landed/active as mostly mechanical conversion. Tightening starts now, not after step 16. + +Planned catch-up timing: + +- During TSM-06 through TSM-08: + - opportunistically remove `@ts-nocheck` in already-converted foundation files when those files are touched for service wiring + - prioritize `src/services/base_service.ts` and `src/easypost.ts` first because they influence many downstream modules +- During TSM-09 through TSM-11: + - complete remaining foundation-file `@ts-nocheck` removals + - add explicit method signatures and key object-shape aliases for hydration paths +- Before opening TSM-90: + - all foundation files converted in TSM-04/05 must be `@ts-nocheck` free + - any remaining permissive typing must be intentional and documented + +Required tracking in each TSM-06+ PR summary: + +- `@ts-nocheck` count in `src/` before/after +- deferred typing items carried forward (if any) +- quick note on where permissive typing remains intentional for SDK compatibility + +## Labeling + +Recommended labels: + +- `ts-migration` +- `stacked-pr` +- `compatibility-critical` +- `permissive-types` +- `tsm-00` ... `tsm-91` + +## PR Summary Standards + +Do not leave the pull request template text in place. + +For each stacked PR, replace the template with a concise, PR-specific summary: + +- 1 short paragraph describing what changed in this layer. +- 3-6 bullets listing concrete file/scope changes. +- a short testing section with exact commands run. + +Keep PR summaries brief and communicative: + +- avoid long narrative prose. +- avoid repeating migration context from other docs. +- link to stack PR numbers only when needed for dependency context. + +Suggested title pattern: + +- `TSM-XX: ` + +Suggested body shape: + +- `Summary` +- `Changes in this PR` +- `Testing` +- `Stack Context` (optional, one line) + +## Risk Register + +| Risk | Likelihood | Impact | Mitigation | +| ------------------------------------------------ | ---------- | ------ | ----------------------------------------------------- | +| Hidden runtime behavior drift during conversion | Medium | High | no-refactor rule, baseline snapshots, test parity | +| Type tightening causes consumer compile failures | Medium | High | permissive guardrails, type tests, widen by default | +| Branch drift within stack | Medium | High | frequent `gh stack sync`, single maintainer ownership | +| Declaration output path mistakes at cutover | Medium | High | package smoke tests + TS demo compile | +| CI duration growth slows review loop | Medium | Medium | targeted checks per layer + full checks at cutover | + +## Cutover Gate Checklist (Must Be Green Before TSM-90 Merge) + +- [ ] Layers TSM-00 through TSM-15 are complete and green +- [ ] No remaining `.js` source in `src/` +- [ ] Declarations generated from TS source successfully +- [ ] `types/` no longer required by any script/workflow +- [ ] package consume tests pass for CJS/ESM/TS users + +## Release Readiness Checklist (Before Landing Top PR) + +- [ ] Changelog entry drafted for migration internals and no expected runtime break +- [ ] README updated to reflect TS-source-generated declarations +- [ ] UPGRADE_GUIDE updated if any type-level behavior requires note +- [ ] Post-merge monitoring owner assigned + +## Suggested Execution Cadence + +- Foundation phase: one layer per day +- Conversion phase: one to two layers per day depending on churn +- Cutover phase: one focused layer per day +- Rebase/sync window: at least twice daily + +## Optional Automation Helpers + +- Script to verify layer ownership boundaries by glob before CI. +- Script to compare exported key lists against baseline snapshots. +- Script to ensure no `types/` imports remain after cutover. diff --git a/dev-docs/TS_MIGRATION_PLAN.md b/dev-docs/TS_MIGRATION_PLAN.md new file mode 100644 index 00000000..0fc96d7f --- /dev/null +++ b/dev-docs/TS_MIGRATION_PLAN.md @@ -0,0 +1,457 @@ +# EasyPost Node JS->TS Migration Plan + +## Purpose + +Migrate the codebase from JavaScript source + separate declaration files to TypeScript source while: + +- Preserving runtime behavior and public API compatibility. +- Preserving permissive typing philosophy for SDK consumers. +- Removing the standalone `types/` source-of-truth by the end of migration. +- Delivering work as small, reviewable native GitHub stacked pull requests. + +Execution details for branch naming, stack workflow, and PR ordering are documented in `TS_MIGRATION_EXECUTION_BOARD.md`. + +## Current State Summary + +- Runtime source is JavaScript under `src/`. +- Types are maintained separately under `types/` and published via package exports. +- Build is Vite-based and emits both CJS and ESM artifacts. +- CI includes build, node compatibility, tests, lint, coverage, and a TypeScript check for declaration files. + +Implication: the repo currently has dual maintenance burden (runtime JS + parallel type declarations). + +## GitHub Stacks Adoption Model + +This migration uses GitHub native stacked pull requests (public preview) and the `gh stack` CLI extension. + +- Bottom PR targets trunk (`master`). +- Every higher PR targets the branch directly below it. +- No integration branch is used. +- The stack is managed as one dependency chain. +- Rebase/sync is handled with stack-aware workflows (`gh stack sync` / `gh stack rebase`). + +Merge behavior alignment: + +- If the goal is to land the entire migration stack in one action, merge from the top PR (or use stack merge up to top). +- Pull requests still merge bottom-up logically as part of the stack operation. +- If only a lower PR is merged, higher PRs stay open and are automatically re-targeted/rebased by stack mechanics. + +## Migration Principles + +### Backwards Compatibility + +- Keep package name, import paths, exports, and runtime object/service behavior unchanged. +- Keep CJS + ESM outputs and file names (`dist/easypost.js` and `dist/easypost.mjs`). +- Avoid introducing runtime validation that rejects currently accepted input unless explicitly approved as breaking. +- Preserve support matrix for Node versions currently validated in CI. +- Keep public method names, parameter ordering, and return semantics stable. +- Keep all test assertions and code the same, they serve as the source of truth the migration worked properly. + +### Permissive Type Philosophy + +- Maintain broad input and extension points where API payloads are variable. +- Prefer `unknown` over `any` by default at boundaries, but allow targeted `any` escape hatches where required for compatibility/extensibility. +- Use optional fields and index signatures for dynamic API object shapes. +- Strongly type stable contracts (service names, IDs, known envelopes) while leaving long-tail API fields permissive. +- Minimize consumer breakage from stricter typing; widen instead of narrowing when in doubt. + +### Delivery and Risk + +- Use one stacked PR chain with small, independent review scope per layer. +- Keep each PR behavior-preserving and green in CI. +- Defer broad refactors until after full TS compilation parity is established. +- Avoid side branches that bypass stack ordering. +- Open stack PRs as drafts by default and mark ready only after layer checks pass. +- Replace PR template boilerplate with concise PR-specific summaries for each layer. + +## Target End State + +- `src/` fully migrated to `.ts` (and `.mts`/`.cts` only if needed). +- Type declarations generated from TypeScript source during build (`dist/*.d.ts` and maps as needed). +- `types/` directory removed from source control. +- `package.json` `types`/`exports.types` point to generated declaration output in `dist`. +- CI validates TS source compilation and type generation directly from runtime source. + +## Non-Goals + +- No intentional API redesign. +- No large behavioral refactors bundled with migration. +- No strict domain modeling of every API field if that harms permissiveness or compatibility. + +## High-Level Workstreams + +1. Tooling + build pipeline modernization for TS source support. +2. Type strategy + compatibility policy implementation. +3. Incremental source conversion (`src/` modules). +4. Test and CI adaptation. +5. Packaging/export transition from `types/` to generated declarations. +6. Cleanup and stabilization. + +## Stacked PR Plan (Small Chunks) + +The following plan is implemented as one ordered GitHub stack. Each PR is a layer in the same chain. + +Operational note: use `gh stack submit --auto` during creation/updates so new stack PRs open as drafts by default. + +### PR 0 - Baseline Safety Net and Telemetry + +Scope: + +- Add migration tracking doc/checklist references. +- Capture baseline behavior snapshots: + - test pass status + - node compatibility pass status + - package surface snapshot (`exports`, `main`, `module`, `types`) +- Add lightweight API-surface verification script (public entry shape smoke check). + +Acceptance criteria: + +- Existing CI remains green. +- Baseline artifacts/scripts available and documented. + +### PR 1 - TS Build Scaffolding (No Source Conversion Yet) + +Scope: + +- Introduce migration `tsconfig` layout for source compilation: + - `tsconfig.base.json` + - `tsconfig.build.json` + - `tsconfig.test.json` (if needed) +- Configure compiler options for permissive migration: + - `allowJs: true` (initially) + - `checkJs: false` (initially) + - `declaration: true` + - `emitDeclarationOnly: false` (or split with declaration emit config) + - conservative strictness profile with targeted opt-outs where needed +- Update lint/parser setup to handle mixed JS/TS source during transition. +- Keep current outputs intact. + +Acceptance criteria: + +- Build succeeds with mixed JS/TS inputs. +- No runtime output changes. +- CI still green. + +### PR 2 - Package/Export Readiness for Generated Types + +Scope: + +- Prepare package metadata for eventual generated declarations in `dist`. +- Keep current `types/` wiring active until cutover PR. +- Add compatibility tests for CJS and ESM import paths. + +Acceptance criteria: + +- No consumer-visible export change yet. +- Compatibility tests pass. + +### PR 3 - Type Philosophy and Guardrails + +Scope: + +- Add `TYPE_STRATEGY.md` (or section in this plan) codifying permissive TS rules: + - when to use `unknown` vs `any` + - allowable index signatures + - widening rules for public API params + - how to represent dynamic API payloads +- Add type tests for representative consumer usage: + - permissive request payloads + - hook middleware flexibility + - common JS-like TS usage patterns + +Acceptance criteria: + +- Type tests pass. +- Rules documented and enforced in review checklist. + +### PR 4 - Convert Core Entry and Shared Infrastructure + +Scope: + +- Convert entrypoint and shared core files first: + - `src/easypost.js` + - `src/constants.js` + - shared utility modules + - core error handler plumbing +- Preserve dynamic behavior in conversion (no logic rewrite). +- Add minimal internal types/interfaces for request/response hooks. + +Acceptance criteria: + +- Runtime tests unchanged and passing. +- Generated declarations for converted modules are correct. +- No public API breaks. + +### PR 5 - Convert Base Service + Dynamic Hydration Layer + +Scope: + +- Convert `src/services/base_service.js` and closely related model base files. +- Model dynamic object hydration using permissive patterns: + - discriminated known keys where stable + - fallback index signatures for unknown object members +- Keep ID-prefix and object-name mapping behavior identical. + +Acceptance criteria: + +- Existing service tests pass unchanged. +- Type signatures remain permissive for dynamic payloads. + +### PR 6-10 - Service Group Conversion Layers + +Scope: + +- Split service conversion into five sequential stack layers: + - PR6 Group A: Address, Parcel, Customs, Shipment + - PR7 Group B: Batch, Order, Pickup, Rate, SmartRate, ScanForm, Refund + - PR8 Group C: CarrierAccount, CarrierType, CarrierMetadata, Billing + - PR9 Group D: Tracker, Event, Webhook, Insurance, Claim + - PR10 Group E: User, ApiKey, Referral/CustomerPortal, EndShipper, Embeddable, Luma, FedExRegistration +- Convert service files and immediate model dependencies only. +- Keep method signatures and behavior stable. + +Acceptance criteria per layer: + +- Layer-level tests pass. +- No regressions in API behavior. +- Declaration output generated from TS for converted modules. + +### PR 11-15 - Model Group Conversion Layers + +Scope: + +- Convert remaining model classes in five sequential layers aligned to Groups A-E. +- Preserve open object shapes and helper methods. +- Keep serialization/deserialization semantics unchanged. + +Acceptance criteria per layer: + +- Model tests and service integration tests pass. +- No runtime shape regressions. + +### PR 90 - Remove Standalone `types/` Source + +Scope: + +- Replace `types/` declaration source with generated declarations from `src/`. +- Remove `types/` from repo. +- Update package metadata: + - `types` path -> `dist/...d.ts` + - `exports["."].types` -> generated path +- Update CI to compile/check generated declarations from source. + +Acceptance criteria: + +- Consumer TS demo/tests pass against generated declarations. +- `types/` directory no longer required. +- Package pack/install smoke tests pass. + +### PR 91 - Strictness Calibration + Cleanup + +Scope: + +- Remove migration-only tsconfig/lint exceptions no longer needed. +- Keep intentional permissive points documented. +- Clean dead code, stale comments, and temporary migration scripts. + +Acceptance criteria: + +- CI fully green. +- Migration checklist complete. + +## Suggested Stack Graph + +```mermaid +graph TD + PR0[PR0 Baseline Safety Net] + PR1[PR1 TS Build Scaffolding] + PR2[PR2 Package Export Readiness] + PR3[PR3 Type Philosophy Guardrails] + PR4[PR4 Core Entry and Shared Infra] + PR5[PR5 Base Service Dynamic Hydration] + PR6[PR6 Services Group A] + PR7[PR7 Services Group B] + PR8[PR8 Services Group C] + PR9[PR9 Services Group D] + PR10[PR10 Services Group E] + PR11[PR11 Models Group A] + PR12[PR12 Models Group B] + PR13[PR13 Models Group C] + PR14[PR14 Models Group D] + PR15[PR15 Models Group E] + PR90[PR90 Remove types dir] + PR91[PR91 Cleanup] + + PR0 --> PR1 --> PR2 --> PR3 --> PR4 --> PR5 --> PR6 --> PR7 --> PR8 --> PR9 --> PR10 --> PR11 --> PR12 --> PR13 --> PR14 --> PR15 --> PR90 --> PR91 +``` + +## Detailed Change Inventory + +### 1) Source File Extension Changes + +- Rename `src/**/*.js` -> `src/**/*.ts` in batches. +- Update all relative imports to extensionless or TS-compatible resolution strategy consistent with build. +- Ensure generated `dist` file names remain unchanged. + +### 2) Build and Compiler + +- Introduce source-compilation tsconfig for `src/`. +- Generate declarations from source into `dist` (or intermediate + copy to dist). +- Keep Vite bundling behavior for CJS/ESM output parity. +- Add/adjust source map configuration parity. + +### 3) Lint and Formatting + +- Ensure ESLint parser/plugin config supports mixed mode then TS-only mode. +- Add rules to avoid accidental over-tightening of public API types. + +### 4) Package Metadata + +- Update `types` and `exports.types` paths to generated declarations. +- Ensure `files`/publish inclusion includes declaration outputs and excludes old `types/` source. + +### 5) CI Workflows + +- Replace declaration-only check with TS source compile + type generation checks. +- Keep node compatibility matrix unchanged. +- Add package smoke test for CJS/ESM/TS consumer install. + +### 6) Tests + +- Keep runtime unit/integration tests unchanged where possible. +- Add type-level consumer tests: + - permissive object payload acceptance + - middleware hook typing flexibility + - common endpoint return value usage + +### 7) Documentation + +- Update README and upgrade guide sections to reflect: + - types are generated from TS source + - permissive type guarantees + - any known typing caveats +- Add migration note for contributors (how to author permissive TS in this repo). + +### 8) Repo Cleanup + +- Remove `types/` directory after cutover. +- Remove obsolete scripts/config specific to old declaration maintenance. +- Verify docs generation still works with `.ts` inputs or update docs tooling config. + +## Compatibility Contract Checklist (Must Pass Before Final Cutover) + +- Public exports unchanged (`import`/`require` behavior). +- Public class/service names unchanged. +- Public method signatures behavior-compatible. +- Runtime response object behavior unchanged (including dynamic fields). +- Node compatibility matrix green. +- Existing tests green. +- TS consumer demo compiles using generated declarations. +- No mandatory code changes for existing JS consumers. + +## Permissive Typing Rules (Concrete) + +Use these defaults unless there is strong evidence a narrower type is needed: + +- Request payload inputs: + - `Record` for open payloads. + - Optional known fields plus index signature for endpoint-specific extras. +- API response objects: + - Known top-level fields typed. + - Additional dynamic properties via `[key: string]: unknown`. +- Middleware/hooks: + - Flexible request/response object interfaces with extensible fields. +- IDs and enums: + - Keep stable ID prefixes and known literal unions where non-breaking. + - Prefer `string` over narrow unions when providers may introduce new values. +- Escape hatches: + - Allow local `any` with comments for compatibility-critical dynamic points. + +## Multi-Agent Execution Plan + +Multiple agents can still help, but stack landing is serialized by design. + +Recommended model: + +1. One stack maintainer agent/person owns branch creation, `gh stack submit`, and `gh stack sync`. +2. Contributor agents prepare patches for upcoming layers against the current top branch tip. +3. Maintainer applies accepted patches in order, one stack layer per PR. +4. Reviewers approve each layer independently in GitHub stack UI. + +Coordination rules: + +- Each PR must include: + - scope statement + - compatibility checklist results + - test evidence + - risk notes +- No integration branch. +- No out-of-order branch creation. + +## PR Template Additions (Recommended) + +For each stacked PR, require: + +- What changed (module list) +- Why safe (compatibility notes) +- Type permissiveness notes (what remained intentionally broad) +- Test evidence (runtime + type) +- Follow-up tasks left for next stack layer + +## Rollback Strategy + +- Because PRs are small and stacked, rollback by reverting from the highest merged layer downward as needed. +- Keep behavior snapshots from PR0 for quick diff-based validation. +- If type breakage appears in consumers, widen types in the nearest layer without runtime changes. + +## Definition of Done + +Migration is complete when: + +- All runtime source under `src/` is TypeScript. +- Declarations are generated from source and published from `dist`. +- `types/` directory is removed. +- Compatibility checklist is fully green. +- Documentation and contributor guidance are updated. +- Stack is fully merged with no unresolved compatibility regressions. + +## Post-Migration Cleanup (Expected) + +Once the migration is complete and stable, evaluate and remove transitional scaffolding that is no longer needed. + +Likely cleanup candidates: + +- Consolidate TypeScript scripts in `package.json` if split commands are no longer necessary. +- Consolidate `tsconfig` files if build/test/type-checking can be expressed with fewer configs. +- Remove temporary migration-only lint exceptions once TypeScript source is the default. +- Re-evaluate migration-only compatibility tests and keep only long-term contract tests. +- Remove migration planning boilerplate from active contributor workflows after rollout. + +Keep long-term: + +- Runtime CJS/ESM compatibility tests. +- Type compatibility tests that protect permissive public SDK behavior. +- Any config explicitly required for dual-module packaging stability. + +## Execution Checklist + +- [ ] PR0 baseline and API surface snapshot +- [ ] PR1 TS scaffolding for mixed-mode build +- [ ] PR2 package/export readiness +- [ ] PR3 permissive typing policy and type tests +- [ ] PR4 core entry + shared infra conversion +- [ ] PR5 base service + hydration conversion +- [ ] PR6 services group A conversion +- [ ] PR7 services group B conversion +- [ ] PR8 services group C conversion +- [ ] PR9 services group D conversion +- [ ] PR10 services group E conversion +- [ ] PR11 models group A conversion +- [ ] PR12 models group B conversion +- [ ] PR13 models group C conversion +- [ ] PR14 models group D conversion +- [ ] PR15 models group E conversion +- [ ] PR90 remove `types/`, switch to generated declarations +- [ ] PR91 cleanup and strictness calibration +- [ ] README/UPGRADE docs updated +- [ ] final package smoke tests for CJS/ESM/TS consumers diff --git a/dev-docs/TYPE_STRATEGY.md b/dev-docs/TYPE_STRATEGY.md new file mode 100644 index 00000000..abd81db9 --- /dev/null +++ b/dev-docs/TYPE_STRATEGY.md @@ -0,0 +1,56 @@ +# Type Strategy (Permissive-First) + +## Goal + +As the source migrates from JavaScript to TypeScript, keep the SDK permissive and backwards-compatible for consumers while improving maintainability for contributors. + +## Core Rules + +1. Prefer permissive API boundaries. + +- Request payloads should allow extension fields. +- Response objects should model known fields and allow unknown extras. + +1. Prefer `unknown` over `any` by default. + +- Use `unknown` for data that must be narrowed before use. +- Use `any` only at compatibility-critical dynamic seams (middleware adapters, opaque third-party objects), with a short justification comment. + +1. Widen rather than narrow when uncertain. + +- If a strict type risks breaking existing consumers, choose the wider compatible type in migration PRs. +- Track stricter candidates as follow-up work, not migration blockers. + +1. Keep runtime behavior unchanged. + +- No new runtime schema enforcement during migration. +- Typing changes must not alter accepted payloads or returned object shapes. + +## Recommended Patterns + +1. Input payloads + +- Use known optional fields plus an index signature for open-ended payloads. +- Use `Record` for opaque payload passthroughs. + +1. Output payloads + +- Type stable fields explicitly. +- Add `[key: string]: unknown` for provider-specific or future fields. + +1. Middleware and hooks + +- Keep callback inputs broad enough for current and future adapters. +- Preserve pass-through support for wrapper libraries. + +1. Enums and literals + +- Avoid over-constraining values that may expand server-side. +- Prefer `string` where provider values are not contractually closed. + +## Review Checklist + +- Does this type change preserve existing consumer call patterns? +- Does it avoid rejecting currently valid payloads? +- Is any `any` usage localized and justified? +- Are dynamic fields still representable without unsafe casts at call sites? diff --git a/package.json b/package.json index 0134f858..af1fcb82 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,10 @@ "scan": "npx audit-ci -m --config ./audit-ci.jsonc", "test": "cross-env NODE_ENV=test vitest run", "test:node-compatibility": "cross-env NODE_ENV=test node ./test/node_compatibility", - "typescript": "npm run typescript:declarations && npm run typescript:source", + "typescript": "npm run typescript:declarations && npm run typescript:source && npm run typescript:compat", "typescript:declarations": "npx tsc -p tsconfig.json", "typescript:source": "npx tsc -p tsconfig.build.json", + "typescript:compat": "npx tsc -p tsconfig.type-tests.json", "watch": "vite build --watch" }, "dependencies": { diff --git a/test/types/permissive_types.ts b/test/types/permissive_types.ts new file mode 100644 index 00000000..7232c7cf --- /dev/null +++ b/test/types/permissive_types.ts @@ -0,0 +1,32 @@ +import EasyPostClient from '../..'; + +const client = new EasyPostClient('api-key', { + requestMiddleware: (request: any) => request, +}); + +client.addRequestHook((config: { requestBody: unknown }) => { + const body: unknown = config.requestBody; + void body; +}); + +client.addResponseHook((config: { responseBody: unknown }) => { + const body: unknown = config.responseBody; + void body; +}); + +const permissivePayload: Record = { + shipment: { + to_address: { + city: 'Austin', + state: 'TX', + zip: '73301', + country: 'US', + custom_provider_field: true, + }, + }, + custom_top_level_field: { + source: 'type-test', + }, +}; + +client.makeApiCall('post', '/shipments', permissivePayload); diff --git a/tsconfig.type-tests.json b/tsconfig.type-tests.json new file mode 100644 index 00000000..29da741b --- /dev/null +++ b/tsconfig.type-tests.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["test/types/**/*.ts"] +}