dto2ts 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Serkan Çakmak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # dto2ts
2
+
3
+ Generate **TypeScript models** and **fetch-based CRUD services** from your C# API.
4
+
5
+ Point it at your ASP.NET Core backend and get a fully typed, ready-to-use client for your
6
+ frontend — no manual DTO copying, no drift between backend and frontend.
7
+
8
+ Two input sources are supported:
9
+
10
+ - **OpenAPI / Swagger** — a `swagger.json` file or a live URL (e.g. the spec ASP.NET Core
11
+ generates automatically). The most robust option.
12
+ - **C# source files** (`.cs`) — parses your DTO/record/enum definitions directly, no need to
13
+ run the API first.
14
+
15
+ The generated code has **zero runtime dependencies** — it uses the built-in `fetch`, so it
16
+ runs in the browser, Node 18+, Angular, React, Vue, or anywhere else.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install --save-dev dto2ts
24
+ ```
25
+
26
+ Or run it once without installing:
27
+
28
+ ```bash
29
+ npx dto2ts --help
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ ### From OpenAPI / Swagger
35
+
36
+ ```bash
37
+ # From a live API
38
+ npx dto2ts -s openapi -i https://localhost:5001/openapi/v1.json -o ./src/api
39
+
40
+ # From a local file
41
+ npx dto2ts -s openapi -i ./swagger.json -o ./src/api
42
+ ```
43
+
44
+ ### From C# source files
45
+
46
+ ```bash
47
+ # Point at a folder — all .cs files are scanned recursively (bin/ and obj/ are skipped)
48
+ npx dto2ts -s csharp -i ../Backend/Dtos -o ./src/api
49
+ ```
50
+
51
+ ## What gets generated
52
+
53
+ One file per type, following the Angular style guide (`.model.ts` / `.enum.ts` / `.service.ts`),
54
+ with barrel `index.ts` files so you can import from a single path:
55
+
56
+ ```
57
+ src/api/
58
+ ├── models/
59
+ │ ├── product-dto.model.ts # export interface ProductDto
60
+ │ ├── category-dto.model.ts # export interface CategoryDto
61
+ │ ├── product-status.enum.ts # export enum ProductStatus
62
+ │ └── index.ts # barrel
63
+ ├── services/
64
+ │ ├── product.service.ts # export class ProductService
65
+ │ ├── category.service.ts
66
+ │ └── index.ts # barrel
67
+ ├── http-client.ts # fetch-based HttpClient + ApiError
68
+ └── index.ts # top-level barrel re-export
69
+ ```
70
+
71
+ Model files reference each other with `import type` (no runtime coupling), and services import
72
+ their DTOs from the `../models` barrel — so everything compiles cleanly even under strict
73
+ `verbatimModuleSyntax`.
74
+
75
+ Example generated model:
76
+
77
+ ```ts
78
+ export enum ProductStatus {
79
+ Draft = 0,
80
+ Active = 1,
81
+ Archived = 2,
82
+ }
83
+
84
+ export interface ProductDto {
85
+ id: number;
86
+ name: string;
87
+ description?: string;
88
+ price: number;
89
+ stock?: number;
90
+ status: ProductStatus;
91
+ createdAt: string;
92
+ tags: string[];
93
+ category?: CategoryDto;
94
+ }
95
+ ```
96
+
97
+ And a generated service:
98
+
99
+ ```ts
100
+ export class ProductService {
101
+ constructor(
102
+ private readonly http: HttpClient,
103
+ private readonly basePath: string = "/api/products"
104
+ ) {}
105
+
106
+ list(options?: RequestOptions): Promise<ProductDto[]>;
107
+ getById(id: number, options?: RequestOptions): Promise<ProductDto>;
108
+ create(payload: ProductDto, options?: RequestOptions): Promise<ProductDto>;
109
+ update(id: number, payload: ProductDto, options?: RequestOptions): Promise<ProductDto>;
110
+ patch(id: number, payload: Partial<ProductDto>, options?: RequestOptions): Promise<ProductDto>;
111
+ remove(id: number, options?: RequestOptions): Promise<void>;
112
+ }
113
+ ```
114
+
115
+ ## Using the generated client
116
+
117
+ ```ts
118
+ import { HttpClient, ProductService, ApiError } from "./api";
119
+
120
+ const http = new HttpClient({
121
+ baseUrl: "https://api.example.com",
122
+ // Optional: attach headers to every request (sync or async — great for auth tokens)
123
+ getHeaders: () => ({ Authorization: `Bearer ${getToken()}` }),
124
+ });
125
+
126
+ const products = new ProductService(http);
127
+
128
+ // CRUD
129
+ const all = await products.list({ query: { page: 1, pageSize: 20 } });
130
+ const one = await products.getById(42);
131
+ const created = await products.create({ id: 0, name: "Pen", price: 9.9, status: 1, tags: [], createdAt: "" });
132
+ const updated = await products.update(42, created);
133
+ const patched = await products.patch(42, { price: 12.5 });
134
+ await products.remove(42);
135
+
136
+ // Error handling
137
+ try {
138
+ await products.getById(999);
139
+ } catch (e) {
140
+ if (e instanceof ApiError) {
141
+ console.error(e.status, e.body); // e.g. 404 and the server response body
142
+ }
143
+ }
144
+ ```
145
+
146
+ ### Angular
147
+
148
+ The generated `HttpClient` is framework-agnostic. In Angular you can register a service via DI:
149
+
150
+ ```ts
151
+ // app.config.ts
152
+ import { HttpClient, ProductService } from "./api";
153
+
154
+ export const appConfig = {
155
+ providers: [
156
+ { provide: HttpClient, useFactory: () => new HttpClient({ baseUrl: "http://localhost:5099" }) },
157
+ ProductService,
158
+ ],
159
+ };
160
+ ```
161
+
162
+ ```ts
163
+ // a component
164
+ private readonly products = inject(ProductService);
165
+ this.products.list().then((items) => (this.items = items));
166
+ ```
167
+
168
+ ## Configuration file
169
+
170
+ Instead of CLI flags, drop a `dto2ts.config.json` in your project root:
171
+
172
+ ```json
173
+ {
174
+ "source": "openapi",
175
+ "input": "./swagger.json",
176
+ "output": "./src/api",
177
+ "camelCase": true,
178
+ "dateAsString": true,
179
+ "generateServices": true,
180
+ "idType": "number",
181
+ "entities": ["ProductDto", "CategoryDto"],
182
+ "basePaths": { "ProductDto": "/api/v1/products" }
183
+ }
184
+ ```
185
+
186
+ Then just run `npx dto2ts`. CLI flags override the config file.
187
+
188
+ ## Options
189
+
190
+ | CLI flag | Config key | Description | Default |
191
+ | ------------------ | ---------------------- | ---------------------------------------------------- | ---------------- |
192
+ | `-s, --source` | `source` | `openapi` \| `csharp` | — (required) |
193
+ | `-i, --input` | `input` | `swagger.json` path/URL, or a `.cs` file/folder | — (required) |
194
+ | `-o, --output` | `output` | Output directory | — (required) |
195
+ | `--no-camel-case` | `camelCase: false` | Keep original PascalCase field names | camelCase |
196
+ | `--date-as-date` | `dateAsString: false` | Map `DateTime` → `Date` instead of `string` | `string` |
197
+ | `--no-services` | `generateServices: false` | Generate models only, no services | services on |
198
+ | `--entities a,b` | `entities: [...]` | Only generate services for these models | all with an `id` |
199
+ | `--id-type` | `idType` | Default TS type for the `id` parameter | `number` |
200
+ | — | `basePaths` | Map of model name → base path override | convention |
201
+
202
+ ## Type mapping (C# → TypeScript)
203
+
204
+ | C# | TypeScript |
205
+ | ----------------------------------------------- | --------------------------------- |
206
+ | `string`, `char`, `Guid` | `string` |
207
+ | `int`, `long`, `decimal`, `double`, `byte`, … | `number` |
208
+ | `bool` | `boolean` |
209
+ | `DateTime`, `DateTimeOffset`, `DateOnly` | `string` (or `Date`) |
210
+ | `T?`, `Nullable<T>` | optional field (`prop?`) |
211
+ | `List<T>`, `IEnumerable<T>`, `T[]`, `HashSet<T>`| `T[]` |
212
+ | `Dictionary<K,V>` | `Record<string \| number, V>` |
213
+ | `Task<T>` | `T` (unwrapped) |
214
+ | `enum` | `enum` |
215
+ | custom class / record | reference to the matching interface |
216
+
217
+ ## How services are generated
218
+
219
+ - **OpenAPI**: real routes are detected from the `paths` section — the base path and id type
220
+ come straight from the spec.
221
+ - **C#**: any interface with an `id` field is treated as a resource; the base path follows the
222
+ `/api/<pluralized-name>` convention.
223
+ - In both cases you can override with the `entities` and `basePaths` config options.
224
+
225
+ ## Programmatic API
226
+
227
+ ```ts
228
+ import { generate } from "dto2ts";
229
+
230
+ const { api, files } = await generate({
231
+ source: "openapi",
232
+ input: "./swagger.json",
233
+ output: "./src/api",
234
+ });
235
+
236
+ console.log(`Generated ${api.models.length} models`, files);
237
+ ```
238
+
239
+ ## Notes & gotchas
240
+
241
+ - Generated files are **overwritten on every run** — don't edit them by hand. Re-run dto2ts as
242
+ part of your build instead.
243
+ - ASP.NET Core serializes JSON as camelCase by default, so keep `camelCase: true` (the default).
244
+ - **Enums via .NET's default OpenAPI**: `System.Text.Json` serializes enums as integers, and the
245
+ generated spec exposes them as a bare `{ "type": "integer" }` with no member names. If you use
246
+ the `openapi` source, such enums become an empty type. Two fixes:
247
+ 1. Prefer the **`csharp` source**, which reads the real enum definition, or
248
+ 2. Register a `JsonStringEnumConverter` in your API so the spec carries the enum values.
249
+ - The C# parser is regex-based and covers standard DTO/record/enum shapes. For deeply nested or
250
+ heavily generic types, prefer the OpenAPI source.
251
+
252
+ ## License
253
+
254
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const config_1 = require("./config");
6
+ const generate_1 = require("./generate");
7
+ const program = new commander_1.Command();
8
+ program
9
+ .name("dto2ts")
10
+ .description("C# API (OpenAPI/Swagger veya .cs DTO) kaynaklarından TS model + fetch CRUD service üretir.")
11
+ .version("0.1.0");
12
+ program
13
+ .command("generate", { isDefault: true })
14
+ .description("Model ve service dosyalarını üret")
15
+ .option("-s, --source <source>", "kaynak türü: openapi | csharp")
16
+ .option("-i, --input <path>", "swagger.json yolu/URL'si veya .cs dosyası/klasörü")
17
+ .option("-o, --output <dir>", "çıktı klasörü")
18
+ .option("--no-camel-case", "alan adlarını camelCase'e çevirme (orijinal PascalCase kalsın)")
19
+ .option("--date-as-date", "DateTime alanlarını Date olarak üret (varsayılan: string)")
20
+ .option("--no-services", "yalnızca model üret, service üretme")
21
+ .option("--entities <list>", "yalnızca bu modeller için service üret (virgülle ayır)")
22
+ .option("--id-type <type>", "id alanının varsayılan tipi (number | string)")
23
+ .action(async (opts) => {
24
+ try {
25
+ const fileConfig = (0, config_1.loadConfigFile)() ?? {};
26
+ const cli = {};
27
+ if (opts.source)
28
+ cli.source = opts.source;
29
+ if (opts.input)
30
+ cli.input = opts.input;
31
+ if (opts.output)
32
+ cli.output = opts.output;
33
+ if (opts.camelCase === false)
34
+ cli.camelCase = false;
35
+ if (opts.dateAsDate)
36
+ cli.dateAsString = false;
37
+ if (opts.services === false)
38
+ cli.generateServices = false;
39
+ if (opts.entities)
40
+ cli.entities = String(opts.entities).split(",").map((s) => s.trim()).filter(Boolean);
41
+ if (opts.idType)
42
+ cli.idType = opts.idType;
43
+ const merged = { ...fileConfig, ...cli };
44
+ const start = Date.now();
45
+ const { api, files } = await (0, generate_1.generate)(merged);
46
+ const ms = Date.now() - start;
47
+ const modelCount = api.models.length;
48
+ const serviceCount = api.resources.length;
49
+ console.log(`✔ dto2ts tamamlandı (${ms}ms)`);
50
+ console.log(` ${modelCount} model, ${serviceCount} service üretildi:`);
51
+ for (const f of files)
52
+ console.log(` → ${f}`);
53
+ }
54
+ catch (err) {
55
+ console.error(`✖ Hata: ${err instanceof Error ? err.message : String(err)}`);
56
+ process.exitCode = 1;
57
+ }
58
+ });
59
+ program.parseAsync(process.argv);
@@ -0,0 +1,24 @@
1
+ export interface GenConfig {
2
+ /** Girdi kaynağı. */
3
+ source: "openapi" | "csharp";
4
+ /** openapi için swagger.json yolu/URL'si; csharp için dosya ya da klasör yolu. */
5
+ input: string;
6
+ /** Üretilen dosyaların yazılacağı klasör. */
7
+ output: string;
8
+ /** Alan adlarını camelCase'e çevir (ASP.NET Core varsayılan JSON davranışı). Varsayılan: true. */
9
+ camelCase?: boolean;
10
+ /** DateTime alanlarını "string" olarak üret (false ise "Date"). Varsayılan: true. */
11
+ dateAsString?: boolean;
12
+ /** CRUD service üretilsin mi. Varsayılan: true. */
13
+ generateServices?: boolean;
14
+ /** Service üretimini yalnızca bu modellerle sınırla (boşsa hepsi/uygun olanlar). */
15
+ entities?: string[];
16
+ /** Model adı -> base path override. Örn. { "ProductDto": "/api/v1/products" }. */
17
+ basePaths?: Record<string, string>;
18
+ /** Id alanının varsayılan tipi (base path'e /{id} eklenirken). Varsayılan: "number". */
19
+ idType?: string;
20
+ }
21
+ export declare const DEFAULT_CONFIG: Partial<GenConfig>;
22
+ /** Çalışma dizininde config dosyası ara ve yükle. */
23
+ export declare function loadConfigFile(cwd?: string): Partial<GenConfig> | null;
24
+ export declare function resolveConfig(partial: Partial<GenConfig>): GenConfig;
package/dist/config.js ADDED
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEFAULT_CONFIG = void 0;
37
+ exports.loadConfigFile = loadConfigFile;
38
+ exports.resolveConfig = resolveConfig;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ exports.DEFAULT_CONFIG = {
42
+ camelCase: true,
43
+ dateAsString: true,
44
+ generateServices: true,
45
+ idType: "number",
46
+ };
47
+ const CONFIG_FILE_NAMES = ["dto2ts.config.json", ".dto2tsrc.json"];
48
+ /** Çalışma dizininde config dosyası ara ve yükle. */
49
+ function loadConfigFile(cwd = process.cwd()) {
50
+ for (const name of CONFIG_FILE_NAMES) {
51
+ const p = path.join(cwd, name);
52
+ if (fs.existsSync(p)) {
53
+ const raw = fs.readFileSync(p, "utf8");
54
+ return JSON.parse(raw);
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ function resolveConfig(partial) {
60
+ const merged = { ...exports.DEFAULT_CONFIG, ...partial };
61
+ if (!merged.source)
62
+ throw new Error('config: "source" zorunlu ("openapi" | "csharp").');
63
+ if (!merged.input)
64
+ throw new Error('config: "input" zorunlu.');
65
+ if (!merged.output)
66
+ throw new Error('config: "output" zorunlu.');
67
+ return merged;
68
+ }
@@ -0,0 +1,10 @@
1
+ import { GenConfig } from "./config";
2
+ import { ApiModel } from "./ir";
3
+ export interface GenerateResult {
4
+ api: ApiModel;
5
+ files: string[];
6
+ }
7
+ /** Kaynağı parse edip ApiModel (IR) üret. */
8
+ export declare function parseSource(config: GenConfig): Promise<ApiModel>;
9
+ /** Tam üretim akışı: parse -> generate -> diske yaz. */
10
+ export declare function generate(partial: Partial<GenConfig>): Promise<GenerateResult>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseSource = parseSource;
37
+ exports.generate = generate;
38
+ const path = __importStar(require("path"));
39
+ const config_1 = require("./config");
40
+ const openapi_1 = require("./parsers/openapi");
41
+ const csharp_1 = require("./parsers/csharp");
42
+ const models_1 = require("./generators/models");
43
+ const services_1 = require("./generators/services");
44
+ const http_client_1 = require("./generators/http-client");
45
+ const fs_1 = require("./utils/fs");
46
+ /** Kaynağı parse edip ApiModel (IR) üret. */
47
+ async function parseSource(config) {
48
+ if (config.source === "openapi")
49
+ return (0, openapi_1.parseOpenApi)(config);
50
+ if (config.source === "csharp")
51
+ return (0, csharp_1.parseCsharp)(config);
52
+ throw new Error(`Bilinmeyen source: ${config.source}`);
53
+ }
54
+ function generateBarrel(hasServices) {
55
+ const lines = [
56
+ "// Bu dosya dto2ts tarafından otomatik üretildi.",
57
+ 'export * from "./models";',
58
+ 'export * from "./http-client";',
59
+ ];
60
+ if (hasServices)
61
+ lines.push('export * from "./services";');
62
+ return lines.join("\n") + "\n";
63
+ }
64
+ /** Tam üretim akışı: parse -> generate -> diske yaz. */
65
+ async function generate(partial) {
66
+ const config = (0, config_1.resolveConfig)(partial);
67
+ const api = await parseSource(config);
68
+ const outDir = path.resolve(config.output);
69
+ const written = [];
70
+ const emit = (relPath, content) => {
71
+ const full = path.join(outDir, relPath);
72
+ (0, fs_1.writeFile)(full, content);
73
+ written.push(full);
74
+ };
75
+ // models/*.model.ts | *.enum.ts + models/index.ts
76
+ for (const file of (0, models_1.generateModelFiles)(api))
77
+ emit(file.path, file.content);
78
+ // http-client.ts
79
+ emit("http-client.ts", (0, http_client_1.generateHttpClient)());
80
+ // services/*.service.ts + services/index.ts
81
+ const serviceFiles = config.generateServices === false ? [] : (0, services_1.generateServiceFiles)(api);
82
+ for (const file of serviceFiles)
83
+ emit(file.path, file.content);
84
+ // kök index.ts
85
+ emit("index.ts", generateBarrel(serviceFiles.length > 0));
86
+ return { api, files: written };
87
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Statik http-client.ts içeriği. Üretilen service'lerin dayandığı,
3
+ * fetch tabanlı, sıfır bağımlılıklı istemci.
4
+ */
5
+ export declare function generateHttpClient(): string;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateHttpClient = generateHttpClient;
4
+ /**
5
+ * Statik http-client.ts içeriği. Üretilen service'lerin dayandığı,
6
+ * fetch tabanlı, sıfır bağımlılıklı istemci.
7
+ */
8
+ function generateHttpClient() {
9
+ return `// Bu dosya dto2ts tarafından otomatik üretildi. Elle düzenlenebilir kabul edilir ama
10
+ // yeniden üretimde üzerine yazılır.
11
+
12
+ export type QueryValue = string | number | boolean | null | undefined | Array<string | number | boolean>;
13
+
14
+ export interface RequestOptions {
15
+ /** İstek başlıkları (base başlıklarla birleşir). */
16
+ headers?: Record<string, string>;
17
+ /** Query string parametreleri. */
18
+ query?: Record<string, QueryValue>;
19
+ /** İptal için AbortSignal. */
20
+ signal?: AbortSignal;
21
+ }
22
+
23
+ export interface HttpClientOptions {
24
+ /** API kök adresi. Örn. "https://api.example.com". */
25
+ baseUrl: string;
26
+ /** Her isteğe eklenecek başlıklar (senkron ya da async, örn. token). */
27
+ getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
28
+ /** Özel fetch implementasyonu (test/SSR için). Varsayılan: global fetch. */
29
+ fetch?: typeof fetch;
30
+ }
31
+
32
+ /** HTTP başarısız olduğunda fırlatılan hata. */
33
+ export class ApiError extends Error {
34
+ constructor(
35
+ public readonly status: number,
36
+ public readonly statusText: string,
37
+ public readonly body: unknown,
38
+ public readonly url: string
39
+ ) {
40
+ super(\`HTTP \${status} \${statusText} — \${url}\`);
41
+ this.name = "ApiError";
42
+ }
43
+ }
44
+
45
+ function buildQuery(query?: Record<string, QueryValue>): string {
46
+ if (!query) return "";
47
+ const params = new URLSearchParams();
48
+ for (const [key, value] of Object.entries(query)) {
49
+ if (value === null || value === undefined) continue;
50
+ if (Array.isArray(value)) {
51
+ for (const v of value) params.append(key, String(v));
52
+ } else {
53
+ params.append(key, String(value));
54
+ }
55
+ }
56
+ const s = params.toString();
57
+ return s ? \`?\${s}\` : "";
58
+ }
59
+
60
+ export class HttpClient {
61
+ private readonly baseUrl: string;
62
+ private readonly fetchImpl: typeof fetch;
63
+
64
+ constructor(private readonly options: HttpClientOptions) {
65
+ this.baseUrl = options.baseUrl.replace(/\\/+$/, "");
66
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
67
+ if (!this.fetchImpl) {
68
+ throw new Error("Global fetch bulunamadı. Node 18+ kullanın ya da options.fetch verin.");
69
+ }
70
+ }
71
+
72
+ async request<T>(
73
+ method: string,
74
+ path: string,
75
+ body?: unknown,
76
+ options: RequestOptions = {}
77
+ ): Promise<T> {
78
+ const url = \`\${this.baseUrl}\${path.startsWith("/") ? path : "/" + path}\${buildQuery(options.query)}\`;
79
+
80
+ const headers: Record<string, string> = {
81
+ Accept: "application/json",
82
+ ...(this.options.getHeaders ? await this.options.getHeaders() : {}),
83
+ ...options.headers,
84
+ };
85
+
86
+ const hasBody = body !== undefined && body !== null;
87
+ if (hasBody) headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
88
+
89
+ const res = await this.fetchImpl(url, {
90
+ method,
91
+ headers,
92
+ body: hasBody ? JSON.stringify(body) : undefined,
93
+ signal: options.signal,
94
+ });
95
+
96
+ if (!res.ok) {
97
+ let errBody: unknown = null;
98
+ try {
99
+ errBody = await res.json();
100
+ } catch {
101
+ try {
102
+ errBody = await res.text();
103
+ } catch {
104
+ /* yoksay */
105
+ }
106
+ }
107
+ throw new ApiError(res.status, res.statusText, errBody, url);
108
+ }
109
+
110
+ if (res.status === 204) return undefined as T;
111
+ const text = await res.text();
112
+ if (!text) return undefined as T;
113
+ return JSON.parse(text) as T;
114
+ }
115
+
116
+ get<T>(path: string, options?: RequestOptions): Promise<T> {
117
+ return this.request<T>("GET", path, undefined, options);
118
+ }
119
+ post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {
120
+ return this.request<T>("POST", path, body, options);
121
+ }
122
+ put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {
123
+ return this.request<T>("PUT", path, body, options);
124
+ }
125
+ patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T> {
126
+ return this.request<T>("PATCH", path, body, options);
127
+ }
128
+ delete<T>(path: string, options?: RequestOptions): Promise<T> {
129
+ return this.request<T>("DELETE", path, undefined, options);
130
+ }
131
+ }
132
+ `;
133
+ }
@@ -0,0 +1,10 @@
1
+ import { ApiModel, ModelDef } from "../ir";
2
+ export interface GeneratedFile {
3
+ /** Çıktı klasörüne göre göreli yol. Örn. "models/product-dto.model.ts". */
4
+ path: string;
5
+ content: string;
6
+ }
7
+ /** Bir modelin uzantısız dosya adı tabanı. */
8
+ export declare function modelFileBase(m: ModelDef): string;
9
+ /** Her modeli kendi dosyasına derle (models/<ad>.model.ts | <ad>.enum.ts) + barrel. */
10
+ export declare function generateModelFiles(api: ApiModel): GeneratedFile[];