Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b00556b
breaking(defaultResultHandler): rm status wrapper.
RobinTail May 23, 2026
bc48740
fix(test): updating the unit tests accordingly.
RobinTail May 23, 2026
f8ce732
Updating generated example and its test.
RobinTail May 23, 2026
59dbbf0
Merge branch 'master' into simpler-default-result
RobinTail May 23, 2026
7f30d47
fix(test): updating integration tests.
RobinTail May 23, 2026
d939891
feat(docs): Updating Readme and introducing v29 into Changelog.
RobinTail May 23, 2026
6507f91
fix(plan): improving the client plan.
RobinTail May 23, 2026
c1ab667
feat: new client types and implementation.
RobinTail May 23, 2026
a3db4a1
fix(client): rm return type since casting of tuple is necessary.
RobinTail May 23, 2026
bfa7791
fix(test): deconstruction.
RobinTail May 23, 2026
6190a68
fix(plan): better migration plan.
RobinTail May 24, 2026
a3d87c6
fix(plan): better migration plan (2).
RobinTail May 24, 2026
99b74ee
feat(migration): basic migration strategy.
RobinTail May 24, 2026
63dcaaf
fix(migration): rm redundant const.
RobinTail May 24, 2026
da01402
fix(migration): assertions instead of assumption.
RobinTail May 24, 2026
de7a5c5
fix(migration): rm indent.
RobinTail May 25, 2026
afb580b
fix(migration): static legacyHandlerCode.
RobinTail May 25, 2026
9b37e7b
fix(migration): fix type and name for node.imported.
RobinTail May 25, 2026
01da287
fix(migration): single esquery for legacyImport.
RobinTail May 25, 2026
de12bc4
fix(migration): simpler replacement.
RobinTail May 25, 2026
e6607d0
fix(migration): AST-based check for zod import presence.
RobinTail May 25, 2026
cb79334
fix(migration): AST check for existing imports of the framework.
RobinTail May 25, 2026
437d9bf
fix(migration): mv EndpointsFactory import check into the needed impo…
RobinTail May 25, 2026
01bc383
fix(migration): extracted getRangeWithComma.
RobinTail May 25, 2026
0c5ed21
fix(migration): mv specifiers handling.
RobinTail May 25, 2026
ecfedc9
fix(migration): reduce const.
RobinTail May 25, 2026
e086cfe
fix(migration): reduce const.
RobinTail May 25, 2026
088cc62
Merge branch 'master' into simpler-default-result
RobinTail Jul 3, 2026
de1a4c1
Merge branch 'master' into simpler-default-result
RobinTail Jul 5, 2026
cb73824
Merge branch 'master' into simpler-default-result
RobinTail Jul 8, 2026
9091f48
Merge branch 'master' into simpler-default-result
RobinTail Jul 28, 2026
ea12bb6
fix: rm buildUnionOrSingle, simpler unions.
RobinTail Jul 28, 2026
96277aa
Changelog: add usage example for the new Client::provide().
RobinTail Jul 28, 2026
374e12d
Merge branch 'master' into simpler-default-result
RobinTail Jul 29, 2026
eab4aea
Merge branch 'master' into simpler-default-result
RobinTail Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## Version 29

### v29.0.0

- Breaking change: The `defaultResultHandler` no longer wraps endpoint output in
`{ status: "success", data: … }` and error messages in `{ status: "error", error: { message: … } }`:
- On success, the endpoint output is sent as-is (bare JSON object);
- On error, the body is `{ message: … }` (without `error` wrapper and `status` field);
- The HTTP status code (`200` vs `4xx`/`5xx`) already discriminates success vs error, making the
`status` string redundant;
- `DefaultResponse<OUT>` type is simplified to `OUT | { message: string }`;
- `arrayResultHandler` is not affected — it already returns bare arrays and plain-text errors.

```diff
- { "status": "success", "data": { "greetings": "Hello!" } }
+ { "greetings": "Hello!" }

- { "status": "error", "error": { "message": "Not found" } }
+ { "message": "Not found" }
```

## Version 28

### v28.1.0
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ curl -L -X GET 'localhost:8090/v1/hello?name=Rick'
You should receive the following response:

```json
{ "status": "success", "data": { "greetings": "Hello, Rick. Happy coding!" } }
{ "greetings": "Hello, Rick. Happy coding!" }
```

# Basic features
Expand Down Expand Up @@ -869,8 +869,8 @@ The `defaultResultHandler` sets the HTTP status code and ensures the following t

```ts
type DefaultResponse<OUT> =
| { status: "success"; data: OUT } // Positive response
| { status: "error"; error: { message: string } }; // or Negative response
| OUT // Positive response
| { message: string }; // or Negative response
```

You can create your own result handler by using this example as a template:
Expand All @@ -885,17 +885,17 @@ import {

const yourResultHandler = new ResultHandler({
positive: (data) => ({
schema: z.object({ data }),
schema: data,
mimeType: "application/json", // optinal or array
}),
negative: z.object({ error: z.string() }),
negative: z.object({ message: z.string() }),
handler: ({ error, input, output, request, response, logger }) => {
if (error) {
const { statusCode } = ensureHttpError(error);
const message = getMessageFromError(error);
return void response.status(statusCode).json({ error: message });
return void response.status(statusCode).json({ message });
}
response.status(200).json({ data: output });
response.status(200).json(output);
},
});
```
Expand Down Expand Up @@ -1108,7 +1108,7 @@ test("should respond successfully", async () => {
expect(loggerMock._getLogs().error).toHaveLength(0);
expect(responseMock._getStatusCode()).toBe(200);
expect(responseMock._getHeaders()).toHaveProperty("x-custom", "one"); // lower case!
expect(responseMock._getJSONData()).toEqual({ status: "success" });
expect(responseMock._getJSONData()).toEqual({ greetings: "Hello, World" });
});
```

Expand Down
7 changes: 1 addition & 6 deletions cjs-test/quick-start.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ describe("CJS Test", async () => {
);
expect(response.status).toBe(200);
const json = await response.json();
expect(json).toEqual({
status: "success",
data: {
greetings: "Hello, Rick. Happy coding!",
},
});
expect(json).toEqual({ greetings: "Hello, Rick. Happy coding!" });
});
});
});
7 changes: 1 addition & 6 deletions compat-test/quick-start.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,7 @@ describe("ESM Test", async () => {
);
expect(response.status).toBe(200);
const json = await response.json();
expect(json).toEqual({
status: "success",
data: {
greetings: "Hello, Rick. Happy coding!",
},
});
expect(json).toEqual({ greetings: "Hello, Rick. Happy coding!" });
});
});
});
7 changes: 1 addition & 6 deletions esm-test/quick-start.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,7 @@ describe("ESM Test", async () => {
);
expect(response.status).toBe(200);
const json = await response.json();
expect(json).toEqual({
status: "success",
data: {
greetings: "Hello, Rick. Happy coding!",
},
});
expect(json).toEqual({ greetings: "Hello, Rick. Happy coding!" });
});
});
});
187 changes: 80 additions & 107 deletions example/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Example > Client > Issue #2177: should handle path params correctly 1`] = `
{
"data": {
[
200,
{
"createdAt": "2022-01-22T00:00:00.000Z",
"name": "Alan Turing",
},
"status": "success",
}
]
`;

exports[`Example > Client > Should perform the request with a positive response 1`] = `
{
"data": {
[
200,
{
"features": [
{
"features": [
Expand Down Expand Up @@ -42,97 +43,75 @@ exports[`Example > Client > Should perform the request with a positive response
"id": 10,
"name": "John Doe",
},
"status": "success",
}
]
`;

exports[`Example > Negative > GET request should fail on missing input param 1`] = `
{
"error": {
"message": "id: Invalid input: expected string, received undefined",
},
"status": "error",
"message": "id: Invalid input: expected string, received undefined",
}
`;

exports[`Example > Negative > PATCH request should fail on schema validation 1`] = `
{
"error": {
"message": "id: should be greater than or equal to 0",
},
"status": "error",
"message": "id: should be greater than or equal to 0",
}
`;

exports[`Example > Negative > Should fail to upload if the file is too large 1`] = `
{
"error": {
"message": "The file is too large",
},
"status": "error",
"message": "The file is too large",
}
`;

exports[`Example > Negative > Should respond with error to the missing static file request 1`] = `
{
"error": {
"message": "Can not GET /public/missing.svg",
},
"status": "error",
"message": "Can not GET /public/missing.svg",
}
`;

exports[`Example > Positive > Should accept raw data 0 1`] = `
{
"data": {
"length": 48687,
},
"status": "success",
"length": 48687,
}
`;

exports[`Example > Positive > Should accept raw data 1 1`] = `
{
"data": {
"length": 48687,
},
"status": "success",
"length": 48687,
}
`;

exports[`Example > Positive > Should handle valid GET request 1`] = `
{
"data": {
"features": [
{
"features": [
{
"title": "Above 180cm",
},
],
"title": "Tall",
},
{
"title": "Young",
},
{
"features": [
{
"features": [
{
"title": "About Typescript",
},
],
"title": "Tells funny jokes",
},
],
"title": "Cute",
},
],
"id": 50,
"name": "John Doe",
},
"status": "success",
"features": [
{
"features": [
{
"title": "Above 180cm",
},
],
"title": "Tall",
},
{
"title": "Young",
},
{
"features": [
{
"features": [
{
"title": "About Typescript",
},
],
"title": "Tells funny jokes",
},
],
"title": "Cute",
},
],
"id": 50,
"name": "John Doe",
}
`;

Expand Down Expand Up @@ -175,46 +154,43 @@ exports[`Example > Positive > Should respond with array (legacy API ResultHandle

exports[`Example > Positive > Should respond with paginated list (ez.paginated) 1`] = `
{
"data": {
"limit": 20,
"offset": 0,
"total": 8,
"users": [
{
"name": "Maria Merian",
"role": "manager",
},
{
"name": "Mary Anning",
"role": "operator",
},
{
"name": "Marie Skłodowska Curie",
"role": "admin",
},
{
"name": "Henrietta Leavitt",
"role": "manager",
},
{
"name": "Lise Meitner",
"role": "operator",
},
{
"name": "Alice Ball",
"role": "admin",
},
{
"name": "Gerty Cori",
"role": "manager",
},
{
"name": "Helen Taussig",
"role": "operator",
},
],
},
"status": "success",
"limit": 20,
"offset": 0,
"total": 8,
"users": [
{
"name": "Maria Merian",
"role": "manager",
},
{
"name": "Mary Anning",
"role": "operator",
},
{
"name": "Marie Skłodowska Curie",
"role": "admin",
},
{
"name": "Henrietta Leavitt",
"role": "manager",
},
{
"name": "Lise Meitner",
"role": "operator",
},
{
"name": "Alice Ball",
"role": "admin",
},
{
"name": "Gerty Cori",
"role": "manager",
},
{
"name": "Helen Taussig",
"role": "operator",
},
],
}
`;

Expand All @@ -226,9 +202,6 @@ exports[`Example > Positive > Should stream an image with a correct header 1`] =

exports[`Example > Protocol > Issue #2706: Should handle parser failures but retain CORS headers 1`] = `
{
"error": {
"message": StringMatching /Unterminated string in JSON at position 14/,
},
"status": "error",
"message": StringMatching /Unterminated string in JSON at position 14/,
}
`;
Loading