Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
# Changelog

## Version 30

### v30.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.
- Breaking change: The generated `Client::provide()` method returns a `[statusCode, body]` tuple
instead of the bare body:
- The new `EncodedResponse` interface maps each endpoint to a discriminated union of
`[StatusCode, Body]` tuples, with the HTTP status code as the discriminant;
- Use destructuring with a status check to narrow the body type:

```typescript
const [status, body] = await client.provide("get /v1/user/retrieve", {
id: "10",
});

if (status === 200) {
body; // narrowed down to { name: string }
} else if (status === 400) {
body; // narrowed down to { message: string }
}
```

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

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

- const response = await client.provide("post /v1/user/create", { name: "John Doe" });
+ const [status, response] = await client.provide("post /v1/user/create", { name: "John Doe" });
```

## Version 29

### v29.0.0
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,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 @@ -914,8 +914,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 @@ -930,17 +930,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 @@ -1155,7 +1155,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 @@ -32,12 +32,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!" });
});
});
});
2 changes: 1 addition & 1 deletion compat-test/migration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import { describe, test, expect } from "vitest";
describe("Migration", () => {
test("should migrate", async () => {
const fixed = await readFile("./sample.ts", "utf-8");
expect(fixed).toContain(`new Integration({});`);
expect(fixed).toContain("legacyResultHandler");
});
});
2 changes: 1 addition & 1 deletion compat-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"private": true,
"scripts": {
"pretest": "echo 'await Integration.create({});' > sample.ts",
"pretest": "echo 'import { defaultResultHandler } from \"express-zod-api\";' > sample.ts",
"test": "eslint --fix && vitest --run",
"posttest": "rm sample.ts"
},
Expand Down
7 changes: 1 addition & 6 deletions compat-test/quick-start.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,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 @@ -32,12 +32,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!" });
});
});
});
Loading