@xemahq/api-client-generator 0.1.1

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.
Files changed (62) hide show
  1. package/README.md +73 -0
  2. package/dist/bin/xema-client-generate.d.ts +3 -0
  3. package/dist/bin/xema-client-generate.d.ts.map +1 -0
  4. package/dist/bin/xema-client-generate.js +6 -0
  5. package/dist/bin/xema-client-generate.js.map +1 -0
  6. package/dist/bin/xema-openapi.d.ts +3 -0
  7. package/dist/bin/xema-openapi.d.ts.map +1 -0
  8. package/dist/bin/xema-openapi.js +28 -0
  9. package/dist/bin/xema-openapi.js.map +1 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +26 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/lib/client-package.d.ts +17 -0
  15. package/dist/lib/client-package.d.ts.map +1 -0
  16. package/dist/lib/client-package.js +114 -0
  17. package/dist/lib/client-package.js.map +1 -0
  18. package/dist/lib/client-readme.d.ts +9 -0
  19. package/dist/lib/client-readme.d.ts.map +1 -0
  20. package/dist/lib/client-readme.js +100 -0
  21. package/dist/lib/client-readme.js.map +1 -0
  22. package/dist/lib/custom-fetch-template.d.ts +2 -0
  23. package/dist/lib/custom-fetch-template.d.ts.map +1 -0
  24. package/dist/lib/custom-fetch-template.js +166 -0
  25. package/dist/lib/custom-fetch-template.js.map +1 -0
  26. package/dist/lib/default-stubs.d.ts +3 -0
  27. package/dist/lib/default-stubs.d.ts.map +1 -0
  28. package/dist/lib/default-stubs.js +107 -0
  29. package/dist/lib/default-stubs.js.map +1 -0
  30. package/dist/lib/extract-openapi.d.ts +4 -0
  31. package/dist/lib/extract-openapi.d.ts.map +1 -0
  32. package/dist/lib/extract-openapi.js +134 -0
  33. package/dist/lib/extract-openapi.js.map +1 -0
  34. package/dist/lib/generate-clients.d.ts +4 -0
  35. package/dist/lib/generate-clients.d.ts.map +1 -0
  36. package/dist/lib/generate-clients.js +84 -0
  37. package/dist/lib/generate-clients.js.map +1 -0
  38. package/dist/lib/overrides.d.ts +7 -0
  39. package/dist/lib/overrides.d.ts.map +1 -0
  40. package/dist/lib/overrides.js +26 -0
  41. package/dist/lib/overrides.js.map +1 -0
  42. package/dist/lib/prune-spec.d.ts +16 -0
  43. package/dist/lib/prune-spec.d.ts.map +1 -0
  44. package/dist/lib/prune-spec.js +100 -0
  45. package/dist/lib/prune-spec.js.map +1 -0
  46. package/dist/lib/repo-post-process.d.ts +9 -0
  47. package/dist/lib/repo-post-process.d.ts.map +1 -0
  48. package/dist/lib/repo-post-process.js +56 -0
  49. package/dist/lib/repo-post-process.js.map +1 -0
  50. package/dist/lib/seed-openapi-env.d.ts +2 -0
  51. package/dist/lib/seed-openapi-env.d.ts.map +1 -0
  52. package/dist/lib/seed-openapi-env.js +77 -0
  53. package/dist/lib/seed-openapi-env.js.map +1 -0
  54. package/dist/lib/service-config.d.ts +20 -0
  55. package/dist/lib/service-config.d.ts.map +1 -0
  56. package/dist/lib/service-config.js +80 -0
  57. package/dist/lib/service-config.js.map +1 -0
  58. package/dist/lib/service-nest.d.ts +15 -0
  59. package/dist/lib/service-nest.d.ts.map +1 -0
  60. package/dist/lib/service-nest.js +21 -0
  61. package/dist/lib/service-nest.js.map +1 -0
  62. package/package.json +72 -0
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ <p align="center">
2
+ <svg width="680" height="120" viewBox="0 0 680 120" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="@xemahq/api-client-generator">
3
+ <rect width="680" height="120" rx="14" fill="#0B1020"/>
4
+ <g transform="translate(28,34)">
5
+ <path d="M26 0 L52 15 L52 45 L26 60 L0 45 L0 15 Z" fill="#F59E0B" opacity="0.18"/>
6
+ <path d="M26 12 L41 21 L41 39 L26 48 L11 39 L11 21 Z" fill="#F59E0B"/>
7
+ </g>
8
+ <text x="92" y="52" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" font-size="22" fill="#F8FAFC" font-weight="700">@xemahq/api-client-generator</text>
9
+ <text x="92" y="80" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15" fill="#94A3B8">Central OpenAPI + Orval client generator for Xema services.</text>
10
+ <text x="652" y="105" text-anchor="end" font-family="ui-sans-serif,system-ui,sans-serif" font-size="12" fill="#475569">xema.dev</text>
11
+ </svg>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://xema.dev">Website</a> &middot;
16
+ <a href="https://www.npmjs.com/package/@xemahq/api-client-generator">npm</a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <img alt="npm" src="https://img.shields.io/npm/v/%40xemahq%2Fapi-client-generator?color=2563eb&label=npm">
21
+ <img alt="license" src="https://img.shields.io/npm/l/%40xemahq%2Fapi-client-generator?color=10b981">
22
+ <img alt="types" src="https://img.shields.io/npm/types/%40xemahq%2Fapi-client-generator?color=3178c6">
23
+ </p>
24
+
25
+ # @xemahq/api-client-generator
26
+
27
+ > Central OpenAPI + Orval client generator for Xema services.
28
+
29
+ ## Overview
30
+
31
+ The single, shared generator that produces typed API clients for every Xema
32
+ NestJS service. It extracts one OpenAPI document per API surface
33
+ (public / internal / operator) from decorator metadata, then generates one
34
+ Orval client package per non-empty surface, each sharing the owning service's
35
+ version. Provenance metadata is stamped deterministically into every generated
36
+ client's `package.json`. A route ships in a surface only when it — or its
37
+ controller — is explicitly classified; there is no heuristic inference.
38
+
39
+ ## When to use it
40
+
41
+ - Use it to extract a service's OpenAPI document and generate its client
42
+ packages instead of maintaining per-service generator scripts.
43
+ - Reach for the `xema-openapi` and `xema-client-generate` bins to wire client
44
+ generation into a service's build.
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pnpm add -D @xemahq/api-client-generator
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```bash
55
+ # Extract one OpenAPI document per classified API surface:
56
+ xema-openapi
57
+
58
+ # Generate one Orval client package per non-empty surface:
59
+ xema-client-generate
60
+ ```
61
+
62
+ ## Peer requirements
63
+
64
+ - `@nestjs/common`, `@nestjs/core`, `@nestjs/swagger`, `@nestjs/testing` — the
65
+ service is bootstrapped to read its route metadata.
66
+ - `@xemahq/xema-decorators` — supplies the API-surface classification metadata.
67
+ - `orval` — drives client code generation.
68
+ - `reflect-metadata`, `ts-node`, `tsconfig-paths`, `typescript` — runtime and
69
+ TypeScript toolchain for extraction.
70
+
71
+ ## License
72
+
73
+ Apache-2.0 &copy; Xema — [xema.dev](https://xema.dev)
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=xema-client-generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xema-client-generate.d.ts","sourceRoot":"","sources":["../../src/bin/xema-client-generate.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const generate_clients_1 = require("../lib/generate-clients");
5
+ (0, generate_clients_1.runClientGenerateCli)();
6
+ //# sourceMappingURL=xema-client-generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xema-client-generate.js","sourceRoot":"","sources":["../../src/bin/xema-client-generate.ts"],"names":[],"mappings":";;;AAQA,8DAA+D;AAE/D,IAAA,uCAAoB,GAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=xema-openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xema-openapi.d.ts","sourceRoot":"","sources":["../../src/bin/xema-openapi.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const node_path_1 = require("node:path");
5
+ const { Module } = require('node:module');
6
+ function registerTsNode(cwd) {
7
+ const tsNode = require.resolve('ts-node', { paths: [cwd] });
8
+ require(tsNode).register({
9
+ transpileOnly: true,
10
+ });
11
+ try {
12
+ const paths = require.resolve('tsconfig-paths/register', { paths: [cwd] });
13
+ require(paths);
14
+ }
15
+ catch {
16
+ }
17
+ }
18
+ function ensureNodePath() {
19
+ process.env.NODE_PATH = process.env.NODE_PATH
20
+ ? `node_modules${node_path_1.delimiter}${process.env.NODE_PATH}`
21
+ : 'node_modules';
22
+ Module._initPaths();
23
+ }
24
+ ensureNodePath();
25
+ registerTsNode(process.cwd());
26
+ const { runOpenApiCli } = require('../lib/extract-openapi');
27
+ runOpenApiCli();
28
+ //# sourceMappingURL=xema-openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xema-openapi.js","sourceRoot":"","sources":["../../src/bin/xema-openapi.ts"],"names":[],"mappings":";;;AAWA,yCAAsC;AAEtC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAiC,CAAC;AAE1E,SAAS,cAAc,CAAC,GAAW;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAE3D,OAAO,CAAC,MAAM,CAAwC,CAAC,QAAQ,CAAC;QAC/D,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;IAET,CAAC;AACH,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS;QAC3C,CAAC,CAAC,eAAe,qBAAS,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE;QACpD,CAAC,CAAC,cAAc,CAAC;IAClB,MAAgD,CAAC,UAAU,EAAE,CAAC;AACjE,CAAC;AAED,cAAc,EAAE,CAAC;AACjB,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AAI9B,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,wBAAwB,CAEzD,CAAC;AACF,aAAa,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ export * from './lib/service-config';
2
+ export * from './lib/overrides';
3
+ export * from './lib/seed-openapi-env';
4
+ export * from './lib/default-stubs';
5
+ export * from './lib/prune-spec';
6
+ export * from './lib/extract-openapi';
7
+ export * from './lib/repo-post-process';
8
+ export * from './lib/client-package';
9
+ export * from './lib/generate-clients';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./lib/service-config"), exports);
18
+ __exportStar(require("./lib/overrides"), exports);
19
+ __exportStar(require("./lib/seed-openapi-env"), exports);
20
+ __exportStar(require("./lib/default-stubs"), exports);
21
+ __exportStar(require("./lib/prune-spec"), exports);
22
+ __exportStar(require("./lib/extract-openapi"), exports);
23
+ __exportStar(require("./lib/repo-post-process"), exports);
24
+ __exportStar(require("./lib/client-package"), exports);
25
+ __exportStar(require("./lib/generate-clients"), exports);
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,uDAAqC;AACrC,kDAAgC;AAChC,yDAAuC;AACvC,sDAAoC;AACpC,mDAAiC;AACjC,wDAAsC;AACtC,0DAAwC;AACxC,uDAAqC;AACrC,yDAAuC"}
@@ -0,0 +1,17 @@
1
+ import { ApiSurface } from '@xemahq/xema-decorators';
2
+ import { type ResolvedServiceContext } from './service-config';
3
+ export interface GeneratedClientXemaBlock {
4
+ readonly kind: 'api-client';
5
+ readonly surface: ApiSurface;
6
+ readonly service: string;
7
+ readonly biome: string;
8
+ readonly target: 'server';
9
+ readonly generator: string;
10
+ readonly source: string;
11
+ }
12
+ export declare function resolveMonotonicVersion(existing: unknown, serviceVersion: string): string;
13
+ export declare function ensureClientPackage(ctx: ResolvedServiceContext, surface: ApiSurface): {
14
+ clientDir: string;
15
+ clientSrc: string;
16
+ };
17
+ //# sourceMappingURL=client-package.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-package.d.ts","sourceRoot":"","sources":["../../src/lib/client-package.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAI1B,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAwCD,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,OAAO,EACjB,cAAc,EAAE,MAAM,GACrB,MAAM,CAqBR;AASD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,sBAAsB,EAC3B,OAAO,EAAE,UAAU,GAClB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAkE1C"}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveMonotonicVersion = resolveMonotonicVersion;
4
+ exports.ensureClientPackage = ensureClientPackage;
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const xema_decorators_1 = require("@xemahq/xema-decorators");
8
+ const service_config_1 = require("./service-config");
9
+ const custom_fetch_template_1 = require("./custom-fetch-template");
10
+ const CLIENT_TSCONFIG = {
11
+ compilerOptions: {
12
+ target: 'ES2022',
13
+ module: 'commonjs',
14
+ lib: ['ES2022', 'DOM'],
15
+ types: [],
16
+ strict: true,
17
+ esModuleInterop: true,
18
+ skipLibCheck: true,
19
+ declaration: true,
20
+ outDir: './dist',
21
+ rootDir: './src',
22
+ },
23
+ include: ['src/**/*.ts'],
24
+ };
25
+ function generatorTag() {
26
+ const pkg = require('../../package.json');
27
+ return `@xemahq/api-client-generator@${pkg.version}`;
28
+ }
29
+ function parseVersionCore(v) {
30
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
31
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
32
+ }
33
+ function resolveMonotonicVersion(existing, serviceVersion) {
34
+ if (typeof existing !== 'string' || existing.trim() === '') {
35
+ return serviceVersion;
36
+ }
37
+ const e = parseVersionCore(existing);
38
+ const s = parseVersionCore(serviceVersion);
39
+ if (!e || !s) {
40
+ return existing;
41
+ }
42
+ const [eMaj, eMin, ePat] = e;
43
+ const [sMaj, sMin, sPat] = s;
44
+ if (sMaj !== eMaj) {
45
+ return sMaj > eMaj ? serviceVersion : existing;
46
+ }
47
+ if (sMin !== eMin) {
48
+ return sMin > eMin ? serviceVersion : existing;
49
+ }
50
+ if (sPat !== ePat) {
51
+ return sPat > ePat ? serviceVersion : existing;
52
+ }
53
+ return existing;
54
+ }
55
+ function ensureClientPackage(ctx, surface) {
56
+ const clientDir = (0, service_config_1.surfaceClientDir)(ctx, surface);
57
+ const clientSrc = (0, node_path_1.join)(clientDir, 'src');
58
+ (0, node_fs_1.mkdirSync)(clientSrc, { recursive: true });
59
+ const packageName = (0, service_config_1.surfaceClientPackageName)(ctx.api.serviceId, surface);
60
+ const xema = {
61
+ kind: 'api-client',
62
+ surface,
63
+ service: ctx.api.serviceId,
64
+ biome: ctx.api.biomeId,
65
+ target: 'server',
66
+ generator: generatorTag(),
67
+ source: `openapi.${surface}.json`,
68
+ };
69
+ const pkgPath = (0, node_path_1.join)(clientDir, 'package.json');
70
+ const existing = (0, node_fs_1.existsSync)(pkgPath)
71
+ ? JSON.parse((0, node_fs_1.readFileSync)(pkgPath, 'utf8'))
72
+ : {};
73
+ const existingScripts = existing.scripts ?? {};
74
+ const existingDevDeps = existing.devDependencies ?? {};
75
+ const pkg = {
76
+ ...existing,
77
+ name: packageName,
78
+ version: resolveMonotonicVersion(existing.version, ctx.version),
79
+ main: './dist/index.js',
80
+ types: './dist/index.d.ts',
81
+ files: ['dist'],
82
+ scripts: {
83
+ ...existingScripts,
84
+ build: 'tsc -p tsconfig.json',
85
+ prepublishOnly: 'npm run build',
86
+ },
87
+ publishConfig: { registry: 'https://npm.pkg.github.com' },
88
+ devDependencies: {
89
+ ...existingDevDeps,
90
+ typescript: 'catalog:',
91
+ },
92
+ xema,
93
+ };
94
+ (0, node_fs_1.writeFileSync)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
95
+ const tsconfigPath = (0, node_path_1.join)(clientDir, 'tsconfig.json');
96
+ if (!(0, node_fs_1.existsSync)(tsconfigPath)) {
97
+ (0, node_fs_1.writeFileSync)(tsconfigPath, `${JSON.stringify(CLIENT_TSCONFIG, null, 2)}\n`, 'utf8');
98
+ }
99
+ ensureCustomFetch(ctx, clientSrc);
100
+ return { clientDir, clientSrc };
101
+ }
102
+ function ensureCustomFetch(ctx, clientSrc) {
103
+ const target = (0, node_path_1.join)(clientSrc, 'custom-fetch.ts');
104
+ if ((0, node_fs_1.existsSync)(target)) {
105
+ return;
106
+ }
107
+ const publicSibling = (0, node_path_1.join)((0, service_config_1.surfaceClientDir)(ctx, xema_decorators_1.ApiSurface.Public), 'src', 'custom-fetch.ts');
108
+ if ((0, node_fs_1.existsSync)(publicSibling)) {
109
+ (0, node_fs_1.cpSync)(publicSibling, target);
110
+ return;
111
+ }
112
+ (0, node_fs_1.writeFileSync)(target, custom_fetch_template_1.CANONICAL_CUSTOM_FETCH, 'utf8');
113
+ }
114
+ //# sourceMappingURL=client-package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-package.js","sourceRoot":"","sources":["../../src/lib/client-package.ts"],"names":[],"mappings":";;AAmEA,0DAwBC;AASD,kDAqEC;AAzKD,qCAMiB;AACjB,yCAAiC;AAEjC,6DAAqD;AAErD,qDAI0B;AAC1B,mEAAiE;AAajE,MAAM,eAAe,GAAG;IACtB,eAAe,EAAE;QACf,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,UAAU;QAClB,GAAG,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;QACtB,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,IAAI;QACZ,eAAe,EAAE,IAAI;QACrB,YAAY,EAAE,IAAI;QAClB,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,QAAQ;QAChB,OAAO,EAAE,OAAO;KACjB;IACD,OAAO,EAAE,CAAC,aAAa,CAAC;CACzB,CAAC;AAEF,SAAS,YAAY;IAEnB,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;IACjE,OAAO,gCAAgC,GAAG,CAAC,OAAO,EAAE,CAAC;AACvD,CAAC;AAGD,SAAS,gBAAgB,CAAC,CAAS;IACjC,MAAM,CAAC,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,CAAC;AAYD,SAAgB,uBAAuB,CACrC,QAAiB,EACjB,cAAsB;IAEtB,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC3D,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,MAAM,CAAC,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjD,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjD,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC;IACjD,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AASD,SAAgB,mBAAmB,CACjC,GAA2B,EAC3B,OAAmB;IAEnB,MAAM,SAAS,GAAG,IAAA,iCAAgB,EAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACzC,IAAA,mBAAS,EAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,MAAM,WAAW,GAAG,IAAA,yCAAwB,EAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACzE,MAAM,IAAI,GAA6B;QACrC,IAAI,EAAE,YAAY;QAClB,OAAO;QACP,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS;QAC1B,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO;QACtB,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,YAAY,EAAE;QACzB,MAAM,EAAE,WAAW,OAAO,OAAO;KAClC,CAAC;IAEF,MAAM,OAAO,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,IAAA,oBAAU,EAAC,OAAO,CAAC;QAClC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,OAAO,EAAE,MAAM,CAAC,CAA6B;QACxE,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,eAAe,GAAI,QAAQ,CAAC,OAAkC,IAAI,EAAE,CAAC;IAC3E,MAAM,eAAe,GAClB,QAAQ,CAAC,eAA0C,IAAI,EAAE,CAAC;IAQ7D,MAAM,GAAG,GAA4B;QACnC,GAAG,QAAQ;QACX,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,uBAAuB,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC;QAC/D,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,mBAAmB;QAC1B,KAAK,EAAE,CAAC,MAAM,CAAC;QACf,OAAO,EAAE;YACP,GAAG,eAAe;YAElB,KAAK,EAAE,sBAAsB;YAC7B,cAAc,EAAE,eAAe;SAChC;QACD,aAAa,EAAE,EAAE,QAAQ,EAAE,4BAA4B,EAAE;QACzD,eAAe,EAAE;YACf,GAAG,eAAe;YAElB,UAAU,EAAE,UAAU;SACvB;QAED,IAAI;KACL,CAAC;IACF,IAAA,uBAAa,EAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEpE,MAAM,YAAY,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IACtD,IAAI,CAAC,IAAA,oBAAU,EAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,IAAA,uBAAa,EACX,YAAY,EACZ,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAC/C,MAAM,CACP,CAAC;IACJ,CAAC;IAED,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAElC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAClC,CAAC;AASD,SAAS,iBAAiB,CACxB,GAA2B,EAC3B,SAAiB;IAEjB,MAAM,MAAM,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAClD,IAAI,IAAA,oBAAU,EAAC,MAAM,CAAC,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IACD,MAAM,aAAa,GAAG,IAAA,gBAAI,EACxB,IAAA,iCAAgB,EAAC,GAAG,EAAE,4BAAU,CAAC,MAAM,CAAC,EACxC,KAAK,EACL,iBAAiB,CAClB,CAAC;IACF,IAAI,IAAA,oBAAU,EAAC,aAAa,CAAC,EAAE,CAAC;QAC9B,IAAA,gBAAM,EAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IACD,IAAA,uBAAa,EAAC,MAAM,EAAE,8CAAsB,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { ApiSurface } from '@xemahq/xema-decorators';
2
+ export declare function ensureClientReadme(params: {
3
+ clientDir: string;
4
+ packageName: string;
5
+ serviceTitle: string;
6
+ surface: ApiSurface;
7
+ repoRoot: string;
8
+ }): void;
9
+ //# sourceMappingURL=client-readme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-readme.d.ts","sourceRoot":"","sources":["../../src/lib/client-readme.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AA+CrD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAmEP"}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureClientReadme = ensureClientReadme;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const xema_decorators_1 = require("@xemahq/xema-decorators");
7
+ const CLIENT_ACCENT = '#14B8A6';
8
+ function detectRepoSpdx(repoRoot) {
9
+ const licensePath = (0, node_path_1.join)(repoRoot, 'LICENSE');
10
+ if ((0, node_fs_1.existsSync)(licensePath)) {
11
+ const head = (0, node_fs_1.readFileSync)(licensePath, 'utf8').slice(0, 200);
12
+ if (/Apache License/i.test(head)) {
13
+ return 'Apache-2.0';
14
+ }
15
+ if (/all rights reserved/i.test(head)) {
16
+ return 'Proprietary';
17
+ }
18
+ }
19
+ return 'Proprietary';
20
+ }
21
+ function banner(packageName, tagline) {
22
+ return [
23
+ `<svg width="680" height="120" viewBox="0 0 680 120" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="${packageName}">`,
24
+ ` <rect width="680" height="120" rx="14" fill="#0B1020"/>`,
25
+ ` <g transform="translate(28,34)">`,
26
+ ` <path d="M26 0 L52 15 L52 45 L26 60 L0 45 L0 15 Z" fill="${CLIENT_ACCENT}" opacity="0.18"/>`,
27
+ ` <path d="M26 12 L41 21 L41 39 L26 48 L11 39 L11 21 Z" fill="${CLIENT_ACCENT}"/>`,
28
+ ` </g>`,
29
+ ` <text x="92" y="52" font-family="ui-monospace,SFMono-Regular,Menlo,monospace" font-size="22" fill="#F8FAFC" font-weight="700">${packageName}</text>`,
30
+ ` <text x="92" y="80" font-family="ui-sans-serif,system-ui,sans-serif" font-size="15" fill="#94A3B8">${tagline}</text>`,
31
+ ` <text x="652" y="105" text-anchor="end" font-family="ui-sans-serif,system-ui,sans-serif" font-size="12" fill="#475569">xema.dev</text>`,
32
+ `</svg>`,
33
+ ].join('\n');
34
+ }
35
+ function ensureClientReadme(params) {
36
+ const { clientDir, packageName, serviceTitle, surface, repoRoot } = params;
37
+ const enc = encodeURIComponent(packageName);
38
+ const spdx = detectRepoSpdx(repoRoot);
39
+ const surfaceWord = surface === xema_decorators_1.ApiSurface.Public ? '' : `${surface} `;
40
+ const tagline = `Typed, generated ${surfaceWord}HTTP client for the ${serviceTitle}.`;
41
+ const licenseLine = spdx === 'Apache-2.0'
42
+ ? 'Apache-2.0 &copy; Xema — [xema.dev](https://xema.dev)'
43
+ : 'Proprietary — &copy; Xema. All rights reserved. — [xema.dev](https://xema.dev)';
44
+ const md = `<!-- Generated by @xemahq/api-client-generator. Do not edit by hand. -->
45
+ <p align="center">
46
+ ${banner(packageName, tagline)}
47
+ </p>
48
+
49
+ <p align="center">
50
+ <a href="https://xema.dev">Website</a> &middot;
51
+ <a href="https://www.npmjs.com/package/${packageName}">npm</a>
52
+ </p>
53
+
54
+ <p align="center">
55
+ <img alt="npm" src="https://img.shields.io/npm/v/${enc}?color=2563eb&label=npm">
56
+ <img alt="license" src="https://img.shields.io/npm/l/${enc}?color=10b981">
57
+ <img alt="types" src="https://img.shields.io/npm/types/${enc}?color=3178c6">
58
+ </p>
59
+
60
+ # ${packageName}
61
+
62
+ > ${tagline}
63
+
64
+ ## Overview
65
+
66
+ Auto-generated TypeScript client for the **${serviceTitle}**. It exports typed
67
+ request functions and response models that mirror the service's OpenAPI surface,
68
+ so callers get end-to-end type safety without hand-writing HTTP calls. This
69
+ package is produced by \`@xemahq/api-client-generator\` and regenerated whenever
70
+ the service's API changes — do not edit it by hand.
71
+
72
+ ## When to use it
73
+
74
+ - Use it from any TypeScript service or app that calls the ${serviceTitle} over HTTP.
75
+ - You get compile-time types for every endpoint and payload; regenerate to pick
76
+ up API changes rather than editing the client.
77
+
78
+ ## Installation
79
+
80
+ \`\`\`bash
81
+ pnpm add ${packageName}
82
+ \`\`\`
83
+
84
+ ## Usage
85
+
86
+ \`\`\`ts
87
+ // Every endpoint function and response model is exported from the package root.
88
+ import * as client from '${packageName}';
89
+ \`\`\`
90
+
91
+ Call the typed endpoint functions; request and response shapes are fully typed
92
+ from the service's OpenAPI spec.
93
+
94
+ ## License
95
+
96
+ ${licenseLine}
97
+ `;
98
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(clientDir, 'README.md'), md, 'utf8');
99
+ }
100
+ //# sourceMappingURL=client-readme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-readme.js","sourceRoot":"","sources":["../../src/lib/client-readme.ts"],"names":[],"mappings":";;AAkDA,gDAyEC;AA3HD,qCAAkE;AAClE,yCAAiC;AAEjC,6DAAqD;AAYrD,MAAM,aAAa,GAAG,SAAS,CAAC;AAEhC,SAAS,cAAc,CAAC,QAAgB;IACtC,MAAM,WAAW,GAAG,IAAA,gBAAI,EAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC9C,IAAI,IAAA,oBAAU,EAAC,WAAW,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAA,sBAAY,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC7D,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,aAAa,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,SAAS,MAAM,CAAC,WAAmB,EAAE,OAAe;IAClD,OAAO;QACL,6HAA6H,WAAW,IAAI;QAC5I,2DAA2D;QAC3D,oCAAoC;QACpC,gEAAgE,aAAa,oBAAoB;QACjG,mEAAmE,aAAa,KAAK;QACrF,QAAQ;QACR,mIAAmI,WAAW,SAAS;QACvJ,wGAAwG,OAAO,SAAS;QACxH,0IAA0I;QAC1I,QAAQ;KACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAMD,SAAgB,kBAAkB,CAAC,MAMlC;IACC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAC3E,MAAM,GAAG,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,WAAW,GAAG,OAAO,KAAK,4BAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC;IACvE,MAAM,OAAO,GAAG,oBAAoB,WAAW,uBAAuB,YAAY,GAAG,CAAC;IACtF,MAAM,WAAW,GACf,IAAI,KAAK,YAAY;QACnB,CAAC,CAAC,uDAAuD;QACzD,CAAC,CAAC,gFAAgF,CAAC;IAEvF,MAAM,EAAE,GAAG;;EAEX,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC;;;;;2CAKa,WAAW;;;;qDAID,GAAG;yDACC,GAAG;2DACD,GAAG;;;IAG1D,WAAW;;IAEX,OAAO;;;;6CAIkC,YAAY;;;;;;;;6DAQI,YAAY;;;;;;;WAO9D,WAAW;;;;;;;2BAOK,WAAW;;;;;;;;EAQpC,WAAW;CACZ,CAAC;IAEA,IAAA,uBAAa,EAAC,IAAA,gBAAI,EAAC,SAAS,EAAE,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const CANONICAL_CUSTOM_FETCH = "/**\n * Configurable fetch wrapper for Orval-generated clients.\n *\n * Consumers must call `configureClient()` before using any endpoint function.\n * The baseUrl is prepended to the relative paths generated by Orval.\n */\n\nexport interface ClientConfig {\n /** Static base URL (e.g. 'http://localhost:3140') \u2014 no trailing slash. */\n baseUrl?: string;\n /** Per-request base-URL resolver (service-registry-safe). */\n baseUrlResolver?: () => string | Promise<string>;\n /** Optional async callback to get an auth token. */\n getAuthToken?: () => Promise<string>;\n /** Optional callback returning headers to inject on every request. */\n getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n /** Optional callback invoked on 401 before a single retry. */\n onUnauthorized?: () => Promise<void>;\n /** Maximum retry attempts for transient failures (default: 3). */\n maxRetries?: number;\n}\n\nexport class ClientError extends Error {\n constructor(\n public readonly status: number,\n public readonly url: string,\n public readonly body: unknown,\n ) {\n super(`HTTP ${status} from ${url}`);\n this.name = 'ClientError';\n }\n}\n\nlet clientConfig: ClientConfig | null = null;\n\nexport function configureClient(config: ClientConfig): void {\n clientConfig = config;\n}\n\nexport function getClientConfig(): ClientConfig {\n if (!clientConfig) {\n throw new Error(\n 'Client not configured. Call configureClient({ baseUrl }) before using endpoint functions.',\n );\n }\n return clientConfig;\n}\n\nconst RETRYABLE_STATUSES = [429, 502, 503, 504];\n\nasync function buildHeaders(\n config: ClientConfig,\n callerHeaders: RequestInit['headers'],\n): Promise<Headers> {\n const headers = new Headers(callerHeaders);\n if (config.getHeaders) {\n const globalHeaders = await Promise.resolve(config.getHeaders());\n for (const [key, value] of Object.entries(globalHeaders)) {\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n }\n }\n if (config.getAuthToken && !headers.has('Authorization')) {\n const token = await config.getAuthToken();\n headers.set('Authorization', `Bearer ${token}`);\n }\n return headers;\n}\n\nexport const customFetch = async <T>(\n url: string,\n options: RequestInit,\n): Promise<T> => {\n const config = getClientConfig();\n const base = config.baseUrlResolver\n ? await config.baseUrlResolver()\n : config.baseUrl;\n if (base === undefined) {\n throw new Error(\n 'Client not configured: set baseUrl or baseUrlResolver via configureClient().',\n );\n }\n const fullUrl = `${base}${url}`;\n const maxRetries = config.maxRetries ?? 3;\n\n const headers = await buildHeaders(config, options.headers);\n const requestInit: RequestInit = { ...options, headers };\n\n let delay = 1000;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(fullUrl, requestInit);\n\n if (response.status === 401 && config.onUnauthorized && attempt === 0) {\n await config.onUnauthorized();\n const refreshedHeaders = await buildHeaders(config, options.headers);\n const retryResponse = await fetch(fullUrl, { ...options, headers: refreshedHeaders });\n const retryBody = await parseBody(retryResponse);\n if (retryResponse.status >= 400) {\n throw new ClientError(retryResponse.status, fullUrl, retryBody);\n }\n return retryBody as T;\n }\n\n if (!RETRYABLE_STATUSES.includes(response.status) || attempt >= maxRetries) {\n const body = await parseBody(response);\n if (response.status >= 400) {\n throw new ClientError(response.status, fullUrl, body);\n }\n return body as T;\n }\n\n const retryAfter = parseRetryAfter(response.headers.get('Retry-After'));\n const waitMs = retryAfter ?? addJitter(delay);\n await sleep(waitMs);\n delay = Math.min(delay * 2, 30_000);\n } catch (error) {\n if (error instanceof ClientError) throw error;\n lastError = error instanceof Error ? error : new Error(String(error));\n if (attempt >= maxRetries) throw lastError;\n const waitMs = addJitter(delay);\n await sleep(waitMs);\n delay = Math.min(delay * 2, 30_000);\n }\n }\n\n throw lastError ?? new Error(`All retries exhausted for ${fullUrl}`);\n};\n\nasync function parseBody(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type');\n if (contentType?.includes('application/json')) {\n return response.json();\n }\n if (response.status === 204) {\n return undefined;\n }\n return response.text();\n}\n\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (!isNaN(seconds) && seconds >= 0) return seconds * 1000;\n const date = new Date(value);\n if (!isNaN(date.getTime())) return Math.max(0, date.getTime() - Date.now());\n return undefined;\n}\n\nfunction addJitter(delay: number): number {\n return delay + (Math.random() * 2 - 1) * delay * 0.25;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport default customFetch;\n";
2
+ //# sourceMappingURL=custom-fetch-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-fetch-template.d.ts","sourceRoot":"","sources":["../../src/lib/custom-fetch-template.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,sBAAsB,mvKAiKlC,CAAC"}
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CANONICAL_CUSTOM_FETCH = void 0;
4
+ exports.CANONICAL_CUSTOM_FETCH = `/**
5
+ * Configurable fetch wrapper for Orval-generated clients.
6
+ *
7
+ * Consumers must call \`configureClient()\` before using any endpoint function.
8
+ * The baseUrl is prepended to the relative paths generated by Orval.
9
+ */
10
+
11
+ export interface ClientConfig {
12
+ /** Static base URL (e.g. 'http://localhost:3140') — no trailing slash. */
13
+ baseUrl?: string;
14
+ /** Per-request base-URL resolver (service-registry-safe). */
15
+ baseUrlResolver?: () => string | Promise<string>;
16
+ /** Optional async callback to get an auth token. */
17
+ getAuthToken?: () => Promise<string>;
18
+ /** Optional callback returning headers to inject on every request. */
19
+ getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
20
+ /** Optional callback invoked on 401 before a single retry. */
21
+ onUnauthorized?: () => Promise<void>;
22
+ /** Maximum retry attempts for transient failures (default: 3). */
23
+ maxRetries?: number;
24
+ }
25
+
26
+ export class ClientError extends Error {
27
+ constructor(
28
+ public readonly status: number,
29
+ public readonly url: string,
30
+ public readonly body: unknown,
31
+ ) {
32
+ super(\`HTTP \${status} from \${url}\`);
33
+ this.name = 'ClientError';
34
+ }
35
+ }
36
+
37
+ let clientConfig: ClientConfig | null = null;
38
+
39
+ export function configureClient(config: ClientConfig): void {
40
+ clientConfig = config;
41
+ }
42
+
43
+ export function getClientConfig(): ClientConfig {
44
+ if (!clientConfig) {
45
+ throw new Error(
46
+ 'Client not configured. Call configureClient({ baseUrl }) before using endpoint functions.',
47
+ );
48
+ }
49
+ return clientConfig;
50
+ }
51
+
52
+ const RETRYABLE_STATUSES = [429, 502, 503, 504];
53
+
54
+ async function buildHeaders(
55
+ config: ClientConfig,
56
+ callerHeaders: RequestInit['headers'],
57
+ ): Promise<Headers> {
58
+ const headers = new Headers(callerHeaders);
59
+ if (config.getHeaders) {
60
+ const globalHeaders = await Promise.resolve(config.getHeaders());
61
+ for (const [key, value] of Object.entries(globalHeaders)) {
62
+ if (!headers.has(key)) {
63
+ headers.set(key, value);
64
+ }
65
+ }
66
+ }
67
+ if (config.getAuthToken && !headers.has('Authorization')) {
68
+ const token = await config.getAuthToken();
69
+ headers.set('Authorization', \`Bearer \${token}\`);
70
+ }
71
+ return headers;
72
+ }
73
+
74
+ export const customFetch = async <T>(
75
+ url: string,
76
+ options: RequestInit,
77
+ ): Promise<T> => {
78
+ const config = getClientConfig();
79
+ const base = config.baseUrlResolver
80
+ ? await config.baseUrlResolver()
81
+ : config.baseUrl;
82
+ if (base === undefined) {
83
+ throw new Error(
84
+ 'Client not configured: set baseUrl or baseUrlResolver via configureClient().',
85
+ );
86
+ }
87
+ const fullUrl = \`\${base}\${url}\`;
88
+ const maxRetries = config.maxRetries ?? 3;
89
+
90
+ const headers = await buildHeaders(config, options.headers);
91
+ const requestInit: RequestInit = { ...options, headers };
92
+
93
+ let delay = 1000;
94
+ let lastError: Error | undefined;
95
+
96
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
97
+ try {
98
+ const response = await fetch(fullUrl, requestInit);
99
+
100
+ if (response.status === 401 && config.onUnauthorized && attempt === 0) {
101
+ await config.onUnauthorized();
102
+ const refreshedHeaders = await buildHeaders(config, options.headers);
103
+ const retryResponse = await fetch(fullUrl, { ...options, headers: refreshedHeaders });
104
+ const retryBody = await parseBody(retryResponse);
105
+ if (retryResponse.status >= 400) {
106
+ throw new ClientError(retryResponse.status, fullUrl, retryBody);
107
+ }
108
+ return retryBody as T;
109
+ }
110
+
111
+ if (!RETRYABLE_STATUSES.includes(response.status) || attempt >= maxRetries) {
112
+ const body = await parseBody(response);
113
+ if (response.status >= 400) {
114
+ throw new ClientError(response.status, fullUrl, body);
115
+ }
116
+ return body as T;
117
+ }
118
+
119
+ const retryAfter = parseRetryAfter(response.headers.get('Retry-After'));
120
+ const waitMs = retryAfter ?? addJitter(delay);
121
+ await sleep(waitMs);
122
+ delay = Math.min(delay * 2, 30_000);
123
+ } catch (error) {
124
+ if (error instanceof ClientError) throw error;
125
+ lastError = error instanceof Error ? error : new Error(String(error));
126
+ if (attempt >= maxRetries) throw lastError;
127
+ const waitMs = addJitter(delay);
128
+ await sleep(waitMs);
129
+ delay = Math.min(delay * 2, 30_000);
130
+ }
131
+ }
132
+
133
+ throw lastError ?? new Error(\`All retries exhausted for \${fullUrl}\`);
134
+ };
135
+
136
+ async function parseBody(response: Response): Promise<unknown> {
137
+ const contentType = response.headers.get('content-type');
138
+ if (contentType?.includes('application/json')) {
139
+ return response.json();
140
+ }
141
+ if (response.status === 204) {
142
+ return undefined;
143
+ }
144
+ return response.text();
145
+ }
146
+
147
+ function parseRetryAfter(value: string | null): number | undefined {
148
+ if (!value) return undefined;
149
+ const seconds = Number(value);
150
+ if (!isNaN(seconds) && seconds >= 0) return seconds * 1000;
151
+ const date = new Date(value);
152
+ if (!isNaN(date.getTime())) return Math.max(0, date.getTime() - Date.now());
153
+ return undefined;
154
+ }
155
+
156
+ function addJitter(delay: number): number {
157
+ return delay + (Math.random() * 2 - 1) * delay * 0.25;
158
+ }
159
+
160
+ function sleep(ms: number): Promise<void> {
161
+ return new Promise(resolve => setTimeout(resolve, ms));
162
+ }
163
+
164
+ export default customFetch;
165
+ `;
166
+ //# sourceMappingURL=custom-fetch-template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-fetch-template.js","sourceRoot":"","sources":["../../src/lib/custom-fetch-template.ts"],"names":[],"mappings":";;;AAOa,QAAA,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiKrC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TestingModuleBuilder } from '@nestjs/testing';
2
+ export declare function applyDefaultOpenApiStubs(builder: TestingModuleBuilder, serviceDir: string): TestingModuleBuilder;
3
+ //# sourceMappingURL=default-stubs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-stubs.d.ts","sourceRoot":"","sources":["../../src/lib/default-stubs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AA4E5D,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,oBAAoB,EAC7B,UAAU,EAAE,MAAM,GACjB,oBAAoB,CA4HtB"}