@rexeus/typeweaver-clients 0.7.0 โ†’ 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,7 +28,7 @@ npm install @rexeus/typeweaver-core
28
28
  ## ๐Ÿ’ก How to use
29
29
 
30
30
  ```bash
31
- npx typeweaver generate --input ./api/definitions --output ./api/generated --plugins clients
31
+ npx typeweaver generate --input ./api/spec/index.ts --output ./api/generated --plugins clients
32
32
  ```
33
33
 
34
34
  More on the CLI in
@@ -51,16 +51,11 @@ Resource-specific HTTP clients are generated as `<ResourceName>Client.ts` files,
51
51
  checking
52
52
  - **fetch based** - Zero dependencies, uses the native fetch API. Supports custom fetch functions
53
53
  for middleware and testing
54
- - **Response type mapping** - Each response is automatically mapped to the associated response class
55
- and an instance of the class is returned. This ensures that all responses are in the defined
56
- format and it is type-safe.
54
+ - **Response type mapping** - Each response is automatically mapped to the associated typed response
55
+ object. This ensures that all responses are in the defined format and it is type-safe.
57
56
  - **Unknown response handling**
58
57
  - Unknown properties are automatically removed from the response. If a response exceeds the
59
58
  definition, it is not rejected directly.
60
- - If a response does not match any known format, it will be rejected by default as an
61
- `UnknownResponse` instance.
62
- - This unknown response handling can be configured. It is also possible for an `UnknownResponse`
63
- instance to be created without being thrown.
64
59
 
65
60
  **Using generated clients**
66
61
 
@@ -70,9 +65,7 @@ import { TodoClient } from "path/to/generated/output";
70
65
  const client = new TodoClient({
71
66
  fetchFn: customFetch, // Custom fetch function (optional, defaults to globalThis.fetch)
72
67
  baseUrl: "https://api.example.com", // Base URL for all requests (required)
73
- unknownResponseHandling: "throw", // "throw" | "passthrough" for unknown responses
74
- // -> In "passthrough" mode, the received status code determines if the response is thrown
75
- isSuccessStatusCode: code => code < 400, // Custom success status code predicate, determines whether the response is successful or should be thrown
68
+ timeoutMs: 30_000, // Request timeout in milliseconds (optional)
76
69
  });
77
70
  ```
78
71
 
@@ -83,20 +76,12 @@ Request commands are generated as `<OperationId>RequestCommand.ts` files, e.g.
83
76
 
84
77
  - **Type-safe construction** - Constructor enforces correct request structure
85
78
  - **Complete request encapsulation** - Contains method, path, headers, query parameters, and body
86
- - **Response processing** - Transform raw HTTP responses into typed response objects of the correct
87
- response class
79
+ - **Response processing** - Transform raw HTTP responses into typed response objects
88
80
 
89
81
  ### Basic Usage
90
82
 
91
83
  ```typescript
92
- import {
93
- TodoClient,
94
- CreateTodoRequestCommand,
95
- CreateTodoSuccessResponse,
96
- OtherSuccessResponse,
97
- ValidationErrorResponse,
98
- InternalServerErrorResponse,
99
- } from "path/to/generated/output";
84
+ import { TodoClient, CreateTodoRequestCommand } from "path/to/generated/output";
100
85
 
101
86
  const client = new TodoClient({
102
87
  baseUrl: "https://api.example.com",
@@ -107,30 +92,17 @@ const command = new CreateTodoRequestCommand({
107
92
  body: { title: "New Todo", status: "PENDING" },
108
93
  });
109
94
 
110
- try {
111
- const response = await client.send(command);
112
-
113
- // If there is only one success response,
114
- // you can directly access the response like:
115
- console.log("Success:", response.body);
116
-
117
- // If there are multiple success responses, you can check the instance like:
118
- if (response instanceof CreateTodoSuccessResponse) {
119
- console.log("Todo created successfully:", response.body);
120
- }
121
- if (response instanceof OtherSuccessResponse) {
122
- // ... Handle "OtherSuccessResponse"
123
- }
124
- // ...
125
- } catch (error) {
126
- if (error instanceof ValidationErrorResponse) {
127
- // Handle validation errors
128
- }
129
- if (error instanceof InternalServerErrorResponse) {
130
- // Handle internal server errors
131
- }
132
- // ... Handle other errors
95
+ const response = await client.send(command);
96
+
97
+ // Use the type discriminator to narrow the response type
98
+ if (response.type === "CreateTodoSuccess") {
99
+ console.log("Todo created successfully:", response.body);
100
+ } else if (response.type === "ValidationError") {
101
+ // Handle validation errors
102
+ } else if (response.type === "InternalServerError") {
103
+ // Handle internal server errors
133
104
  }
105
+ // ... Handle other response types
134
106
  ```
135
107
 
136
108
  ## ๐Ÿ“„ License
package/dist/index.cjs CHANGED
@@ -6,16 +6,12 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") {
10
- for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
- key = keys[i];
12
- if (!__hasOwnProp.call(to, key) && key !== except) {
13
- __defProp(to, key, {
14
- get: ((k) => from[k]).bind(null, key),
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- });
17
- }
18
- }
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
19
15
  }
20
16
  return to;
21
17
  };
@@ -23,7 +19,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
19
  value: mod,
24
20
  enumerable: true
25
21
  }) : target, mod));
26
-
27
22
  //#endregion
28
23
  let node_path = require("node:path");
29
24
  node_path = __toESM(node_path);
@@ -32,95 +27,75 @@ let _rexeus_typeweaver_gen = require("@rexeus/typeweaver-gen");
32
27
  let _rexeus_typeweaver_zod_to_ts = require("@rexeus/typeweaver-zod-to-ts");
33
28
  let case$1 = require("case");
34
29
  case$1 = __toESM(case$1);
35
-
36
- //#region src/ClientGenerator.ts
30
+ //#region src/clientGenerator.ts
37
31
  const moduleDir$1 = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
38
- var ClientGenerator = class {
39
- static generate(context) {
40
- const clientTemplatePath = node_path.default.join(moduleDir$1, "templates", "Client.ejs");
41
- const commandTemplatePath = node_path.default.join(moduleDir$1, "templates", "RequestCommand.ejs");
42
- for (const [, entityResource] of Object.entries(context.resources.entityResources)) {
43
- this.writeClient(clientTemplatePath, entityResource.operations, context);
44
- this.writeRequestCommands(commandTemplatePath, entityResource.operations, context);
45
- }
32
+ function generate(context) {
33
+ const clientTemplatePath = node_path.default.join(moduleDir$1, "templates", "Client.ejs");
34
+ const commandTemplatePath = node_path.default.join(moduleDir$1, "templates", "RequestCommand.ejs");
35
+ for (const resource of context.normalizedSpec.resources) {
36
+ writeClient(clientTemplatePath, resource, context);
37
+ writeRequestCommands(commandTemplatePath, resource, context);
46
38
  }
47
- static writeClient(templateFilePath, operationResources, context) {
48
- const entityName = operationResources[0].entityName;
49
- const pascalCaseEntityName = case$1.default.pascal(entityName);
50
- const outputDir = operationResources[0].outputDir;
51
- const operations = [];
52
- for (const operationResource of operationResources) {
53
- const { definition, outputResponseFileName, outputResponseValidationFileName, outputRequestFileName } = operationResource;
54
- const { operationId } = definition;
55
- const pascalCaseOperationId = case$1.default.pascal(operationId);
56
- const requestFile = `./${node_path.default.basename(outputRequestFileName, ".ts")}`;
57
- const responseValidatorFile = `./${node_path.default.basename(outputResponseValidationFileName, ".ts")}`;
58
- const responseFile = `./${node_path.default.basename(outputResponseFileName, ".ts")}`;
59
- operations.push({
60
- operationId,
61
- pascalCaseOperationId,
62
- requestFile,
63
- responseValidatorFile,
64
- responseFile
65
- });
66
- }
67
- const content = context.renderTemplate(templateFilePath, {
68
- coreDir: context.coreDir,
69
- pascalCaseEntityName,
70
- operations
39
+ }
40
+ function writeClient(templateFilePath, resource, context) {
41
+ const pascalCaseEntityName = case$1.default.pascal(resource.name);
42
+ const outputDir = context.getResourceOutputDir(resource.name);
43
+ const operations = resource.operations.map((operation) => {
44
+ const outputPaths = context.getOperationOutputPaths({
45
+ resourceName: resource.name,
46
+ operationId: operation.operationId
71
47
  });
72
- const outputClientFile = node_path.default.join(outputDir, `${pascalCaseEntityName}Client.ts`);
73
- const relativePath = node_path.default.relative(context.outputDir, outputClientFile);
74
- context.writeFile(relativePath, content);
75
- }
76
- static writeRequestCommands(templateFilePath, operationResources, context) {
77
- for (const operationResource of operationResources) this.writeRequestCommand(templateFilePath, operationResource, context);
78
- }
79
- static writeRequestCommand(templateFilePath, operationResource, context) {
80
- const { definition, sourceDir, sourceFile, outputDir, outputResponseFileName, outputResponseValidationFileName, outputRequestFileName } = operationResource;
81
- const { operationId, method, request, responses } = definition;
82
- const pascalCaseOperationId = case$1.default.pascal(operationId);
83
- const allResponses = responses;
84
- const ownSuccessResponses = allResponses.filter((r) => r.statusCode >= 200 && r.statusCode < 300 && !r.isReference);
85
- const ownErrorResponses = allResponses.filter((r) => (r.statusCode < 200 || r.statusCode >= 300) && !r.isReference);
86
- const sharedSuccessResponses = allResponses.filter((r) => r.statusCode >= 200 && r.statusCode < 300 && r.isReference);
87
- const sharedErrorResponses = allResponses.filter((r) => (r.statusCode < 200 || r.statusCode >= 300) && r.isReference);
88
- const headerTsType = request.header ? _rexeus_typeweaver_zod_to_ts.TsTypePrinter.print(_rexeus_typeweaver_zod_to_ts.TsTypeNode.fromZod(request.header)) : void 0;
89
- const paramTsType = request.param ? _rexeus_typeweaver_zod_to_ts.TsTypePrinter.print(_rexeus_typeweaver_zod_to_ts.TsTypeNode.fromZod(request.param)) : void 0;
90
- const queryTsType = request.query ? _rexeus_typeweaver_zod_to_ts.TsTypePrinter.print(_rexeus_typeweaver_zod_to_ts.TsTypeNode.fromZod(request.query)) : void 0;
91
- const bodyTsType = request.body ? _rexeus_typeweaver_zod_to_ts.TsTypePrinter.print(_rexeus_typeweaver_zod_to_ts.TsTypeNode.fromZod(request.body)) : void 0;
92
- const successResponseImportPath = (response) => {
93
- if (response.isReference && response.path) return response.path;
94
- return `./${node_path.default.basename(outputResponseFileName, ".ts")}`;
48
+ return {
49
+ operationId: operation.operationId,
50
+ pascalCaseOperationId: case$1.default.pascal(operation.operationId),
51
+ requestFile: `./${node_path.default.basename(outputPaths.requestFileName, ".ts")}`,
52
+ responseValidatorFile: `./${node_path.default.basename(outputPaths.responseValidationFileName, ".ts")}`,
53
+ responseFile: `./${node_path.default.basename(outputPaths.responseFileName, ".ts")}`
95
54
  };
96
- const requestFile = `./${node_path.default.basename(outputRequestFileName, ".ts")}`;
97
- const responseValidatorFile = `./${node_path.default.basename(outputResponseValidationFileName, ".ts")}`;
98
- const relativeSourceFile = node_path.default.relative(sourceDir, sourceFile);
99
- const sourcePath = node_path.default.join(sourceDir, relativeSourceFile.replace(/\.ts$/, ""));
100
- const relativeSourcePath = node_path.default.relative(outputDir, sourcePath);
101
- const content = context.renderTemplate(templateFilePath, {
102
- sourcePath: relativeSourcePath,
103
- operationId,
104
- pascalCaseOperationId,
105
- method,
106
- headerTsType,
107
- paramTsType,
108
- queryTsType,
109
- bodyTsType,
110
- ownSuccessResponses,
111
- ownErrorResponses,
112
- sharedSuccessResponses,
113
- sharedErrorResponses,
114
- requestFile,
115
- responseValidatorFile,
116
- successResponseImportPath
117
- });
118
- const outputCommandFile = node_path.default.join(outputDir, `${pascalCaseOperationId}RequestCommand.ts`);
119
- const relativePath = node_path.default.relative(context.outputDir, outputCommandFile);
120
- context.writeFile(relativePath, content);
121
- }
122
- };
123
-
55
+ });
56
+ const content = context.renderTemplate(templateFilePath, {
57
+ coreDir: context.coreDir,
58
+ pascalCaseEntityName,
59
+ operations
60
+ });
61
+ const outputClientFile = node_path.default.join(outputDir, `${pascalCaseEntityName}Client.ts`);
62
+ const relativePath = node_path.default.relative(context.outputDir, outputClientFile);
63
+ context.writeFile(relativePath, content);
64
+ }
65
+ function writeRequestCommands(templateFilePath, resource, context) {
66
+ resource.operations.forEach((operation) => {
67
+ writeRequestCommand(templateFilePath, resource.name, operation, context);
68
+ });
69
+ }
70
+ function writeRequestCommand(templateFilePath, resourceName, operation, context) {
71
+ const outputPaths = context.getOperationOutputPaths({
72
+ resourceName,
73
+ operationId: operation.operationId
74
+ });
75
+ const request = operation.request ?? {};
76
+ const pascalCaseOperationId = case$1.default.pascal(operation.operationId);
77
+ const headerTsType = request.header ? (0, _rexeus_typeweaver_zod_to_ts.print)((0, _rexeus_typeweaver_zod_to_ts.fromZod)(request.header)) : void 0;
78
+ const paramTsType = request.param ? (0, _rexeus_typeweaver_zod_to_ts.print)((0, _rexeus_typeweaver_zod_to_ts.fromZod)(request.param)) : void 0;
79
+ const queryTsType = request.query ? (0, _rexeus_typeweaver_zod_to_ts.print)((0, _rexeus_typeweaver_zod_to_ts.fromZod)(request.query)) : void 0;
80
+ const bodyTsType = request.body ? (0, _rexeus_typeweaver_zod_to_ts.print)((0, _rexeus_typeweaver_zod_to_ts.fromZod)(request.body)) : void 0;
81
+ const content = context.renderTemplate(templateFilePath, {
82
+ resourceName,
83
+ specPath: context.getSpecImportPath({ importerDir: outputPaths.outputDir }),
84
+ operationId: operation.operationId,
85
+ pascalCaseOperationId,
86
+ method: operation.method,
87
+ headerTsType,
88
+ paramTsType,
89
+ queryTsType,
90
+ bodyTsType,
91
+ requestFile: `./${node_path.default.basename(outputPaths.requestFileName, ".ts")}`,
92
+ responseValidatorFile: `./${node_path.default.basename(outputPaths.responseValidationFileName, ".ts")}`,
93
+ responseFile: `./${node_path.default.basename(outputPaths.responseFileName, ".ts")}`
94
+ });
95
+ const outputCommandFile = node_path.default.join(outputPaths.outputDir, `${pascalCaseOperationId}RequestCommand.ts`);
96
+ const relativePath = node_path.default.relative(context.outputDir, outputCommandFile);
97
+ context.writeFile(relativePath, content);
98
+ }
124
99
  //#endregion
125
100
  //#region src/index.ts
126
101
  const moduleDir = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
@@ -129,9 +104,8 @@ var ClientsPlugin = class extends _rexeus_typeweaver_gen.BasePlugin {
129
104
  generate(context) {
130
105
  const libDir = node_path.default.join(moduleDir, "lib");
131
106
  this.copyLibFiles(context, libDir, "clients");
132
- ClientGenerator.generate(context);
107
+ generate(context);
133
108
  }
134
109
  };
135
-
136
110
  //#endregion
137
- module.exports = ClientsPlugin;
111
+ module.exports = ClientsPlugin;
package/dist/index.mjs CHANGED
@@ -1,97 +1,77 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { BasePlugin } from "@rexeus/typeweaver-gen";
4
- import { TsTypeNode, TsTypePrinter } from "@rexeus/typeweaver-zod-to-ts";
4
+ import { fromZod, print } from "@rexeus/typeweaver-zod-to-ts";
5
5
  import Case from "case";
6
-
7
- //#region src/ClientGenerator.ts
6
+ //#region src/clientGenerator.ts
8
7
  const moduleDir$1 = path.dirname(fileURLToPath(import.meta.url));
9
- var ClientGenerator = class {
10
- static generate(context) {
11
- const clientTemplatePath = path.join(moduleDir$1, "templates", "Client.ejs");
12
- const commandTemplatePath = path.join(moduleDir$1, "templates", "RequestCommand.ejs");
13
- for (const [, entityResource] of Object.entries(context.resources.entityResources)) {
14
- this.writeClient(clientTemplatePath, entityResource.operations, context);
15
- this.writeRequestCommands(commandTemplatePath, entityResource.operations, context);
16
- }
8
+ function generate(context) {
9
+ const clientTemplatePath = path.join(moduleDir$1, "templates", "Client.ejs");
10
+ const commandTemplatePath = path.join(moduleDir$1, "templates", "RequestCommand.ejs");
11
+ for (const resource of context.normalizedSpec.resources) {
12
+ writeClient(clientTemplatePath, resource, context);
13
+ writeRequestCommands(commandTemplatePath, resource, context);
17
14
  }
18
- static writeClient(templateFilePath, operationResources, context) {
19
- const entityName = operationResources[0].entityName;
20
- const pascalCaseEntityName = Case.pascal(entityName);
21
- const outputDir = operationResources[0].outputDir;
22
- const operations = [];
23
- for (const operationResource of operationResources) {
24
- const { definition, outputResponseFileName, outputResponseValidationFileName, outputRequestFileName } = operationResource;
25
- const { operationId } = definition;
26
- const pascalCaseOperationId = Case.pascal(operationId);
27
- const requestFile = `./${path.basename(outputRequestFileName, ".ts")}`;
28
- const responseValidatorFile = `./${path.basename(outputResponseValidationFileName, ".ts")}`;
29
- const responseFile = `./${path.basename(outputResponseFileName, ".ts")}`;
30
- operations.push({
31
- operationId,
32
- pascalCaseOperationId,
33
- requestFile,
34
- responseValidatorFile,
35
- responseFile
36
- });
37
- }
38
- const content = context.renderTemplate(templateFilePath, {
39
- coreDir: context.coreDir,
40
- pascalCaseEntityName,
41
- operations
15
+ }
16
+ function writeClient(templateFilePath, resource, context) {
17
+ const pascalCaseEntityName = Case.pascal(resource.name);
18
+ const outputDir = context.getResourceOutputDir(resource.name);
19
+ const operations = resource.operations.map((operation) => {
20
+ const outputPaths = context.getOperationOutputPaths({
21
+ resourceName: resource.name,
22
+ operationId: operation.operationId
42
23
  });
43
- const outputClientFile = path.join(outputDir, `${pascalCaseEntityName}Client.ts`);
44
- const relativePath = path.relative(context.outputDir, outputClientFile);
45
- context.writeFile(relativePath, content);
46
- }
47
- static writeRequestCommands(templateFilePath, operationResources, context) {
48
- for (const operationResource of operationResources) this.writeRequestCommand(templateFilePath, operationResource, context);
49
- }
50
- static writeRequestCommand(templateFilePath, operationResource, context) {
51
- const { definition, sourceDir, sourceFile, outputDir, outputResponseFileName, outputResponseValidationFileName, outputRequestFileName } = operationResource;
52
- const { operationId, method, request, responses } = definition;
53
- const pascalCaseOperationId = Case.pascal(operationId);
54
- const allResponses = responses;
55
- const ownSuccessResponses = allResponses.filter((r) => r.statusCode >= 200 && r.statusCode < 300 && !r.isReference);
56
- const ownErrorResponses = allResponses.filter((r) => (r.statusCode < 200 || r.statusCode >= 300) && !r.isReference);
57
- const sharedSuccessResponses = allResponses.filter((r) => r.statusCode >= 200 && r.statusCode < 300 && r.isReference);
58
- const sharedErrorResponses = allResponses.filter((r) => (r.statusCode < 200 || r.statusCode >= 300) && r.isReference);
59
- const headerTsType = request.header ? TsTypePrinter.print(TsTypeNode.fromZod(request.header)) : void 0;
60
- const paramTsType = request.param ? TsTypePrinter.print(TsTypeNode.fromZod(request.param)) : void 0;
61
- const queryTsType = request.query ? TsTypePrinter.print(TsTypeNode.fromZod(request.query)) : void 0;
62
- const bodyTsType = request.body ? TsTypePrinter.print(TsTypeNode.fromZod(request.body)) : void 0;
63
- const successResponseImportPath = (response) => {
64
- if (response.isReference && response.path) return response.path;
65
- return `./${path.basename(outputResponseFileName, ".ts")}`;
24
+ return {
25
+ operationId: operation.operationId,
26
+ pascalCaseOperationId: Case.pascal(operation.operationId),
27
+ requestFile: `./${path.basename(outputPaths.requestFileName, ".ts")}`,
28
+ responseValidatorFile: `./${path.basename(outputPaths.responseValidationFileName, ".ts")}`,
29
+ responseFile: `./${path.basename(outputPaths.responseFileName, ".ts")}`
66
30
  };
67
- const requestFile = `./${path.basename(outputRequestFileName, ".ts")}`;
68
- const responseValidatorFile = `./${path.basename(outputResponseValidationFileName, ".ts")}`;
69
- const relativeSourceFile = path.relative(sourceDir, sourceFile);
70
- const sourcePath = path.join(sourceDir, relativeSourceFile.replace(/\.ts$/, ""));
71
- const relativeSourcePath = path.relative(outputDir, sourcePath);
72
- const content = context.renderTemplate(templateFilePath, {
73
- sourcePath: relativeSourcePath,
74
- operationId,
75
- pascalCaseOperationId,
76
- method,
77
- headerTsType,
78
- paramTsType,
79
- queryTsType,
80
- bodyTsType,
81
- ownSuccessResponses,
82
- ownErrorResponses,
83
- sharedSuccessResponses,
84
- sharedErrorResponses,
85
- requestFile,
86
- responseValidatorFile,
87
- successResponseImportPath
88
- });
89
- const outputCommandFile = path.join(outputDir, `${pascalCaseOperationId}RequestCommand.ts`);
90
- const relativePath = path.relative(context.outputDir, outputCommandFile);
91
- context.writeFile(relativePath, content);
92
- }
93
- };
94
-
31
+ });
32
+ const content = context.renderTemplate(templateFilePath, {
33
+ coreDir: context.coreDir,
34
+ pascalCaseEntityName,
35
+ operations
36
+ });
37
+ const outputClientFile = path.join(outputDir, `${pascalCaseEntityName}Client.ts`);
38
+ const relativePath = path.relative(context.outputDir, outputClientFile);
39
+ context.writeFile(relativePath, content);
40
+ }
41
+ function writeRequestCommands(templateFilePath, resource, context) {
42
+ resource.operations.forEach((operation) => {
43
+ writeRequestCommand(templateFilePath, resource.name, operation, context);
44
+ });
45
+ }
46
+ function writeRequestCommand(templateFilePath, resourceName, operation, context) {
47
+ const outputPaths = context.getOperationOutputPaths({
48
+ resourceName,
49
+ operationId: operation.operationId
50
+ });
51
+ const request = operation.request ?? {};
52
+ const pascalCaseOperationId = Case.pascal(operation.operationId);
53
+ const headerTsType = request.header ? print(fromZod(request.header)) : void 0;
54
+ const paramTsType = request.param ? print(fromZod(request.param)) : void 0;
55
+ const queryTsType = request.query ? print(fromZod(request.query)) : void 0;
56
+ const bodyTsType = request.body ? print(fromZod(request.body)) : void 0;
57
+ const content = context.renderTemplate(templateFilePath, {
58
+ resourceName,
59
+ specPath: context.getSpecImportPath({ importerDir: outputPaths.outputDir }),
60
+ operationId: operation.operationId,
61
+ pascalCaseOperationId,
62
+ method: operation.method,
63
+ headerTsType,
64
+ paramTsType,
65
+ queryTsType,
66
+ bodyTsType,
67
+ requestFile: `./${path.basename(outputPaths.requestFileName, ".ts")}`,
68
+ responseValidatorFile: `./${path.basename(outputPaths.responseValidationFileName, ".ts")}`,
69
+ responseFile: `./${path.basename(outputPaths.responseFileName, ".ts")}`
70
+ });
71
+ const outputCommandFile = path.join(outputPaths.outputDir, `${pascalCaseOperationId}RequestCommand.ts`);
72
+ const relativePath = path.relative(context.outputDir, outputCommandFile);
73
+ context.writeFile(relativePath, content);
74
+ }
95
75
  //#endregion
96
76
  //#region src/index.ts
97
77
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -100,10 +80,10 @@ var ClientsPlugin = class extends BasePlugin {
100
80
  generate(context) {
101
81
  const libDir = path.join(moduleDir, "lib");
102
82
  this.copyLibFiles(context, libDir, "clients");
103
- ClientGenerator.generate(context);
83
+ generate(context);
104
84
  }
105
85
  };
106
-
107
86
  //#endregion
108
87
  export { ClientsPlugin as default };
88
+
109
89
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["moduleDir"],"sources":["../src/ClientGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n GeneratorContext,\n OperationResource,\n} from \"@rexeus/typeweaver-gen\";\nimport { TsTypeNode, TsTypePrinter } from \"@rexeus/typeweaver-zod-to-ts\";\nimport Case from \"case\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport class ClientGenerator {\n public static generate(context: GeneratorContext): void {\n const clientTemplatePath = path.join(moduleDir, \"templates\", \"Client.ejs\");\n const commandTemplatePath = path.join(\n moduleDir,\n \"templates\",\n \"RequestCommand.ejs\"\n );\n\n for (const [, entityResource] of Object.entries(\n context.resources.entityResources\n )) {\n this.writeClient(clientTemplatePath, entityResource.operations, context);\n this.writeRequestCommands(\n commandTemplatePath,\n entityResource.operations,\n context\n );\n }\n }\n\n private static writeClient(\n templateFilePath: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n const entityName = operationResources[0]!.entityName;\n const pascalCaseEntityName = Case.pascal(entityName);\n const outputDir = operationResources[0]!.outputDir;\n\n const operations: {\n operationId: string;\n pascalCaseOperationId: string;\n requestFile: string;\n responseValidatorFile: string;\n responseFile: string;\n }[] = [];\n for (const operationResource of operationResources) {\n const {\n definition,\n outputResponseFileName,\n outputResponseValidationFileName,\n outputRequestFileName,\n } = operationResource;\n const { operationId } = definition;\n\n const pascalCaseOperationId = Case.pascal(operationId);\n const requestFile = `./${path.basename(outputRequestFileName, \".ts\")}`;\n const responseValidatorFile = `./${path.basename(outputResponseValidationFileName, \".ts\")}`;\n const responseFile = `./${path.basename(outputResponseFileName, \".ts\")}`;\n\n operations.push({\n operationId,\n pascalCaseOperationId,\n requestFile,\n responseValidatorFile,\n responseFile,\n });\n }\n\n const content = context.renderTemplate(templateFilePath, {\n coreDir: context.coreDir,\n pascalCaseEntityName,\n operations,\n });\n\n const outputClientFile = path.join(\n outputDir,\n `${pascalCaseEntityName}Client.ts`\n );\n const relativePath = path.relative(context.outputDir, outputClientFile);\n context.writeFile(relativePath, content);\n }\n\n private static writeRequestCommands(\n templateFilePath: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n for (const operationResource of operationResources) {\n this.writeRequestCommand(templateFilePath, operationResource, context);\n }\n }\n\n private static writeRequestCommand(\n templateFilePath: string,\n operationResource: OperationResource,\n context: GeneratorContext\n ): void {\n const {\n definition,\n sourceDir,\n sourceFile,\n outputDir,\n outputResponseFileName,\n outputResponseValidationFileName,\n outputRequestFileName,\n } = operationResource;\n\n const { operationId, method, request, responses } = definition;\n const pascalCaseOperationId = Case.pascal(operationId);\n\n // Get response information\n const allResponses = responses;\n const ownSuccessResponses = allResponses.filter(\n r => r.statusCode >= 200 && r.statusCode < 300 && !r.isReference\n );\n const ownErrorResponses = allResponses.filter(\n r => (r.statusCode < 200 || r.statusCode >= 300) && !r.isReference\n );\n const sharedSuccessResponses = allResponses.filter(\n r => r.statusCode >= 200 && r.statusCode < 300 && r.isReference\n );\n const sharedErrorResponses = allResponses.filter(\n r => (r.statusCode < 200 || r.statusCode >= 300) && r.isReference\n );\n\n // Build request type information\n const headerTsType = request.header\n ? TsTypePrinter.print(TsTypeNode.fromZod(request.header))\n : undefined;\n const paramTsType = request.param\n ? TsTypePrinter.print(TsTypeNode.fromZod(request.param))\n : undefined;\n const queryTsType = request.query\n ? TsTypePrinter.print(TsTypeNode.fromZod(request.query))\n : undefined;\n const bodyTsType = request.body\n ? TsTypePrinter.print(TsTypeNode.fromZod(request.body))\n : undefined;\n\n // Helper function to determine response import path\n const successResponseImportPath = (response: {\n name: string;\n isReference?: boolean;\n path?: string;\n }) => {\n if (response.isReference && response.path) {\n return response.path;\n }\n return `./${path.basename(outputResponseFileName, \".ts\")}`;\n };\n\n // Build relative paths\n const requestFile = `./${path.basename(outputRequestFileName, \".ts\")}`;\n const responseValidatorFile = `./${path.basename(outputResponseValidationFileName, \".ts\")}`;\n const relativeSourceFile = path.relative(sourceDir, sourceFile);\n const sourcePath = path.join(\n sourceDir,\n relativeSourceFile.replace(/\\.ts$/, \"\")\n );\n const relativeSourcePath = path.relative(outputDir, sourcePath);\n\n const content = context.renderTemplate(templateFilePath, {\n sourcePath: relativeSourcePath,\n operationId,\n pascalCaseOperationId,\n method,\n headerTsType,\n paramTsType,\n queryTsType,\n bodyTsType,\n ownSuccessResponses,\n ownErrorResponses,\n sharedSuccessResponses,\n sharedErrorResponses,\n requestFile,\n responseValidatorFile,\n successResponseImportPath,\n });\n\n const outputCommandFile = path.join(\n outputDir,\n `${pascalCaseOperationId}RequestCommand.ts`\n );\n const relativePath = path.relative(context.outputDir, outputCommandFile);\n context.writeFile(relativePath, content);\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { ClientGenerator } from \"./ClientGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport default class ClientsPlugin extends BasePlugin {\n public name = \"clients\";\n public override generate(context: GeneratorContext): Promise<void> | void {\n // Copy lib files to lib/clients/ from dist folder\n const libDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libDir, \"clients\");\n\n ClientGenerator.generate(context);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["moduleDir"],"sources":["../src/clientGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n GeneratorContext,\n NormalizedOperation,\n NormalizedResource,\n} from \"@rexeus/typeweaver-gen\";\nimport { fromZod, print } from \"@rexeus/typeweaver-zod-to-ts\";\nimport Case from \"case\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport function generate(context: GeneratorContext): void {\n const clientTemplatePath = path.join(moduleDir, \"templates\", \"Client.ejs\");\n const commandTemplatePath = path.join(\n moduleDir,\n \"templates\",\n \"RequestCommand.ejs\"\n );\n\n for (const resource of context.normalizedSpec.resources) {\n writeClient(clientTemplatePath, resource, context);\n writeRequestCommands(commandTemplatePath, resource, context);\n }\n}\n\nfunction writeClient(\n templateFilePath: string,\n resource: NormalizedResource,\n context: GeneratorContext\n): void {\n const pascalCaseEntityName = Case.pascal(resource.name);\n const outputDir = context.getResourceOutputDir(resource.name);\n\n const operations = resource.operations.map(operation => {\n const outputPaths = context.getOperationOutputPaths({\n resourceName: resource.name,\n operationId: operation.operationId,\n });\n\n return {\n operationId: operation.operationId,\n pascalCaseOperationId: Case.pascal(operation.operationId),\n requestFile: `./${path.basename(outputPaths.requestFileName, \".ts\")}`,\n responseValidatorFile: `./${path.basename(outputPaths.responseValidationFileName, \".ts\")}`,\n responseFile: `./${path.basename(outputPaths.responseFileName, \".ts\")}`,\n };\n });\n\n const content = context.renderTemplate(templateFilePath, {\n coreDir: context.coreDir,\n pascalCaseEntityName,\n operations,\n });\n\n const outputClientFile = path.join(\n outputDir,\n `${pascalCaseEntityName}Client.ts`\n );\n const relativePath = path.relative(context.outputDir, outputClientFile);\n context.writeFile(relativePath, content);\n}\n\nfunction writeRequestCommands(\n templateFilePath: string,\n resource: NormalizedResource,\n context: GeneratorContext\n): void {\n resource.operations.forEach(operation => {\n writeRequestCommand(templateFilePath, resource.name, operation, context);\n });\n}\n\nfunction writeRequestCommand(\n templateFilePath: string,\n resourceName: string,\n operation: NormalizedOperation,\n context: GeneratorContext\n): void {\n const outputPaths = context.getOperationOutputPaths({\n resourceName,\n operationId: operation.operationId,\n });\n const request = operation.request ?? {};\n const pascalCaseOperationId = Case.pascal(operation.operationId);\n\n const headerTsType = request.header\n ? print(fromZod(request.header))\n : undefined;\n const paramTsType = request.param ? print(fromZod(request.param)) : undefined;\n const queryTsType = request.query ? print(fromZod(request.query)) : undefined;\n const bodyTsType = request.body ? print(fromZod(request.body)) : undefined;\n\n const content = context.renderTemplate(templateFilePath, {\n resourceName,\n specPath: context.getSpecImportPath({\n importerDir: outputPaths.outputDir,\n }),\n operationId: operation.operationId,\n pascalCaseOperationId,\n method: operation.method,\n headerTsType,\n paramTsType,\n queryTsType,\n bodyTsType,\n requestFile: `./${path.basename(outputPaths.requestFileName, \".ts\")}`,\n responseValidatorFile: `./${path.basename(outputPaths.responseValidationFileName, \".ts\")}`,\n responseFile: `./${path.basename(outputPaths.responseFileName, \".ts\")}`,\n });\n\n const outputCommandFile = path.join(\n outputPaths.outputDir,\n `${pascalCaseOperationId}RequestCommand.ts`\n );\n const relativePath = path.relative(context.outputDir, outputCommandFile);\n context.writeFile(relativePath, content);\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { generate as generateClients } from \"./clientGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport default class ClientsPlugin extends BasePlugin {\n public name = \"clients\";\n public override generate(context: GeneratorContext): Promise<void> | void {\n // Copy lib files to lib/clients/ from dist folder\n const libDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libDir, \"clients\");\n\n generateClients(context);\n }\n}\n"],"mappings":";;;;;;AAUA,MAAMA,cAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE9D,SAAgB,SAAS,SAAiC;CACxD,MAAM,qBAAqB,KAAK,KAAKA,aAAW,aAAa,aAAa;CAC1E,MAAM,sBAAsB,KAAK,KAC/BA,aACA,aACA,qBACD;AAED,MAAK,MAAM,YAAY,QAAQ,eAAe,WAAW;AACvD,cAAY,oBAAoB,UAAU,QAAQ;AAClD,uBAAqB,qBAAqB,UAAU,QAAQ;;;AAIhE,SAAS,YACP,kBACA,UACA,SACM;CACN,MAAM,uBAAuB,KAAK,OAAO,SAAS,KAAK;CACvD,MAAM,YAAY,QAAQ,qBAAqB,SAAS,KAAK;CAE7D,MAAM,aAAa,SAAS,WAAW,KAAI,cAAa;EACtD,MAAM,cAAc,QAAQ,wBAAwB;GAClD,cAAc,SAAS;GACvB,aAAa,UAAU;GACxB,CAAC;AAEF,SAAO;GACL,aAAa,UAAU;GACvB,uBAAuB,KAAK,OAAO,UAAU,YAAY;GACzD,aAAa,KAAK,KAAK,SAAS,YAAY,iBAAiB,MAAM;GACnE,uBAAuB,KAAK,KAAK,SAAS,YAAY,4BAA4B,MAAM;GACxF,cAAc,KAAK,KAAK,SAAS,YAAY,kBAAkB,MAAM;GACtE;GACD;CAEF,MAAM,UAAU,QAAQ,eAAe,kBAAkB;EACvD,SAAS,QAAQ;EACjB;EACA;EACD,CAAC;CAEF,MAAM,mBAAmB,KAAK,KAC5B,WACA,GAAG,qBAAqB,WACzB;CACD,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,iBAAiB;AACvE,SAAQ,UAAU,cAAc,QAAQ;;AAG1C,SAAS,qBACP,kBACA,UACA,SACM;AACN,UAAS,WAAW,SAAQ,cAAa;AACvC,sBAAoB,kBAAkB,SAAS,MAAM,WAAW,QAAQ;GACxE;;AAGJ,SAAS,oBACP,kBACA,cACA,WACA,SACM;CACN,MAAM,cAAc,QAAQ,wBAAwB;EAClD;EACA,aAAa,UAAU;EACxB,CAAC;CACF,MAAM,UAAU,UAAU,WAAW,EAAE;CACvC,MAAM,wBAAwB,KAAK,OAAO,UAAU,YAAY;CAEhE,MAAM,eAAe,QAAQ,SACzB,MAAM,QAAQ,QAAQ,OAAO,CAAC,GAC9B,KAAA;CACJ,MAAM,cAAc,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,MAAM,CAAC,GAAG,KAAA;CACpE,MAAM,cAAc,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,MAAM,CAAC,GAAG,KAAA;CACpE,MAAM,aAAa,QAAQ,OAAO,MAAM,QAAQ,QAAQ,KAAK,CAAC,GAAG,KAAA;CAEjE,MAAM,UAAU,QAAQ,eAAe,kBAAkB;EACvD;EACA,UAAU,QAAQ,kBAAkB,EAClC,aAAa,YAAY,WAC1B,CAAC;EACF,aAAa,UAAU;EACvB;EACA,QAAQ,UAAU;EAClB;EACA;EACA;EACA;EACA,aAAa,KAAK,KAAK,SAAS,YAAY,iBAAiB,MAAM;EACnE,uBAAuB,KAAK,KAAK,SAAS,YAAY,4BAA4B,MAAM;EACxF,cAAc,KAAK,KAAK,SAAS,YAAY,kBAAkB,MAAM;EACtE,CAAC;CAEF,MAAM,oBAAoB,KAAK,KAC7B,YAAY,WACZ,GAAG,sBAAsB,mBAC1B;CACD,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,kBAAkB;AACxE,SAAQ,UAAU,cAAc,QAAQ;;;;AC7G1C,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE9D,IAAqB,gBAArB,cAA2C,WAAW;CACpD,OAAc;CACd,SAAyB,SAAiD;EAExE,MAAM,SAAS,KAAK,KAAK,WAAW,MAAM;AAC1C,OAAK,aAAa,SAAS,QAAQ,UAAU;AAE7C,WAAgB,QAAQ"}
@@ -16,12 +16,6 @@ import { PathParameterError } from "./PathParameterError";
16
16
  import { RequestCommand } from "./RequestCommand";
17
17
  import { ResponseParseError } from "./ResponseParseError";
18
18
  import type { NetworkErrorCode } from "./NetworkError";
19
- import type { ProcessResponseOptions } from "./RequestCommand";
20
-
21
- /**
22
- * Configuration options for handling unknown responses.
23
- */
24
- export type UnknownResponseHandling = "throw" | "passthrough";
25
19
 
26
20
  /**
27
21
  * Configuration options for ApiClient initialization.
@@ -31,10 +25,6 @@ export type ApiClientProps = {
31
25
  readonly fetchFn?: typeof globalThis.fetch;
32
26
  /** Base URL for API requests */
33
27
  readonly baseUrl: string;
34
- /** How to handle unknown responses. Defaults to "throw" */
35
- readonly unknownResponseHandling?: UnknownResponseHandling;
36
- /** Predicate to determine if a status code represents success. Defaults to 2xx status codes */
37
- readonly isSuccessStatusCode?: (statusCode: number) => boolean;
38
28
  /** Request timeout in milliseconds. When set, requests will be aborted after this duration */
39
29
  readonly timeoutMs?: number;
40
30
  };
@@ -60,8 +50,6 @@ const NETWORK_ERROR_MESSAGES: Readonly<
60
50
  export abstract class ApiClient {
61
51
  private readonly fetchFn: typeof globalThis.fetch;
62
52
  public readonly baseUrl: string;
63
- public readonly unknownResponseHandling: UnknownResponseHandling;
64
- public readonly isSuccessStatusCode: (statusCode: number) => boolean;
65
53
  private readonly timeoutMs: number | undefined;
66
54
 
67
55
  protected constructor(props: ApiClientProps) {
@@ -79,20 +67,9 @@ export abstract class ApiClient {
79
67
  throw new Error("timeoutMs must be a positive finite number");
80
68
  }
81
69
 
82
- this.unknownResponseHandling = props.unknownResponseHandling ?? "throw";
83
- this.isSuccessStatusCode =
84
- props.isSuccessStatusCode ??
85
- ((statusCode: number) => statusCode >= 200 && statusCode < 300);
86
70
  this.timeoutMs = props.timeoutMs;
87
71
  }
88
72
 
89
- protected get processResponseOptions(): ProcessResponseOptions {
90
- return {
91
- unknownResponseHandling: this.unknownResponseHandling,
92
- isSuccessStatusCode: this.isSuccessStatusCode,
93
- };
94
- }
95
-
96
73
  protected async execute(request: RequestCommand): Promise<IHttpResponse> {
97
74
  const { method, path, header, query, param, body } = request;
98
75
 
@@ -5,7 +5,6 @@
5
5
  * @generated by @rexeus/typeweaver
6
6
  */
7
7
 
8
- import { HttpResponse } from "@rexeus/typeweaver-core";
9
8
  import type {
10
9
  HttpMethod,
11
10
  IHttpBody,
@@ -15,28 +14,15 @@ import type {
15
14
  IHttpRequest,
16
15
  IHttpResponse,
17
16
  } from "@rexeus/typeweaver-core";
18
- import type { UnknownResponseHandling } from "./ApiClient";
19
-
20
- /**
21
- * Configuration options for processing HTTP responses.
22
- */
23
- export type ProcessResponseOptions = {
24
- readonly unknownResponseHandling: UnknownResponseHandling;
25
- readonly isSuccessStatusCode: (statusCode: number) => boolean;
26
- };
27
17
 
28
18
  /**
29
19
  * Abstract base class for type-safe API request commands.
30
20
  *
31
- * This class represents a command pattern for HTTP requests, providing:
21
+ * Represents a command pattern for HTTP requests, providing:
32
22
  * - Type-safe request parameters (headers, path params, query, body)
33
- * - Response processing abstraction
23
+ * - Response processing with validation and type narrowing
34
24
  * - Integration with ApiClient for execution
35
25
  *
36
- * Implementations should:
37
- * 1. Set all readonly properties in the constructor
38
- * 2. Implement processResponse to handle response transformation
39
- *
40
26
  * @template Header - The HTTP header type
41
27
  * @template Param - The path parameter type
42
28
  * @template Query - The query string parameter type
@@ -64,19 +50,14 @@ export abstract class RequestCommand<
64
50
  public readonly body!: Body;
65
51
 
66
52
  /**
67
- * Processes the raw HTTP response into a typed response object.
53
+ * Processes the raw HTTP response into a typed, validated response object.
68
54
  *
69
- * This method should handle:
70
- * - Response validation
71
- * - Data transformation
72
- * - Error response handling
55
+ * Returns the full response union (success and error responses).
56
+ * Throws UnknownResponseError if the response doesn't match any defined schema.
73
57
  *
74
58
  * @param response - The raw HTTP response from the server
75
- * @param options - Configuration options for response processing
76
- * @returns The processed, type-safe response object
59
+ * @returns The validated, type-safe response object
60
+ * @throws {UnknownResponseError} If the response doesn't match any defined schema
77
61
  */
78
- public abstract processResponse(
79
- response: IHttpResponse,
80
- options: ProcessResponseOptions
81
- ): HttpResponse;
62
+ public abstract processResponse(response: IHttpResponse): IHttpResponse;
82
63
  }
@@ -2,16 +2,14 @@
2
2
  /**
3
3
  * This file was automatically generated by typeweaver.
4
4
  * DO NOT EDIT. Instead, modify the source definition file and generate again.
5
- *
5
+ *
6
6
  * @generated by @rexeus/typeweaver
7
7
  */
8
8
 
9
9
  import { ApiClient, type ApiClientProps } from "../lib/clients";
10
10
  <% for (const operation of operations) { %>
11
11
  import { <%= operation.operationId %>RequestCommand } from "./<%= operation.operationId %>RequestCommand";
12
- import type {
13
- Successful<%= operation.operationId %>Response
14
- } from "<%= operation.requestFile %>";
12
+ import type { <%= operation.operationId %>Response } from "<%= operation.responseFile %>";
15
13
  <% } %>
16
14
 
17
15
  export type <%= pascalCaseEntityName %>RequestCommands =
@@ -19,9 +17,9 @@ export type <%= pascalCaseEntityName %>RequestCommands =
19
17
  | <%= operation.operationId %>RequestCommand
20
18
  <% } %>;
21
19
 
22
- export type Successful<%= pascalCaseEntityName %>Responses =
20
+ export type <%= pascalCaseEntityName %>Responses =
23
21
  <% for (const operation of operations) { %>
24
- | Successful<%= operation.operationId %>Response
22
+ | <%= operation.operationId %>Response
25
23
  <% } %>;
26
24
 
27
25
 
@@ -31,10 +29,10 @@ export class <%= pascalCaseEntityName %>Client extends ApiClient {
31
29
  }
32
30
 
33
31
  <% for (const operation of operations) { %>
34
- public async send(command: <%= operation.operationId %>RequestCommand): Promise<Successful<%= operation.operationId %>Response>;
32
+ public async send(command: <%= operation.operationId %>RequestCommand): Promise<<%= operation.operationId %>Response>;
35
33
  <% } %>
36
- public async send(command: <%= pascalCaseEntityName %>RequestCommands): Promise<Successful<%= pascalCaseEntityName %>Responses> {
34
+ public async send(command: <%= pascalCaseEntityName %>RequestCommands): Promise<<%= pascalCaseEntityName %>Responses> {
37
35
  const response = await this.execute(command);
38
- return command.processResponse(response, this.processResponseOptions);
36
+ return command.processResponse(response);
39
37
  }
40
- }
38
+ }
@@ -2,13 +2,14 @@
2
2
  /**
3
3
  * This file was automatically generated by typeweaver.
4
4
  * DO NOT EDIT. Instead, modify the source definition file and generate again.
5
- *
5
+ *
6
6
  * @generated by @rexeus/typeweaver
7
7
  */
8
8
 
9
- import definition from "<%= sourcePath %>";
10
- import { HttpMethod, type IHttpResponse, ResponseValidationError, UnknownResponse } from "@rexeus/typeweaver-core";
11
- import { RequestCommand, type ProcessResponseOptions } from "../lib/clients";
9
+ import spec from "<%= specPath %>";
10
+ import { HttpMethod, type IHttpResponse, ResponseValidationError, UnknownResponseError } from "@rexeus/typeweaver-core";
11
+ import { RequestCommand } from "../lib/clients";
12
+ import { getOperationDefinition } from "../lib/types";
12
13
  import { <%= pascalCaseOperationId %>ResponseValidator } from "<%= responseValidatorFile %>";
13
14
  import type {
14
15
  I<%= pascalCaseOperationId %>Request,
@@ -16,11 +17,11 @@ import type {
16
17
  <%= paramTsType ? `I${pascalCaseOperationId}RequestParam,` : "" %>
17
18
  <%= queryTsType ? `I${pascalCaseOperationId}RequestQuery,` : "" %>
18
19
  <%= bodyTsType ? `I${pascalCaseOperationId}RequestBody,` : "" %>
19
- Successful<%= pascalCaseOperationId %>Response,
20
20
  } from "<%= requestFile %>";
21
- <% for (const successResponse of [...ownSuccessResponses, ...sharedSuccessResponses]) { %>
22
- import { <%= successResponse.name %>Response } from "<%= successResponseImportPath(successResponse) %>";
23
- <% } %>
21
+ import type { <%= pascalCaseOperationId %>Response } from "<%= responseFile %>";
22
+
23
+ const definition = getOperationDefinition(spec, "<%= resourceName %>", "<%= operationId %>");
24
+ const responseValidator = new <%= pascalCaseOperationId %>ResponseValidator();
24
25
 
25
26
  export class <%= pascalCaseOperationId %>RequestCommand extends RequestCommand implements I<%= pascalCaseOperationId %>Request {
26
27
  public override readonly operationId = definition.operationId;
@@ -32,8 +33,6 @@ export class <%= pascalCaseOperationId %>RequestCommand extends RequestCommand i
32
33
  public <%= queryTsType ? `override` : `declare` %> readonly query: <%= queryTsType ? `I${pascalCaseOperationId}RequestQuery` : `undefined` %>;
33
34
  public <%= bodyTsType ? `override` : `declare` %> readonly body: <%= bodyTsType ? `I${pascalCaseOperationId}RequestBody` : `undefined` %>;
34
35
 
35
- private readonly responseValidator: <%= pascalCaseOperationId %>ResponseValidator;
36
-
37
36
  public constructor(input: Omit<I<%= pascalCaseOperationId %>Request, "method" | "path">) {
38
37
  super();
39
38
 
@@ -52,40 +51,21 @@ export class <%= pascalCaseOperationId %>RequestCommand extends RequestCommand i
52
51
  <% if (bodyTsType) { %>
53
52
  this.body = input.body;
54
53
  <% } %>
55
-
56
- this.responseValidator = new <%= pascalCaseOperationId %>ResponseValidator();
57
54
  }
58
55
 
59
- public processResponse(
60
- response: IHttpResponse,
61
- options: ProcessResponseOptions
62
- ): Successful<%= pascalCaseOperationId %>Response {
56
+ public processResponse(response: IHttpResponse): <%= pascalCaseOperationId %>Response {
63
57
  try {
64
- const result = this.responseValidator.validate(response);
65
-
66
- <% for (const successResponse of [...ownSuccessResponses, ...sharedSuccessResponses]) { %>
67
- if (result instanceof <%= successResponse.name %>Response) {
68
- return result;
69
- }
70
- <% } %>
71
-
72
- throw result;
58
+ return responseValidator.validate(response);
73
59
  } catch (error) {
74
60
  if (error instanceof ResponseValidationError) {
75
- const unknownResponse = new UnknownResponse(
61
+ throw new UnknownResponseError(
76
62
  response.statusCode,
77
63
  response.header,
78
64
  response.body,
79
65
  error
80
66
  );
81
-
82
- if (options.unknownResponseHandling === "passthrough" && options.isSuccessStatusCode(response.statusCode)) {
83
- return unknownResponse as any;
84
- }
85
-
86
- throw unknownResponse;
87
67
  }
88
68
  throw error;
89
69
  }
90
70
  }
91
- }
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-clients",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Generates HTTP clients directly from your API definitions. Powered by Typeweaver ๐Ÿงตโœจ",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -48,22 +48,22 @@
48
48
  "homepage": "https://github.com/rexeus/typeweaver#readme",
49
49
  "peerDependencies": {
50
50
  "zod": "^4.3.0",
51
- "@rexeus/typeweaver-core": "^0.7.0",
52
- "@rexeus/typeweaver-gen": "^0.7.0"
51
+ "@rexeus/typeweaver-core": "^0.9.0",
52
+ "@rexeus/typeweaver-gen": "^0.9.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@hono/node-server": "^1.19.7",
55
+ "@hono/node-server": "^1.19.11",
56
56
  "test-utils": "file:../test-utils",
57
57
  "zod": "^4.3.6",
58
- "@rexeus/typeweaver-core": "^0.7.0",
59
- "@rexeus/typeweaver-gen": "^0.7.0"
58
+ "@rexeus/typeweaver-core": "^0.9.0",
59
+ "@rexeus/typeweaver-gen": "^0.9.0"
60
60
  },
61
61
  "dependencies": {
62
62
  "case": "^1.6.3",
63
- "@rexeus/typeweaver-zod-to-ts": "^0.7.0"
63
+ "@rexeus/typeweaver-zod-to-ts": "^0.9.0"
64
64
  },
65
65
  "scripts": {
66
- "typecheck": "tsc --noEmit",
66
+ "typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
67
67
  "format": "oxfmt",
68
68
  "build": "tsdown && mkdir -p ./dist/templates ./dist/lib && cp -r ./src/templates/* ./dist/templates/ && cp -r ./src/lib/* ./dist/lib/ && cp ../../LICENSE ../../NOTICE ./dist/",
69
69
  "test": "vitest --run",