@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.
- package/README.md +73 -0
- package/dist/bin/xema-client-generate.d.ts +3 -0
- package/dist/bin/xema-client-generate.d.ts.map +1 -0
- package/dist/bin/xema-client-generate.js +6 -0
- package/dist/bin/xema-client-generate.js.map +1 -0
- package/dist/bin/xema-openapi.d.ts +3 -0
- package/dist/bin/xema-openapi.d.ts.map +1 -0
- package/dist/bin/xema-openapi.js +28 -0
- package/dist/bin/xema-openapi.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/client-package.d.ts +17 -0
- package/dist/lib/client-package.d.ts.map +1 -0
- package/dist/lib/client-package.js +114 -0
- package/dist/lib/client-package.js.map +1 -0
- package/dist/lib/client-readme.d.ts +9 -0
- package/dist/lib/client-readme.d.ts.map +1 -0
- package/dist/lib/client-readme.js +100 -0
- package/dist/lib/client-readme.js.map +1 -0
- package/dist/lib/custom-fetch-template.d.ts +2 -0
- package/dist/lib/custom-fetch-template.d.ts.map +1 -0
- package/dist/lib/custom-fetch-template.js +166 -0
- package/dist/lib/custom-fetch-template.js.map +1 -0
- package/dist/lib/default-stubs.d.ts +3 -0
- package/dist/lib/default-stubs.d.ts.map +1 -0
- package/dist/lib/default-stubs.js +107 -0
- package/dist/lib/default-stubs.js.map +1 -0
- package/dist/lib/extract-openapi.d.ts +4 -0
- package/dist/lib/extract-openapi.d.ts.map +1 -0
- package/dist/lib/extract-openapi.js +134 -0
- package/dist/lib/extract-openapi.js.map +1 -0
- package/dist/lib/generate-clients.d.ts +4 -0
- package/dist/lib/generate-clients.d.ts.map +1 -0
- package/dist/lib/generate-clients.js +84 -0
- package/dist/lib/generate-clients.js.map +1 -0
- package/dist/lib/overrides.d.ts +7 -0
- package/dist/lib/overrides.d.ts.map +1 -0
- package/dist/lib/overrides.js +26 -0
- package/dist/lib/overrides.js.map +1 -0
- package/dist/lib/prune-spec.d.ts +16 -0
- package/dist/lib/prune-spec.d.ts.map +1 -0
- package/dist/lib/prune-spec.js +100 -0
- package/dist/lib/prune-spec.js.map +1 -0
- package/dist/lib/repo-post-process.d.ts +9 -0
- package/dist/lib/repo-post-process.d.ts.map +1 -0
- package/dist/lib/repo-post-process.js +56 -0
- package/dist/lib/repo-post-process.js.map +1 -0
- package/dist/lib/seed-openapi-env.d.ts +2 -0
- package/dist/lib/seed-openapi-env.d.ts.map +1 -0
- package/dist/lib/seed-openapi-env.js +77 -0
- package/dist/lib/seed-openapi-env.js.map +1 -0
- package/dist/lib/service-config.d.ts +20 -0
- package/dist/lib/service-config.d.ts.map +1 -0
- package/dist/lib/service-config.js +80 -0
- package/dist/lib/service-config.js.map +1 -0
- package/dist/lib/service-nest.d.ts +15 -0
- package/dist/lib/service-nest.d.ts.map +1 -0
- package/dist/lib/service-nest.js +21 -0
- package/dist/lib/service-nest.js.map +1 -0
- 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> ·
|
|
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 © Xema — [xema.dev](https://xema.dev)
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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":"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"}
|
|
@@ -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 © Xema — [xema.dev](https://xema.dev)'
|
|
43
|
+
: 'Proprietary — © 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> ·
|
|
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 @@
|
|
|
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"}
|