Skip to content

Extend api contracts with route contract#890

Open
CatchMe2 wants to merge 46 commits intomainfrom
extend-api-contracts-with-route-contract
Open

Extend api contracts with route contract#890
CatchMe2 wants to merge 46 commits intomainfrom
extend-api-contracts-with-route-contract

Conversation

@CatchMe2
Copy link
Copy Markdown
Contributor

@CatchMe2 CatchMe2 commented Mar 13, 2026

Changes

Summary

  • Introduces defineRouteContract — a new way to define contracts that unifies REST and SSE use-cases
  • Adds sendByRouteContract to both backend-http-client and frontend-http-client, and injectByRouteContract + defineFastifyRoute to fastify-api-contracts, so the same contract drives HTTP
    clients and Fastify route registration end-to-end
  • Ships inferTypes helpers (InferSuccessResponse, InferSuccessSchema, HasAnyNonJsonSuccessResponse) for consuming contract response types in application code; introduces ContractNoBody / ContractNonJsonResponse symbols for non-JSON and empty-body scenarios
  • Deprecates buildContract / buildRestContract, buildFastifyRoute, and injectByContract in favour of the new route-contract-first pattern; updates all three package READMEs accordingly

Old vs new contract definition

The old buildContract / buildRestContract API threaded every aspect of the route through explicit generic type parameters. A typical payload route required up to 9 type parameters just to get full inference:

  // Old
  const contract = buildRestContract({                                                                                                                                                                    
    method: 'post',                                                                                                                                                                                       
    successResponseBodySchema: ResponseSchema,                                                                                                                                                            
    requestBodySchema: RequestBodySchema,                                                                                                                                                                 
    requestPathParamsSchema: PathSchema,                                                                                                                                                                  
    requestQuerySchema: QuerySchema,                                                                                                                                                                      
    isEmptyResponseExpected: false,                                                                                                                                                                       
    isNonJSONResponseExpected: false,                                                                                                                                                                     
    pathResolver: ({ projectId }) => `/projects/${projectId}/keys`,                                                                                                                                       
})                                                                                                                                                                                                      

The response type lived on a single successResponseBodySchema field with no per-status-code granularity, and flags like isEmptyResponseExpected / isNonJSONResponseExpected had to be specified as both a runtime value and a boolean generic to keep inference correct.

The new defineRouteContract drops all explicit generics. Types are inferred entirely from the shape of the object you pass in — the same DX, with significantly less noise:

// New

  const contract = defineRouteContract({
    method: 'post',
    requestBodySchema: RequestBodySchema,
    requestPathParamsSchema: PathSchema,
    requestQuerySchema: QuerySchema,
    pathResolver: ({ projectId }) => `/projects/${projectId}/keys`,                                                                                                                                       
    responseSchemasByStatusCode: {                                                                                                                                                                        
      200: ResponseSchema,                                                                                                                                                                                
      204: ContractNoBody,                                                                                                                                                                                
    },            
  })                                                                                                                                                                                                      

Per-status-code responses are first-class, and ContractNoBody / ContractNonJsonResponse symbols replace the boolean flags entirely — no generics, no duplication.

▎ Note: The new API achieves the same level of type safety without being heavily dependent on explicit generics. This results in cleaner contract definitions that are easier to read, write, and maintain, while TypeScript still infers everything correctly.

Checklist

  • Apply one of following labels; major, minor, patch or skip-release
  • I've updated the documentation, or no changes were necessary
  • I've updated the tests, or no changes were necessary

AI Assistance Tracking

We're running a metric to understand where AI assists our engineering work. Please select exactly one of the options below:

Mark "Yes" if AI helped in any part of this work, for example: generating code, refactoring, debugging support,
explaining something, reviewing an idea, or suggesting an approach.

  • Yes, AI assisted with this PR
  • No, AI did not assist with this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: lokalise/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4a4b462a-1365-4706-b7bf-4af4246673d2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request introduces a new defineRouteContract API as the primary pattern for defining HTTP route contracts across the Lokalise API infrastructure. It adds type-safe route contract utilities to the api-contracts package, including support for response body validation, path parameter resolution, and non-JSON responses via ContractNoBody and defineNonJsonResponse. The backend-http-client, frontend-http-client, and fastify-api-contracts packages are updated with new sendByRouteContract and defineFastifyRoute implementations that consume these contracts with full type inference. Deprecated markers are added to older contract builders (buildContract, buildRestContract, buildSseContract) and client functions (sendByContract). Comprehensive test suites validate the new contract pattern, type inference, client behavior, and Fastify integration.

Sequence Diagram(s)

sequenceDiagram
    participant Client as HTTP Client
    participant Contract as RouteContract
    participant PathResolver as Path Resolver
    participant Validator as Zod Validator
    participant HTTP as HTTP Layer
    participant ResponseHandler as Response Handler

    Client->>Contract: sendByRouteContract(contract, params)
    Contract->>PathResolver: Resolve path with pathParams
    PathResolver-->>Contract: Resolved URL path
    Contract->>Validator: Validate pathParams schema
    Validator-->>Contract: Validated params
    Contract->>Validator: Validate query schema
    Validator-->>Contract: Validated query
    Contract->>Validator: Validate body schema (if present)
    Validator-->>Contract: Validated body
    Contract->>HTTP: Send HTTP request (method, URL, headers, query, body)
    HTTP-->>Contract: Receive HTTP response (statusCode, body)
    Contract->>ResponseHandler: Select response schema by statusCode
    ResponseHandler-->>Contract: Response schema
    Contract->>Validator: Validate response body against schema
    Validator-->>Contract: Parsed response
    Contract-->>Client: Return typed result (success or error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • dariacm
  • CarlosGamero
  • DrParanoia
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main change: introducing a new route contract system to the API contracts package. Clear and specific about the primary addition.
Description check ✅ Passed Description includes a comprehensive summary of changes, before/after code examples, and addresses all major additions. However, the release label checkbox is not selected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch extend-api-contracts-with-route-contract

Comment @coderabbitai help to get the list of available commands and usage tips.

}

async function sendResourceChange<
export async function sendResourceChange<
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This file is already quite large, could we extract this newly exported method into an internal utilities file (or something similar) to keep things more manageable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of extracting this method, it might be better to remove the outdated functions that currently take up a large portion of this file. That should significantly reduce its size and keep related logic in one place.

Not sure if we want to remove depreciations within this PR, tho.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure either, maybe we should release it as deprecated to not force migration

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that the existing httpClient file has 6 deprecated functions. Once we remove them, the file itself will be way smaller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, agree on extracting shared code to a util file. Will do.

@@ -0,0 +1,382 @@
import type { Readable } from 'node:stream'
import { ContractNoBody, defineRouteContract } from '@lokalise/api-contracts'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update api-contract version on package.json?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, how is the versioning managed? Do we have to manually bump versions in package.json files?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The release pipeline updates the version based on the PR label, but if you need to adjust the version constraint in this library, that has to be done manually.

Now that I think about it, you’ll most likely need to split this PR into two: one for api-contracts so it can be published independently, and another for the remaining libraries to update their version constraints accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the changes are backward compatible, which means that we will update minor versions. We know the current version, so IMO we can update dependency ranges for all dependent packages in place within the same PR. Wouldn't it work?

type InferSchemaInput,
type InferSuccessResponse,
type RouteContract,
} from '@lokalise/api-contracts'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about package json

@CatchMe2 CatchMe2 added the minor label Mar 23, 2026
@CatchMe2 CatchMe2 marked this pull request as ready for review March 23, 2026 13:44
@CatchMe2 CatchMe2 requested review from a team, dariacm and kibertoad as code owners March 23, 2026 13:44
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
packages/app/fastify-api-contracts/package.json (1)

53-53: ⚠️ Potential issue | 🟠 Major

Replace wildcard dependency version with a bounded range.

Line 53 sets @lokalise/api-contracts to "*", which makes dev builds non-deterministic and can silently pull incompatible releases. Please pin or use a bounded semver range (e.g. ^6.5.3) to keep CI and local behavior stable.

Suggested change
-        "@lokalise/api-contracts": "*",
+        "@lokalise/api-contracts": "^6.5.3",
#!/bin/bash
set -euo pipefail

# 1) Find wildcard specs for this dependency across the repo
rg -n '"@lokalise/api-contracts"\s*:\s*"\*"' --glob '**/package.json'

# 2) Show all declared ranges for this dependency (package manifests)
rg -n '"@lokalise/api-contracts"\s*:\s*"' --glob '**/package.json'

# 3) Check whether lockfiles exist to understand current pinning behavior
fd '^(pnpm-lock.yaml|package-lock.json|yarn.lock)$' -H
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/fastify-api-contracts/package.json` at line 53, Replace the
wildcard dependency for "@lokalise/api-contracts" in
packages/app/fastify-api-contracts/package.json with a bounded semver range (for
example "^6.5.3") to avoid non-deterministic installs; locate the dependency
entry for "@lokalise/api-contracts" in that package.json, change the version
string from "*" to a specific range, then run the package manager install
(pnpm/yarn/npm) to update the lockfile so CI and local builds remain
deterministic.
🧹 Nitpick comments (4)
packages/app/fastify-api-contracts/src/types.ts (1)

22-28: Consider reusing this alias to remove duplicate handler type definitions.

FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn can be expressed via FastifyHandlerMethod to keep one canonical RouteHandlerMethod shape.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/fastify-api-contracts/src/types.ts` around lines 22 - 28,
Replace the duplicated handler type definitions by expressing
FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn in terms of the existing
FastifyHandlerMethod alias: update the type declarations for
FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn to reference
FastifyHandlerMethod<...> with the appropriate type parameters (ReplyType,
BodyType, ParamsType, QueryType, HeadersType) so they share the canonical
RouteHandlerMethod shape defined by FastifyHandlerMethod; locate the current
FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn declarations and change
them to use FastifyHandlerMethod instead of re-declaring RouteHandlerMethod/
FastifyContractRouteInterface.
packages/app/api-contracts/src/route-contract/inferTypes.ts (1)

9-16: Comment mentions ContractNonJsonResponseType which doesn't exist.

The JSDoc comment on line 12 mentions ContractNonJsonResponseType but the actual type used is TypedNonJsonResponse. Consider updating the comment for accuracy:

 /**
  * Maps sentinels to their effective Zod schema:
  * - TypedNonJsonResponse<S> → S (the inner schema)
- * - ContractNoBodyType / ContractNonJsonResponseType → undefined
+ * - ContractNoBodyType → undefined
  * - z.Schema → preserved as-is
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/api-contracts/src/route-contract/inferTypes.ts` around lines 9 -
16, Update the JSDoc for the ToZodSchema type to accurately reflect the actual
sentinel types used in the code: replace the incorrect mention of
ContractNonJsonResponseType with the real sentinel TypedNonJsonResponse and
ensure ContractNoBodyType and z.Schema are described correctly; reference the
ToZodSchema type and the TypedNonJsonResponse sentinel so the comment matches
the conditional mapping implemented in the type.
packages/app/backend-http-client/src/client/sendByRouteContract.ts (1)

64-85: Multiple @ts-expect-error comments indicate type inference limitations.

The "FixMe" annotations suggest these are known type inference gaps. While functional, these comments accumulate tech debt. Consider creating a tracking issue for improving the types if not already tracked.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/backend-http-client/src/client/sendByRouteContract.ts` around
lines 64 - 85, The multiple `@ts-expect-error` comments around the calls to
sendResourceChange and sendNonPayload (using method, path, params,
responseSchema, options) hide real typing gaps; replace them by giving params a
correct type or narrowing/casting it before use (e.g. define a RequestParams
type with body, headers, queryParams and ensure the function signature or local
variable uses that type, or add proper overloads for
sendResourceChange/sendNonPayload) so you can remove the `@ts-expect-error` lines;
if you can't fix it now, create a tracking issue and add a concise TODO
referencing sendResourceChange and sendNonPayload to avoid accumulating tech
debt.
packages/app/backend-http-client/src/client/sendByRouteContract.spec.ts (1)

189-203: Contract defines 201 but mock returns 200.

The contract specifies responseSchemasByStatusCode: { 201: responseSchema } but the mock server returns status 200. While this doesn't break the test (validation passes because response body matches), it's inconsistent and could mask issues if status-code-specific response handling is later implemented.

📝 Suggested fix
-      await mockServer.forPost('/products').thenJson(200, { id: 21 }, JSON_HEADERS)
+      await mockServer.forPost('/products').thenJson(201, { id: 21 }, JSON_HEADERS)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/backend-http-client/src/client/sendByRouteContract.spec.ts`
around lines 189 - 203, The test's contract expects a 201 response
(responseSchemasByStatusCode: { 201: responseSchema }) but the mock server
returns 200 via mockServer.forPost('/products').thenJson(200, ...), causing an
inconsistency; update the mock or the contract so they match—either change
mockServer.forPost('/products').thenJson to return 201 or add a 200 schema to
responseSchemasByStatusCode—so sendByRouteContract validation and future
status-specific logic are aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/backend-http-client/src/client/sendByRouteContract.spec.ts`:
- Around line 309-316: The test suite recreates the client each test via
buildClient but never closes it; add an afterEach that checks for and awaits the
client shutdown to prevent resource leaks (e.g., await client.close() or await
client.stop() if close() is not available). Update the same test file’s describe
block that uses buildClient and the client variable to perform cleanup in
afterEach (guard with if (client && typeof client.close === 'function') await
client.close()) so each test properly disposes the previous client before the
next runs.
- Around line 33-36: Tests create a new undici client each beforeEach via
buildClient assigned to client but never close the previous instance; modify the
test teardown to close the existing client to avoid leaking sockets—either call
client.close() (or await client.close() if async) at the start of beforeEach
before reassigning, or add an afterEach/afterAll hook that checks if client is
set and closes it; update references to client and buildClient in
sendByRouteContract.spec.ts accordingly and ensure mockServer.reset remains
called.

In `@packages/app/frontend-http-client/src/sendByRouteContract.spec.ts`:
- Around line 305-320: The test has a mismatch between the declared TypeScript
type (expectTypeOf(result).toEqualTypeOf<undefined>()) and the runtime assertion
(expect(result).toBeNull()) for the ContractNoBody case; update the assertion in
the spec to match the declared type by replacing the runtime check with
expect(result).toBeUndefined() (or alternatively, if you prefer null semantics,
change the ContractNoBody/type in sendByRouteContract to return null and update
the type assertion to <null>), locating the test around the sendByRouteContract
call that uses defineRouteContract and ContractNoBody in
sendByRouteContract.spec.ts.

In `@packages/app/frontend-http-client/src/sendByRouteContract.ts`:
- Around line 77-79: The function currently returns null when responseSchema ===
ContractNoBody, causing a runtime/type mismatch because InferSuccessResponse for
ContractNoBody expects undefined; update the runtime to return undefined instead
of null in the responseSchema === ContractNoBody branch (in
sendByRouteContract's handling of ContractNoBody) so the returned value matches
the declared types, and adjust any tests that asserted null to expect undefined.

In `@packages/app/frontend-http-client/src/utils/headersUtils.ts`:
- Around line 3-4: The resolveHeaders implementation applies the nullish
fallback to the Promise when headers is an async factory; change resolveHeaders
to await the factory result before applying ?? {} so undefined resolutions fall
back correctly. Specifically, update the resolveHeaders function (symbol:
resolveHeaders, types: HeadersSource, HeadersObject) to obtain the value via
"const value = typeof headers === 'function' ? await headers() : headers" (or
use Promise.resolve(headers()).then(...)) and then "return value ?? {}", and
mark the function async (or ensure it always returns a resolved Promise) so
callers still get a HeadersObject or Promise<HeadersObject>.

---

Duplicate comments:
In `@packages/app/fastify-api-contracts/package.json`:
- Line 53: Replace the wildcard dependency for "@lokalise/api-contracts" in
packages/app/fastify-api-contracts/package.json with a bounded semver range (for
example "^6.5.3") to avoid non-deterministic installs; locate the dependency
entry for "@lokalise/api-contracts" in that package.json, change the version
string from "*" to a specific range, then run the package manager install
(pnpm/yarn/npm) to update the lockfile so CI and local builds remain
deterministic.

---

Nitpick comments:
In `@packages/app/api-contracts/src/route-contract/inferTypes.ts`:
- Around line 9-16: Update the JSDoc for the ToZodSchema type to accurately
reflect the actual sentinel types used in the code: replace the incorrect
mention of ContractNonJsonResponseType with the real sentinel
TypedNonJsonResponse and ensure ContractNoBodyType and z.Schema are described
correctly; reference the ToZodSchema type and the TypedNonJsonResponse sentinel
so the comment matches the conditional mapping implemented in the type.

In `@packages/app/backend-http-client/src/client/sendByRouteContract.spec.ts`:
- Around line 189-203: The test's contract expects a 201 response
(responseSchemasByStatusCode: { 201: responseSchema }) but the mock server
returns 200 via mockServer.forPost('/products').thenJson(200, ...), causing an
inconsistency; update the mock or the contract so they match—either change
mockServer.forPost('/products').thenJson to return 201 or add a 200 schema to
responseSchemasByStatusCode—so sendByRouteContract validation and future
status-specific logic are aligned.

In `@packages/app/backend-http-client/src/client/sendByRouteContract.ts`:
- Around line 64-85: The multiple `@ts-expect-error` comments around the calls to
sendResourceChange and sendNonPayload (using method, path, params,
responseSchema, options) hide real typing gaps; replace them by giving params a
correct type or narrowing/casting it before use (e.g. define a RequestParams
type with body, headers, queryParams and ensure the function signature or local
variable uses that type, or add proper overloads for
sendResourceChange/sendNonPayload) so you can remove the `@ts-expect-error` lines;
if you can't fix it now, create a tracking issue and add a concise TODO
referencing sendResourceChange and sendNonPayload to avoid accumulating tech
debt.

In `@packages/app/fastify-api-contracts/src/types.ts`:
- Around line 22-28: Replace the duplicated handler type definitions by
expressing FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn in terms of the
existing FastifyHandlerMethod alias: update the type declarations for
FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn to reference
FastifyHandlerMethod<...> with the appropriate type parameters (ReplyType,
BodyType, ParamsType, QueryType, HeadersType) so they share the canonical
RouteHandlerMethod shape defined by FastifyHandlerMethod; locate the current
FastifyPayloadHandlerFn and FastifyNoPayloadHandlerFn declarations and change
them to use FastifyHandlerMethod instead of re-declaring RouteHandlerMethod/
FastifyContractRouteInterface.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: lokalise/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7daac91-3abe-4e10-8050-2fd2f3b4c008

📥 Commits

Reviewing files that changed from the base of the PR and between 42609d3 and 6871af2.

📒 Files selected for processing (31)
  • packages/app/api-contracts/README.md
  • packages/app/api-contracts/src/HttpStatusCodes.ts
  • packages/app/api-contracts/src/contractBuilder.ts
  • packages/app/api-contracts/src/index.ts
  • packages/app/api-contracts/src/rest/restContractBuilder.ts
  • packages/app/api-contracts/src/route-contract/defineRouteContract.spec.ts
  • packages/app/api-contracts/src/route-contract/defineRouteContract.ts
  • packages/app/api-contracts/src/route-contract/inferTypes.spec.ts
  • packages/app/api-contracts/src/route-contract/inferTypes.ts
  • packages/app/api-contracts/src/sse/sseContractBuilders.ts
  • packages/app/backend-http-client/README.md
  • packages/app/backend-http-client/src/client/httpClient.ts
  • packages/app/backend-http-client/src/client/sendByRouteContract.spec.ts
  • packages/app/backend-http-client/src/client/sendByRouteContract.ts
  • packages/app/backend-http-client/src/index.ts
  • packages/app/fastify-api-contracts/README.md
  • packages/app/fastify-api-contracts/package.json
  • packages/app/fastify-api-contracts/src/defineFastifyRoute.spec.ts
  • packages/app/fastify-api-contracts/src/defineFastifyRoute.ts
  • packages/app/fastify-api-contracts/src/fastifyRouteBuilder.ts
  • packages/app/fastify-api-contracts/src/index.ts
  • packages/app/fastify-api-contracts/src/injectByContract.ts
  • packages/app/fastify-api-contracts/src/injectByRouteContract.ts
  • packages/app/fastify-api-contracts/src/types.ts
  • packages/app/frontend-http-client/README.md
  • packages/app/frontend-http-client/src/client.ts
  • packages/app/frontend-http-client/src/index.ts
  • packages/app/frontend-http-client/src/sendByRouteContract.spec.ts
  • packages/app/frontend-http-client/src/sendByRouteContract.ts
  • packages/app/frontend-http-client/src/sse.ts
  • packages/app/frontend-http-client/src/utils/headersUtils.ts


export type CommonRouteContract = {
// biome-ignore lint/suspicious/noExplicitAny: Required for compatibility with generics
pathResolver: RoutePathResolver<any>
Copy link
Copy Markdown
Collaborator

@kibertoad kibertoad Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean we lose type-safety for manual execution of pathResolver on the contract?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be fully typed, because contracts are defined using TypedPathRouteContract
https://github.com/lokalise/shared-ts-libs/pull/890/changes#diff-7588d197d143b886c42286f074f73edd8f5f855a9396b022485e8ba0328e1cf5R49

Here is some more info #890 (comment)

responseHeaderSchema?: z.ZodType
responseSchemasByStatusCode: ResponseSchemasByStatusCode

metadata?: Record<string, unknown>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do I understand correctly that we lose customizability here, users no longer can augment this type globally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not losing anything, the existing contract implementation is identical (but I just inlined the type)

export interface CommonRouteDefinitionMetadata extends Record<string, unknown> {}

export type CommonRouteDefinition<
  ...
  metadata?: CommonRouteDefinitionMetadata
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants