routesync 1.0.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/dist/cli.js ADDED
@@ -0,0 +1,436 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // packages/cli/src/index.ts
27
+ var import_commander5 = require("commander");
28
+
29
+ // packages/cli/src/commands/scan.ts
30
+ var import_commander = require("commander");
31
+ var import_ora = __toESM(require("ora"));
32
+ var import_chalk = __toESM(require("chalk"));
33
+
34
+ // packages/cli/src/parsers/LaravelRouteParser.ts
35
+ var import_fs_extra = __toESM(require("fs-extra"));
36
+ var LaravelRouteParser = class {
37
+ async parse(filePath) {
38
+ const content = await import_fs_extra.default.readFile(filePath, "utf-8");
39
+ return this.parseContent(content);
40
+ }
41
+ parseContent(content) {
42
+ const routes = [];
43
+ const lines = content.split("\n");
44
+ let currentAuth = false;
45
+ let currentMiddleware = [];
46
+ let insideAuthGroup = false;
47
+ let insideAdminGroup = false;
48
+ let braceDepth = 0;
49
+ let groupBraceDepth = 0;
50
+ for (const line of lines) {
51
+ const trimmed = line.trim();
52
+ if (trimmed.includes("middleware('auth:sanctum')")) {
53
+ insideAuthGroup = true;
54
+ currentAuth = true;
55
+ groupBraceDepth = braceDepth;
56
+ }
57
+ if (trimmed.includes("middleware(['auth:sanctum', 'admin'])")) {
58
+ insideAdminGroup = true;
59
+ currentMiddleware = ["auth:sanctum", "admin"];
60
+ groupBraceDepth = braceDepth;
61
+ }
62
+ braceDepth += (trimmed.match(/{/g) ?? []).length;
63
+ braceDepth -= (trimmed.match(/}/g) ?? []).length;
64
+ if (braceDepth <= groupBraceDepth && insideAuthGroup) {
65
+ insideAuthGroup = false;
66
+ currentAuth = false;
67
+ }
68
+ if (braceDepth <= groupBraceDepth && insideAdminGroup) {
69
+ insideAdminGroup = false;
70
+ currentMiddleware = [];
71
+ }
72
+ const routeMatch = trimmed.match(
73
+ /Route::(get|post|put|patch|delete|match)\(['"]([^'"]+)['"]/i
74
+ );
75
+ if (routeMatch) {
76
+ const method = routeMatch[1].toUpperCase();
77
+ const path5 = routeMatch[2];
78
+ routes.push({
79
+ name: this.inferName(method, path5),
80
+ method,
81
+ path: path5,
82
+ auth: insideAuthGroup || insideAdminGroup,
83
+ middleware: insideAdminGroup ? ["admin"] : []
84
+ });
85
+ }
86
+ }
87
+ return routes;
88
+ }
89
+ inferName(method, path5) {
90
+ const parts = path5.replace(/^\//, "").split("/");
91
+ const resource = parts[0] ?? "resource";
92
+ const hasId = parts.some((p) => p.startsWith("{"));
93
+ const map = {
94
+ GET: hasId ? `${resource}.show` : `${resource}.index`,
95
+ POST: `${resource}.store`,
96
+ PUT: `${resource}.update`,
97
+ PATCH: `${resource}.update`,
98
+ DELETE: `${resource}.destroy`
99
+ };
100
+ return map[method] ?? `${resource}.action`;
101
+ }
102
+ };
103
+
104
+ // packages/cli/src/generators/ManifestGenerator.ts
105
+ var import_fs_extra2 = __toESM(require("fs-extra"));
106
+ var ManifestGenerator = class {
107
+ static generate(routes, baseURL) {
108
+ return {
109
+ version: "1.0.0",
110
+ baseURL,
111
+ routes,
112
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
113
+ };
114
+ }
115
+ static async save(manifest, outputPath) {
116
+ await import_fs_extra2.default.writeJson(outputPath, manifest, { spaces: 2 });
117
+ }
118
+ };
119
+
120
+ // packages/cli/src/commands/scan.ts
121
+ var scanCommand = new import_commander.Command("scan").description("Scan Laravel/PHP routes and output a route manifest").option("-i, --input <path>", "Path to routes/api.php", "routes/api.php").option("-o, --output <path>", "Output manifest path", "routesync.manifest.json").option("-b, --baseURL <url>", "API base URL", "http://localhost/api").action(async (options) => {
122
+ const spinner = (0, import_ora.default)("Scanning routes...").start();
123
+ try {
124
+ const parser = new LaravelRouteParser();
125
+ const routes = await parser.parse(options.input);
126
+ const manifest = ManifestGenerator.generate(routes, options.baseURL);
127
+ await ManifestGenerator.save(manifest, options.output);
128
+ spinner.succeed(
129
+ import_chalk.default.green(`Found ${routes.length} routes \u2192 ${options.output}`)
130
+ );
131
+ routes.forEach((r) => {
132
+ console.log(
133
+ ` ${import_chalk.default.cyan(r.method.padEnd(7))} ${import_chalk.default.white(r.path)} ${r.auth ? import_chalk.default.yellow("[auth]") : ""}`
134
+ );
135
+ });
136
+ } catch (err) {
137
+ spinner.fail(import_chalk.default.red(`Scan failed: ${err.message}`));
138
+ process.exit(1);
139
+ }
140
+ });
141
+
142
+ // packages/cli/src/commands/generate.ts
143
+ var import_commander2 = require("commander");
144
+ var import_ora2 = __toESM(require("ora"));
145
+ var import_chalk2 = __toESM(require("chalk"));
146
+
147
+ // packages/cli/src/generators/SDKGenerator.ts
148
+ var import_path = __toESM(require("path"));
149
+ var import_fs_extra3 = __toESM(require("fs-extra"));
150
+ var SDKGenerator = class _SDKGenerator {
151
+ static async generate(manifest, outputDir) {
152
+ const grouped = _SDKGenerator.groupByResource(manifest.routes);
153
+ const lines = [];
154
+ lines.push(`// Auto-generated by routesync. Do not edit manually.`);
155
+ lines.push(`// Generated at: ${manifest.generatedAt}`);
156
+ lines.push(``);
157
+ lines.push(`import { defineApi } from '@routesync/sdk'`);
158
+ lines.push(``);
159
+ lines.push(`export const api = defineApi({`);
160
+ for (const [group, routes] of Object.entries(grouped)) {
161
+ lines.push(` ${group}: {`);
162
+ for (const route of routes) {
163
+ const actionName = _SDKGenerator.toActionName(route);
164
+ lines.push(` ${actionName}: {`);
165
+ lines.push(` method: '${route.method}',`);
166
+ lines.push(` path: '${route.path}',`);
167
+ if (route.auth) lines.push(` auth: true,`);
168
+ lines.push(` },`);
169
+ }
170
+ lines.push(` },`);
171
+ }
172
+ lines.push(`})`);
173
+ lines.push(``);
174
+ lines.push(`export default api`);
175
+ await import_fs_extra3.default.writeFile(import_path.default.join(outputDir, "api.ts"), lines.join("\n"));
176
+ }
177
+ static groupByResource(routes) {
178
+ const groups = {};
179
+ for (const route of routes) {
180
+ const parts = route.path.replace(/^\//, "").split("/");
181
+ const group = parts[0]?.replace(/-/g, "_") ?? "root";
182
+ if (!groups[group]) groups[group] = [];
183
+ groups[group].push(route);
184
+ }
185
+ return groups;
186
+ }
187
+ static toActionName(route) {
188
+ const parts = route.path.replace(/^\//, "").split("/");
189
+ const method = route.method.toLowerCase();
190
+ const hasId = parts.some((p) => p.startsWith("{"));
191
+ if (method === "get" && !hasId) return "index";
192
+ if (method === "get" && hasId) return "show";
193
+ if (method === "post") return "store";
194
+ if (method === "put" || method === "patch") return "update";
195
+ if (method === "delete") return "destroy";
196
+ return `${method}Action`;
197
+ }
198
+ };
199
+
200
+ // packages/cli/src/generators/TypeGenerator.ts
201
+ var import_path2 = __toESM(require("path"));
202
+ var import_fs_extra4 = __toESM(require("fs-extra"));
203
+ var TypeGenerator = class _TypeGenerator {
204
+ static async generate(manifest, outputDir) {
205
+ const lines = [];
206
+ lines.push(`// Auto-generated by routesync. Do not edit manually.`);
207
+ lines.push(``);
208
+ lines.push(`export interface ApiResponse<T = any> {`);
209
+ lines.push(` success: boolean`);
210
+ lines.push(` message?: string`);
211
+ lines.push(` data: T`);
212
+ lines.push(` meta?: PaginationMeta`);
213
+ lines.push(`}`);
214
+ lines.push(``);
215
+ lines.push(`export interface PaginationMeta {`);
216
+ lines.push(` current_page: number`);
217
+ lines.push(` last_page: number`);
218
+ lines.push(` per_page: number`);
219
+ lines.push(` total: number`);
220
+ lines.push(`}`);
221
+ lines.push(``);
222
+ lines.push(`export interface ApiError {`);
223
+ lines.push(` success: false`);
224
+ lines.push(` message: string`);
225
+ lines.push(` errors?: Record<string, string[]>`);
226
+ lines.push(` status?: number`);
227
+ lines.push(`}`);
228
+ lines.push(``);
229
+ const resources = new Set(
230
+ manifest.routes.map((r) => r.path.replace(/^\//, "").split("/")[0])
231
+ );
232
+ for (const resource of resources) {
233
+ const typeName = _TypeGenerator.toTypeName(resource ?? "");
234
+ lines.push(`export interface ${typeName} {`);
235
+ lines.push(` id: number`);
236
+ lines.push(` // TODO: Add ${resource} fields`);
237
+ lines.push(` created_at?: string`);
238
+ lines.push(` updated_at?: string`);
239
+ lines.push(`}`);
240
+ lines.push(``);
241
+ }
242
+ await import_fs_extra4.default.writeFile(import_path2.default.join(outputDir, "types.ts"), lines.join("\n"));
243
+ }
244
+ static toTypeName(resource) {
245
+ return resource.replace(/-_/g, " ").split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
246
+ }
247
+ };
248
+
249
+ // packages/cli/src/generators/HookGenerator.ts
250
+ var import_path3 = __toESM(require("path"));
251
+ var import_fs_extra5 = __toESM(require("fs-extra"));
252
+ var HookGenerator = class _HookGenerator {
253
+ static async generate(manifest, outputDir) {
254
+ const lines = [];
255
+ lines.push(`// Auto-generated by routesync. Do not edit manually.`);
256
+ lines.push(``);
257
+ lines.push(`import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'`);
258
+ lines.push(`import { api } from './api'`);
259
+ lines.push(``);
260
+ const grouped = _HookGenerator.groupByResource(manifest.routes);
261
+ for (const [group, routes] of Object.entries(grouped)) {
262
+ const typeName = _HookGenerator.toTypeName(group);
263
+ for (const route of routes) {
264
+ const method = route.method.toUpperCase();
265
+ const hookName = _HookGenerator.toHookName(group, route);
266
+ const queryKey = `['${group}']`;
267
+ if (method === "GET") {
268
+ lines.push(`export function ${hookName}(params?: Record<string, any>) {`);
269
+ lines.push(` return useQuery({`);
270
+ lines.push(` queryKey: ${queryKey},`);
271
+ lines.push(` queryFn: () => api.${group}.${_HookGenerator.toActionName(route)}({ query: params })`);
272
+ lines.push(` })`);
273
+ lines.push(`}`);
274
+ lines.push(``);
275
+ } else {
276
+ lines.push(`export function ${hookName}() {`);
277
+ lines.push(` const queryClient = useQueryClient()`);
278
+ lines.push(` return useMutation({`);
279
+ lines.push(` mutationFn: (data: any) => api.${group}.${_HookGenerator.toActionName(route)}({ body: data }),`);
280
+ lines.push(` onSuccess: () => {`);
281
+ lines.push(` queryClient.invalidateQueries({ queryKey: ${queryKey} })`);
282
+ lines.push(` }`);
283
+ lines.push(` })`);
284
+ lines.push(`}`);
285
+ lines.push(``);
286
+ }
287
+ }
288
+ }
289
+ await import_fs_extra5.default.writeFile(import_path3.default.join(outputDir, "hooks.ts"), lines.join("\n"));
290
+ }
291
+ static groupByResource(routes) {
292
+ const groups = {};
293
+ for (const route of routes) {
294
+ const group = route.path.replace(/^\//, "").split("/")[0]?.replace(/-/g, "_") ?? "root";
295
+ if (!groups[group]) groups[group] = [];
296
+ groups[group].push(route);
297
+ }
298
+ return groups;
299
+ }
300
+ static toHookName(group, route) {
301
+ const method = route.method.toUpperCase();
302
+ const name = _HookGenerator.toTypeName(group);
303
+ const hasId = route.path.includes("{");
304
+ if (method === "GET" && !hasId) return `use${name}List`;
305
+ if (method === "GET" && hasId) return `use${name}Detail`;
306
+ if (method === "POST") return `useCreate${name}`;
307
+ if (method === "PUT" || method === "PATCH") return `useUpdate${name}`;
308
+ if (method === "DELETE") return `useDelete${name}`;
309
+ return `use${name}Action`;
310
+ }
311
+ static toActionName(route) {
312
+ const method = route.method.toLowerCase();
313
+ const hasId = route.path.includes("{");
314
+ if (method === "get" && !hasId) return "index";
315
+ if (method === "get" && hasId) return "show";
316
+ if (method === "post") return "store";
317
+ if (method === "put" || method === "patch") return "update";
318
+ if (method === "delete") return "destroy";
319
+ return "action";
320
+ }
321
+ static toTypeName(resource) {
322
+ return resource.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
323
+ }
324
+ };
325
+
326
+ // packages/cli/src/commands/generate.ts
327
+ var import_fs_extra6 = __toESM(require("fs-extra"));
328
+ var generateCommand = new import_commander2.Command("generate").description("Generate typed SDK, types, and hooks from route manifest").option("-m, --manifest <path>", "Path to route manifest", "routesync.manifest.json").option("-o, --output <path>", "Output directory", "src/api").option("--no-hooks", "Skip generating React hooks").action(async (options) => {
329
+ const spinner = (0, import_ora2.default)("Generating SDK...").start();
330
+ try {
331
+ if (!import_fs_extra6.default.existsSync(options.manifest)) {
332
+ throw new Error(
333
+ `Manifest not found: ${options.manifest}. Run 'routesync scan' first.`
334
+ );
335
+ }
336
+ const manifest = await import_fs_extra6.default.readJson(options.manifest);
337
+ await import_fs_extra6.default.ensureDir(options.output);
338
+ spinner.text = "Generating types...";
339
+ await TypeGenerator.generate(manifest, options.output);
340
+ spinner.text = "Generating SDK...";
341
+ await SDKGenerator.generate(manifest, options.output);
342
+ if (options.hooks !== false) {
343
+ spinner.text = "Generating hooks...";
344
+ await HookGenerator.generate(manifest, options.output);
345
+ }
346
+ spinner.succeed(import_chalk2.default.green(`SDK generated \u2192 ${options.output}`));
347
+ console.log(` ${import_chalk2.default.cyan("api.ts")} Typed API client`);
348
+ console.log(` ${import_chalk2.default.cyan("types.ts")} Response/request types`);
349
+ if (options.hooks !== false) {
350
+ console.log(` ${import_chalk2.default.cyan("hooks.ts")} React Query hooks`);
351
+ }
352
+ } catch (err) {
353
+ spinner.fail(import_chalk2.default.red(`Generate failed: ${err.message}`));
354
+ process.exit(1);
355
+ }
356
+ });
357
+
358
+ // packages/cli/src/commands/sync.ts
359
+ var import_commander3 = require("commander");
360
+ var import_ora3 = __toESM(require("ora"));
361
+ var import_chalk3 = __toESM(require("chalk"));
362
+ var import_fs_extra7 = __toESM(require("fs-extra"));
363
+ var syncCommand = new import_commander3.Command("sync").description("Scan routes and generate SDK in one step").option("-i, --input <path>", "Path to routes/api.php", "routes/api.php").option("-o, --output <path>", "Output directory", "src/api").option("-b, --baseURL <url>", "API base URL", "http://localhost/api").option("--no-hooks", "Skip generating React hooks").action(async (options) => {
364
+ console.log(import_chalk3.default.bold.blue("\n routesync sync\n"));
365
+ const steps = [
366
+ { text: "Scanning Laravel routes" },
367
+ { text: "Generating types" },
368
+ { text: "Generating SDK" },
369
+ { text: "Generating hooks" }
370
+ ];
371
+ const spinner = (0, import_ora3.default)(steps[0].text).start();
372
+ try {
373
+ const parser = new LaravelRouteParser();
374
+ const routes = await parser.parse(options.input);
375
+ const manifest = ManifestGenerator.generate(routes, options.baseURL);
376
+ spinner.succeed(import_chalk3.default.green(`\u2714 ${steps[0].text} (${routes.length} routes)`));
377
+ await import_fs_extra7.default.ensureDir(options.output);
378
+ spinner.start(steps[1].text);
379
+ await TypeGenerator.generate(manifest, options.output);
380
+ spinner.succeed(import_chalk3.default.green(`\u2714 ${steps[1].text}`));
381
+ spinner.start(steps[2].text);
382
+ await SDKGenerator.generate(manifest, options.output);
383
+ spinner.succeed(import_chalk3.default.green(`\u2714 ${steps[2].text}`));
384
+ if (options.hooks !== false) {
385
+ spinner.start(steps[3].text);
386
+ await HookGenerator.generate(manifest, options.output);
387
+ spinner.succeed(import_chalk3.default.green(`\u2714 ${steps[3].text}`));
388
+ }
389
+ console.log(import_chalk3.default.bold.green("\n Sync complete!\n"));
390
+ console.log(` Output: ${import_chalk3.default.cyan(options.output)}`);
391
+ } catch (err) {
392
+ spinner.fail(import_chalk3.default.red(`Sync failed: ${err.message}`));
393
+ process.exit(1);
394
+ }
395
+ });
396
+
397
+ // packages/cli/src/commands/watch.ts
398
+ var import_commander4 = require("commander");
399
+ var import_chalk4 = __toESM(require("chalk"));
400
+ var import_fs_extra8 = __toESM(require("fs-extra"));
401
+ var import_path4 = __toESM(require("path"));
402
+ var watchCommand = new import_commander4.Command("watch").description("Watch routes file and re-sync on changes").option("-i, --input <path>", "Path to routes/api.php", "routes/api.php").option("-o, --output <path>", "Output directory", "src/api").option("-b, --baseURL <url>", "API base URL", "http://localhost/api").action(async (options) => {
403
+ console.log(import_chalk4.default.bold.blue("\n routesync watch\n"));
404
+ console.log(` Watching: ${import_chalk4.default.cyan(options.input)}`);
405
+ console.log(import_chalk4.default.gray(" Press Ctrl+C to stop\n"));
406
+ let debounceTimer;
407
+ const onChange = () => {
408
+ clearTimeout(debounceTimer);
409
+ debounceTimer = setTimeout(async () => {
410
+ console.log(import_chalk4.default.yellow(` [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Routes changed, syncing...`));
411
+ try {
412
+ const { execSync } = await import("child_process");
413
+ execSync(
414
+ `routesync sync --input ${options.input} --output ${options.output} --baseURL ${options.baseURL}`,
415
+ { stdio: "inherit" }
416
+ );
417
+ } catch {
418
+ console.error(import_chalk4.default.red(" Sync failed"));
419
+ }
420
+ }, 300);
421
+ };
422
+ if (!import_fs_extra8.default.existsSync(options.input)) {
423
+ console.error(import_chalk4.default.red(` File not found: ${options.input}`));
424
+ process.exit(1);
425
+ }
426
+ import_fs_extra8.default.watch(import_path4.default.resolve(options.input), onChange);
427
+ });
428
+
429
+ // packages/cli/src/index.ts
430
+ var program = new import_commander5.Command();
431
+ program.name("routesync").description("Laravel routes to typed frontend SDKs").version("1.0.0");
432
+ program.addCommand(scanCommand);
433
+ program.addCommand(generateCommand);
434
+ program.addCommand(syncCommand);
435
+ program.addCommand(watchCommand);
436
+ program.parse(process.argv);
@@ -0,0 +1,206 @@
1
+ import { AxiosRequestConfig, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
2
+
3
+ interface ServiceConfig {
4
+ baseURL: string;
5
+ token?: string;
6
+ headers?: Record<string, string>;
7
+ timeout?: number;
8
+ retry?: RetryConfig;
9
+ cache?: boolean;
10
+ }
11
+ interface RetryConfig {
12
+ attempts: number;
13
+ delay?: number;
14
+ statusCodes?: number[];
15
+ }
16
+ interface AuthConfig {
17
+ type: 'bearer' | 'basic' | 'api-key';
18
+ token?: string;
19
+ apiKey?: string;
20
+ apiKeyHeader?: string;
21
+ }
22
+
23
+ declare class HttpClient {
24
+ private client;
25
+ constructor(config: ServiceConfig);
26
+ private setupInterceptors;
27
+ setToken(token: string): void;
28
+ removeToken(): void;
29
+ get<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
30
+ post<T>(url: string, body?: any, config?: AxiosRequestConfig): Promise<T>;
31
+ put<T>(url: string, body?: any, config?: AxiosRequestConfig): Promise<T>;
32
+ patch<T>(url: string, body?: any, config?: AxiosRequestConfig): Promise<T>;
33
+ delete<T>(url: string, config?: AxiosRequestConfig): Promise<T>;
34
+ upload<T>(url: string, formData: FormData): Promise<T>;
35
+ getInstance(): AxiosInstance;
36
+ }
37
+
38
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
39
+ type RouteTransform = (value: unknown) => unknown;
40
+ interface RouteTransformMap {
41
+ params?: RouteTransform;
42
+ query?: RouteTransform;
43
+ body?: RouteTransform;
44
+ request?: RouteTransform;
45
+ response?: RouteTransform;
46
+ }
47
+ type RouteMapper = RouteTransform | RouteTransformMap;
48
+ interface RouteParserSchema {
49
+ parse?: RouteTransform;
50
+ safeParse?: RouteTransform;
51
+ }
52
+ interface RouteSchemaMap {
53
+ params?: RouteSchemaValue;
54
+ query?: RouteSchemaValue;
55
+ body?: RouteSchemaValue;
56
+ request?: RouteSchemaValue;
57
+ response?: RouteSchemaValue;
58
+ }
59
+ type RouteSchemaValue = RouteTransform | RouteParserSchema;
60
+ type RouteSchema = RouteSchemaValue | RouteSchemaMap;
61
+ interface RequestOptions {
62
+ params?: Record<string, any>;
63
+ headers?: Record<string, string>;
64
+ timeout?: number;
65
+ signal?: AbortSignal;
66
+ }
67
+ interface RouteDefinition {
68
+ method: HttpMethod;
69
+ path: string;
70
+ auth?: boolean;
71
+ schema?: RouteSchema;
72
+ mapper?: RouteMapper;
73
+ headers?: Record<string, string>;
74
+ cache?: unknown;
75
+ retry?: unknown;
76
+ body?: Record<string, any>;
77
+ params?: Record<string, any>;
78
+ query?: Record<string, any>;
79
+ }
80
+ interface ApiDefinition {
81
+ [group: string]: {
82
+ [action: string]: RouteDefinition;
83
+ };
84
+ }
85
+
86
+ declare class Request {
87
+ private _url;
88
+ private _method;
89
+ private _body?;
90
+ private _options?;
91
+ url(url: string): this;
92
+ method(method: HttpMethod): this;
93
+ body(data: any): this;
94
+ options(opts: RequestOptions): this;
95
+ build(): {
96
+ url: string;
97
+ method: HttpMethod;
98
+ body: any;
99
+ options: RequestOptions | undefined;
100
+ };
101
+ }
102
+
103
+ interface ApiResponse<T = any> {
104
+ success: boolean;
105
+ message?: string;
106
+ data: T;
107
+ meta?: PaginationMeta;
108
+ }
109
+ interface PaginationMeta {
110
+ current_page: number;
111
+ last_page: number;
112
+ per_page: number;
113
+ total: number;
114
+ from?: number;
115
+ to?: number;
116
+ }
117
+
118
+ declare class Response<T = any> {
119
+ private raw;
120
+ constructor(raw: ApiResponse<T>);
121
+ get data(): T;
122
+ get success(): boolean;
123
+ get message(): string | undefined;
124
+ isOk(): boolean;
125
+ unwrap(): T;
126
+ }
127
+
128
+ type RequestInterceptor = (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
129
+ type ResponseInterceptor = (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
130
+ type ErrorInterceptor = (error: any) => any;
131
+ declare class Interceptor {
132
+ private client;
133
+ constructor(client: AxiosInstance);
134
+ addRequestInterceptor(onFulfilled: RequestInterceptor, onRejected?: ErrorInterceptor): number;
135
+ addResponseInterceptor(onFulfilled: ResponseInterceptor, onRejected?: ErrorInterceptor): number;
136
+ removeRequestInterceptor(id: number): void;
137
+ removeResponseInterceptor(id: number): void;
138
+ }
139
+
140
+ declare class TokenManager {
141
+ private static TOKEN_KEY;
142
+ private token;
143
+ set(token: string): void;
144
+ get(): string | null;
145
+ clear(): void;
146
+ exists(): boolean;
147
+ }
148
+
149
+ declare class AuthMiddleware {
150
+ private tokenManager;
151
+ constructor(tokenManager: TokenManager);
152
+ inject(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig;
153
+ getAuthHeader(): Record<string, string>;
154
+ }
155
+
156
+ declare class PathResolver {
157
+ /**
158
+ * Resolve path params.
159
+ * e.g. resolvePath('/produk/:id', { id: 10 }) => '/produk/10'
160
+ */
161
+ static resolve(path: string, params?: Record<string, any>): string;
162
+ static extractParams(path: string): string[];
163
+ static hasParams(path: string): boolean;
164
+ }
165
+
166
+ declare class QueryBuilder {
167
+ static build(params?: Record<string, any>): string;
168
+ static append(url: string, params?: Record<string, any>): string;
169
+ }
170
+
171
+ declare class ApiError extends Error {
172
+ readonly status?: number;
173
+ readonly errors?: Record<string, string[]>;
174
+ readonly success = false;
175
+ constructor(message: string, status?: number, errors?: Record<string, string[]>);
176
+ isUnauthorized(): boolean;
177
+ isForbidden(): boolean;
178
+ isNotFound(): boolean;
179
+ isValidationError(): boolean;
180
+ isServerError(): boolean;
181
+ getFirstError(field: string): string | undefined;
182
+ getAllErrors(): string[];
183
+ }
184
+
185
+ declare class ErrorHandler {
186
+ static handle(error: any): never;
187
+ static isApiError(error: unknown): error is ApiError;
188
+ }
189
+
190
+ interface RouteManifest {
191
+ version: string;
192
+ baseURL: string;
193
+ routes: ParsedRoute[];
194
+ generatedAt: string;
195
+ }
196
+ interface ParsedRoute {
197
+ name: string;
198
+ method: string;
199
+ path: string;
200
+ auth: boolean;
201
+ middleware: string[];
202
+ group?: string;
203
+ action?: string;
204
+ }
205
+
206
+ export { type ApiDefinition, ApiError, type ApiResponse, type AuthConfig, AuthMiddleware, ErrorHandler, HttpClient, type HttpMethod, Interceptor, type PaginationMeta, type ParsedRoute, PathResolver, QueryBuilder, Request, type RequestOptions, Response, type RetryConfig, type RouteDefinition, type RouteManifest, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type ServiceConfig, TokenManager };