@apollo-deploy/tesseract 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -110
- package/dist/adapters/csharp/index.d.ts +5 -8
- package/dist/adapters/csharp/index.d.ts.map +1 -1
- package/dist/adapters/csharp/index.js +314 -74
- package/dist/adapters/csharp/index.js.map +1 -1
- package/dist/adapters/documentation.d.ts +17 -0
- package/dist/adapters/documentation.d.ts.map +1 -0
- package/dist/adapters/documentation.js +268 -0
- package/dist/adapters/documentation.js.map +1 -0
- package/dist/adapters/go/index.d.ts +5 -8
- package/dist/adapters/go/index.d.ts.map +1 -1
- package/dist/adapters/go/index.js +197 -101
- package/dist/adapters/go/index.js.map +1 -1
- package/dist/adapters/kotlin/index.d.ts +5 -9
- package/dist/adapters/kotlin/index.d.ts.map +1 -1
- package/dist/adapters/kotlin/index.js +280 -212
- package/dist/adapters/kotlin/index.js.map +1 -1
- package/dist/adapters/php/index.d.ts +5 -15
- package/dist/adapters/php/index.d.ts.map +1 -1
- package/dist/adapters/php/index.js +362 -90
- package/dist/adapters/php/index.js.map +1 -1
- package/dist/adapters/python/index.d.ts +5 -8
- package/dist/adapters/python/index.d.ts.map +1 -1
- package/dist/adapters/python/index.js +240 -109
- package/dist/adapters/python/index.js.map +1 -1
- package/dist/adapters/ruby/index.d.ts +3 -3
- package/dist/adapters/ruby/index.d.ts.map +1 -1
- package/dist/adapters/ruby/index.js +235 -46
- package/dist/adapters/ruby/index.js.map +1 -1
- package/dist/adapters/rust/index.d.ts +5 -9
- package/dist/adapters/rust/index.d.ts.map +1 -1
- package/dist/adapters/rust/index.js +237 -169
- package/dist/adapters/rust/index.js.map +1 -1
- package/dist/adapters/shared.d.ts +7 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +68 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/type-utils.d.ts +13 -0
- package/dist/adapters/type-utils.d.ts.map +1 -0
- package/dist/adapters/type-utils.js +105 -0
- package/dist/adapters/type-utils.js.map +1 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitSchema.js +39 -27
- package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +15 -15
- package/dist/adapters/typescript/emitter/importCollector.d.ts +2 -4
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.js +40 -50
- package/dist/adapters/typescript/emitter/importCollector.js.map +1 -1
- package/dist/adapters/typescript/emitter/parsers.d.ts +1 -1
- package/dist/adapters/typescript/emitter/parsers.js +13 -13
- package/dist/adapters/typescript/emitter/typeHoister.d.ts +1 -1
- package/dist/adapters/typescript/emitter/typeHoister.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/typeHoister.js +12 -20
- package/dist/adapters/typescript/emitter/typeHoister.js.map +1 -1
- package/dist/adapters/typescript/index.d.ts +3 -3
- package/dist/adapters/typescript/index.d.ts.map +1 -1
- package/dist/adapters/typescript/index.js +148 -166
- package/dist/adapters/typescript/index.js.map +1 -1
- package/dist/adapters/typescript/shared.d.ts +2 -2
- package/dist/adapters/typescript/shared.js +12 -12
- package/dist/cli.js +50 -37
- package/dist/cli.js.map +1 -1
- package/dist/collector.d.ts +32 -7
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +149 -49
- package/dist/collector.js.map +1 -1
- package/dist/elysia.js +2 -2
- package/dist/elysia.js.map +1 -1
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js.map +1 -1
- package/dist/fastify.d.ts +9 -2
- package/dist/fastify.d.ts.map +1 -1
- package/dist/fastify.js +40 -21
- package/dist/fastify.js.map +1 -1
- package/dist/helpers/handlebars.d.ts +7 -16
- package/dist/helpers/handlebars.d.ts.map +1 -1
- package/dist/helpers/handlebars.js +127 -177
- package/dist/helpers/handlebars.js.map +1 -1
- package/dist/hono.d.ts.map +1 -1
- package/dist/hono.js.map +1 -1
- package/dist/index.d.ts +14 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -30
- package/dist/index.js.map +1 -1
- package/dist/koa.d.ts.map +1 -1
- package/dist/koa.js.map +1 -1
- package/dist/nestjs.d.ts.map +1 -1
- package/dist/nestjs.js +41 -14
- package/dist/nestjs.js.map +1 -1
- package/dist/pipeline/enrich.d.ts +2 -13
- package/dist/pipeline/enrich.d.ts.map +1 -1
- package/dist/pipeline/enrich.js +157 -212
- package/dist/pipeline/enrich.js.map +1 -1
- package/dist/pipeline/intake.d.ts +3 -3
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +342 -238
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/pipeline/write.d.ts +1 -14
- package/dist/pipeline/write.d.ts.map +1 -1
- package/dist/pipeline/write.js +73 -113
- package/dist/pipeline/write.js.map +1 -1
- package/dist/types/config.d.ts +14 -26
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +16 -12
- package/dist/types/config.js.map +1 -1
- package/dist/types/ir.d.ts +12 -8
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/manifest.d.ts +10 -11
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js +54 -14
- package/dist/types/manifest.js.map +1 -1
- package/dist/types/sdk-module.d.ts +2 -2
- package/dist/types/sdk-module.js +1 -1
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +3 -10
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/naming.d.ts.map +1 -1
- package/dist/utils/naming.js +174 -184
- package/dist/utils/naming.js.map +1 -1
- package/dist/utils/npm-token.d.ts +1 -17
- package/dist/utils/npm-token.d.ts.map +1 -1
- package/dist/utils/npm-token.js +27 -76
- package/dist/utils/npm-token.js.map +1 -1
- package/dist/utils/version.d.ts +14 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +60 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +4 -3
- package/templates/csharp/client.hbs +26 -49
- package/templates/csharp/csproj.hbs +7 -0
- package/templates/csharp/domain.hbs +48 -130
- package/templates/csharp/errors.hbs +17 -25
- package/templates/csharp/readme.hbs +31 -44
- package/templates/csharp/transport.hbs +238 -136
- package/templates/csharp/types.hbs +30 -76
- package/templates/go/client.hbs +44 -109
- package/templates/go/domain.hbs +83 -25
- package/templates/go/go-mod.hbs +1 -4
- package/templates/go/readme.hbs +32 -29
- package/templates/go/transport.hbs +174 -173
- package/templates/go/types.hbs +17 -53
- package/templates/go/utils.hbs +3 -2
- package/templates/kotlin/build-gradle.hbs +1 -1
- package/templates/kotlin/client.hbs +18 -15
- package/templates/kotlin/domain.hbs +54 -51
- package/templates/kotlin/errors.hbs +1 -1
- package/templates/kotlin/readme.hbs +35 -137
- package/templates/kotlin/transport.hbs +140 -123
- package/templates/kotlin/types.hbs +30 -81
- package/templates/php/client.hbs +8 -46
- package/templates/php/composer.hbs +2 -1
- package/templates/php/domain.hbs +86 -117
- package/templates/php/errors.hbs +1 -1
- package/templates/php/readme.hbs +42 -49
- package/templates/php/transport.hbs +192 -145
- package/templates/php/types.hbs +63 -60
- package/templates/python/client.hbs +15 -2
- package/templates/python/domain.hbs +50 -108
- package/templates/python/pyproject.hbs +5 -0
- package/templates/python/readme.hbs +47 -30
- package/templates/python/resources-init.hbs +1 -1
- package/templates/python/transport.hbs +167 -53
- package/templates/python/types.hbs +22 -58
- package/templates/ruby/client.hbs +10 -6
- package/templates/ruby/domain.hbs +48 -67
- package/templates/ruby/errors.hbs +3 -3
- package/templates/ruby/gemspec.hbs +3 -10
- package/templates/ruby/index.hbs +3 -3
- package/templates/ruby/readme.hbs +33 -70
- package/templates/ruby/transport.hbs +129 -103
- package/templates/ruby/types.hbs +11 -16
- package/templates/ruby/version.hbs +1 -1
- package/templates/rust/cargo.hbs +2 -0
- package/templates/rust/client.hbs +10 -1
- package/templates/rust/domain.hbs +69 -52
- package/templates/rust/error.hbs +3 -1
- package/templates/rust/lib.hbs +4 -5
- package/templates/rust/readme.hbs +33 -46
- package/templates/rust/transport.hbs +170 -159
- package/templates/rust/types.hbs +34 -111
- package/templates/typescript/domain-class.hbs +14 -6
- package/templates/typescript/domain.hbs +13 -5
- package/templates/typescript/package-json.hbs +3 -1
- package/templates/typescript/readme.hbs +42 -42
- package/templates/typescript/transport.hbs +46 -8
- package/templates/typescript/types-errors.hbs +1 -1
- package/dist/adapters/typescript/model-emitter.d.ts +0 -13
- package/dist/adapters/typescript/model-emitter.d.ts.map +0 -1
- package/dist/adapters/typescript/model-emitter.js +0 -415
- package/dist/adapters/typescript/model-emitter.js.map +0 -1
|
@@ -10,6 +10,7 @@ declare(strict_types=1);
|
|
|
10
10
|
|
|
11
11
|
namespace {{namespace}};
|
|
12
12
|
|
|
13
|
+
use DateTimeImmutable;
|
|
13
14
|
use GuzzleHttp\Client as GuzzleClient;
|
|
14
15
|
use GuzzleHttp\ClientInterface;
|
|
15
16
|
use GuzzleHttp\Exception\ConnectException;
|
|
@@ -19,15 +20,11 @@ use GuzzleHttp\RequestOptions;
|
|
|
19
20
|
use Psr\Http\Message\ResponseInterface;
|
|
20
21
|
use Throwable;
|
|
21
22
|
|
|
22
|
-
/**
|
|
23
|
-
* HTTP transport layer with retry logic and security scheme injection.
|
|
24
|
-
*
|
|
25
|
-
* Wraps a GuzzleHttp\Client and provides executeRequest() for typed
|
|
26
|
-
* request/response handling with automatic auth header injection
|
|
27
|
-
* and configurable retry with exponential backoff + jitter.
|
|
28
|
-
*/
|
|
29
23
|
class Transport
|
|
30
24
|
{
|
|
25
|
+
private const RETRYABLE_METHODS = ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'];
|
|
26
|
+
private const RETRYABLE_STATUSES = [408, 425, 429, 500, 502, 503, 504];
|
|
27
|
+
|
|
31
28
|
private ClientInterface $http;
|
|
32
29
|
|
|
33
30
|
/** @var array<string, mixed> */
|
|
@@ -38,6 +35,7 @@ class Transport
|
|
|
38
35
|
* baseUrl?: string,
|
|
39
36
|
* timeoutMs?: int,
|
|
40
37
|
* retries?: array{attempts?: int, backoffMs?: int, jitter?: bool, maxBackoffMs?: int},
|
|
38
|
+
* retryUnsafeRequests?: bool,
|
|
41
39
|
* defaultHeaders?: array<string, string>,
|
|
42
40
|
{{#each securitySchemes}}
|
|
43
41
|
* {{configKey}}?: string,
|
|
@@ -46,14 +44,16 @@ class Transport
|
|
|
46
44
|
*/
|
|
47
45
|
public function __construct(array $config = [])
|
|
48
46
|
{
|
|
49
|
-
$
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
$timeoutMs = (int) ($config['timeoutMs'] ?? 15000);
|
|
48
|
+
if ($timeoutMs <= 0) {
|
|
49
|
+
throw new \InvalidArgumentException('timeoutMs must be greater than zero');
|
|
50
|
+
}
|
|
53
51
|
|
|
52
|
+
$this->config = $config;
|
|
53
|
+
$baseUrl = rtrim((string) ($config['baseUrl'] ?? '{{baseUrl}}'), '/');
|
|
54
54
|
$headers = $config['defaultHeaders'] ?? [];
|
|
55
55
|
$headers['Accept'] = $headers['Accept'] ?? 'application/json';
|
|
56
|
-
$headers['User-Agent'] = $headers['User-Agent'] ?? '{{clientName}}-php-sdk/{{
|
|
56
|
+
$headers['User-Agent'] = $headers['User-Agent'] ?? '{{clientName}}-php-sdk/{{packageVersion}}';
|
|
57
57
|
|
|
58
58
|
$this->http = new GuzzleClient([
|
|
59
59
|
'base_uri' => $baseUrl . '/',
|
|
@@ -64,217 +64,264 @@ class Transport
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Execute an HTTP request with retry logic and auth injection.
|
|
68
|
-
*
|
|
69
67
|
* @param array{
|
|
70
68
|
* method: string,
|
|
71
69
|
* path: string,
|
|
72
70
|
* query?: array<string, mixed>,
|
|
73
71
|
* body?: mixed,
|
|
74
72
|
* headers?: array<string, string>,
|
|
73
|
+
* contentType?: string,
|
|
75
74
|
* timeoutMs?: int,
|
|
75
|
+
* retries?: int,
|
|
76
|
+
* retryUnsafeRequests?: bool,
|
|
77
|
+
* stream?: bool,
|
|
76
78
|
* } $requestDef
|
|
77
|
-
* @return ResponseInterface
|
|
78
79
|
* @throws SDKError
|
|
79
80
|
*/
|
|
80
81
|
public function executeRequest(array $requestDef): ResponseInterface
|
|
81
82
|
{
|
|
82
|
-
$method = strtoupper($requestDef['method']);
|
|
83
|
-
$path = ltrim($requestDef['path'], '/');
|
|
83
|
+
$method = strtoupper((string) $requestDef['method']);
|
|
84
|
+
$path = ltrim((string) $requestDef['path'], '/');
|
|
84
85
|
$query = $requestDef['query'] ?? [];
|
|
85
86
|
$body = $requestDef['body'] ?? null;
|
|
86
|
-
$
|
|
87
|
-
$
|
|
88
|
-
|
|
89
|
-
// ── Inject auth headers ────────────────────────────────────────────
|
|
90
|
-
$authHeaders = $this->buildAuthHeaders();
|
|
91
|
-
|
|
92
|
-
$options = [
|
|
93
|
-
RequestOptions::HEADERS => array_merge($authHeaders, $extraHeaders),
|
|
94
|
-
];
|
|
87
|
+
$headers = array_merge($this->buildAuthHeaders(), $requestDef['headers'] ?? []);
|
|
88
|
+
$contentType = (string) ($requestDef['contentType'] ?? 'application/json');
|
|
95
89
|
|
|
96
|
-
|
|
90
|
+
$options = [RequestOptions::HEADERS => $headers];
|
|
91
|
+
if ($query !== []) {
|
|
97
92
|
$options[RequestOptions::QUERY] = $query;
|
|
98
93
|
}
|
|
99
|
-
|
|
100
94
|
if ($body !== null) {
|
|
101
|
-
$
|
|
95
|
+
if ($contentType === 'application/json') {
|
|
96
|
+
$options[RequestOptions::JSON] = $body;
|
|
97
|
+
} elseif ($contentType === 'multipart/form-data' && is_array($body)) {
|
|
98
|
+
$options[RequestOptions::MULTIPART] = $body;
|
|
99
|
+
} else {
|
|
100
|
+
$options[RequestOptions::BODY] = $body;
|
|
101
|
+
$options[RequestOptions::HEADERS]['Content-Type'] = $contentType;
|
|
102
|
+
}
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
if (($requestDef['stream'] ?? false) === true) {
|
|
105
|
+
$options[RequestOptions::STREAM] = true;
|
|
106
|
+
}
|
|
107
|
+
if (isset($requestDef['timeoutMs'])) {
|
|
108
|
+
$timeoutMs = (int) $requestDef['timeoutMs'];
|
|
109
|
+
if ($timeoutMs <= 0) {
|
|
110
|
+
throw new \InvalidArgumentException('timeoutMs must be greater than zero');
|
|
111
|
+
}
|
|
105
112
|
$options[RequestOptions::TIMEOUT] = $timeoutMs / 1000.0;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
$retryConfig = $this->config['retries'] ?? [];
|
|
116
|
+
$maxRetries = max(0, (int) ($requestDef['retries'] ?? $retryConfig['attempts'] ?? 3));
|
|
117
|
+
$retryUnsafe = (bool) (
|
|
118
|
+
$requestDef['retryUnsafeRequests']
|
|
119
|
+
?? $this->config['retryUnsafeRequests']
|
|
120
|
+
?? false
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return $this->executeWithRetry(
|
|
124
|
+
$method,
|
|
125
|
+
$path,
|
|
126
|
+
$options,
|
|
127
|
+
$maxRetries,
|
|
128
|
+
$retryUnsafe,
|
|
129
|
+
);
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
/** @throws SDKError */
|
|
133
|
+
private function executeWithRetry(
|
|
134
|
+
string $method,
|
|
135
|
+
string $path,
|
|
136
|
+
array $options,
|
|
137
|
+
int $maxRetries,
|
|
138
|
+
bool $retryUnsafe,
|
|
139
|
+
): ResponseInterface {
|
|
117
140
|
$retryConfig = $this->config['retries'] ?? [];
|
|
118
|
-
$
|
|
119
|
-
$
|
|
120
|
-
$
|
|
121
|
-
$
|
|
122
|
-
|
|
123
|
-
$lastException = null;
|
|
141
|
+
$baseBackoffMs = max(0, (int) ($retryConfig['backoffMs'] ?? 400));
|
|
142
|
+
$useJitter = (bool) ($retryConfig['jitter'] ?? true);
|
|
143
|
+
$maxBackoffMs = max($baseBackoffMs, (int) ($retryConfig['maxBackoffMs'] ?? 30000));
|
|
144
|
+
$canRetry = $this->canRetryMethod($method, $options[RequestOptions::HEADERS] ?? [], $retryUnsafe);
|
|
145
|
+
$lastError = null;
|
|
124
146
|
|
|
125
|
-
for ($attempt =
|
|
147
|
+
for ($attempt = 0; $attempt <= $maxRetries; $attempt++) {
|
|
126
148
|
try {
|
|
127
149
|
$request = new Request($method, $path, $options[RequestOptions::HEADERS] ?? []);
|
|
128
150
|
$response = $this->http->send($request, $options);
|
|
129
|
-
|
|
130
|
-
// Retry on server errors (5xx) and 429 (rate limit)
|
|
131
151
|
$status = $response->getStatusCode();
|
|
132
|
-
|
|
152
|
+
|
|
153
|
+
if ($status >= 200 && $status < 300) {
|
|
133
154
|
return $response;
|
|
134
155
|
}
|
|
135
156
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
$
|
|
157
|
+
$lastError = $this->errorFromResponse($response);
|
|
158
|
+
if (!$canRetry || !in_array($status, self::RETRYABLE_STATUSES, true) || $attempt >= $maxRetries) {
|
|
159
|
+
throw $lastError;
|
|
139
160
|
}
|
|
140
|
-
} catch (ConnectException $e) {
|
|
141
|
-
$lastException = $e;
|
|
142
161
|
|
|
143
|
-
|
|
144
|
-
$
|
|
145
|
-
$
|
|
162
|
+
$this->sleep($this->retryDelayMs(
|
|
163
|
+
$response->getHeaderLine('Retry-After'),
|
|
164
|
+
$attempt,
|
|
165
|
+
$baseBackoffMs,
|
|
166
|
+
$useJitter,
|
|
167
|
+
$maxBackoffMs,
|
|
168
|
+
));
|
|
169
|
+
} catch (ConnectException $exception) {
|
|
170
|
+
$lastError = SDKError::fromException($exception);
|
|
171
|
+
if (!$canRetry || $attempt >= $maxRetries) {
|
|
172
|
+
throw $lastError;
|
|
146
173
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
$
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
174
|
+
$this->sleep($this->retryDelayMs(
|
|
175
|
+
null,
|
|
176
|
+
$attempt,
|
|
177
|
+
$baseBackoffMs,
|
|
178
|
+
$useJitter,
|
|
179
|
+
$maxBackoffMs,
|
|
180
|
+
));
|
|
181
|
+
} catch (RequestException $exception) {
|
|
182
|
+
$response = $exception->getResponse();
|
|
183
|
+
$lastError = $response === null
|
|
184
|
+
? SDKError::fromException($exception)
|
|
185
|
+
: $this->errorFromResponse($response, $exception);
|
|
186
|
+
if (
|
|
187
|
+
!$canRetry
|
|
188
|
+
|| $attempt >= $maxRetries
|
|
189
|
+
|| ($response !== null && !in_array($response->getStatusCode(), self::RETRYABLE_STATUSES, true))
|
|
190
|
+
) {
|
|
191
|
+
throw $lastError;
|
|
159
192
|
}
|
|
160
|
-
|
|
193
|
+
$this->sleep($this->retryDelayMs(
|
|
194
|
+
$response?->getHeaderLine('Retry-After'),
|
|
195
|
+
$attempt,
|
|
196
|
+
$baseBackoffMs,
|
|
197
|
+
$useJitter,
|
|
198
|
+
$maxBackoffMs,
|
|
199
|
+
));
|
|
161
200
|
}
|
|
162
201
|
}
|
|
163
202
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
203
|
+
throw $lastError ?? new SDKError('Request failed after retries', 0, 'RETRY_EXHAUSTED');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @param array<string, string> $headers */
|
|
207
|
+
private function canRetryMethod(string $method, array $headers, bool $retryUnsafe): bool
|
|
208
|
+
{
|
|
209
|
+
if ($retryUnsafe || in_array($method, self::RETRYABLE_METHODS, true)) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
$effectiveHeaders = array_merge($this->config['defaultHeaders'] ?? [], $headers);
|
|
213
|
+
foreach ($effectiveHeaders as $name => $value) {
|
|
214
|
+
if (strcasecmp((string) $name, 'X-Idempotency-Key') === 0 && $value !== '') {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
167
217
|
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
168
220
|
|
|
169
|
-
|
|
170
|
-
|
|
221
|
+
private function retryDelayMs(
|
|
222
|
+
?string $retryAfter,
|
|
223
|
+
int $attempt,
|
|
224
|
+
int $baseMs,
|
|
225
|
+
bool $jitter,
|
|
226
|
+
int $maxMs,
|
|
227
|
+
): int {
|
|
228
|
+
$retryAfterMs = $this->parseRetryAfterMs($retryAfter);
|
|
229
|
+
if ($retryAfterMs !== null) {
|
|
230
|
+
return min($retryAfterMs, $maxMs);
|
|
171
231
|
}
|
|
172
232
|
|
|
173
|
-
|
|
233
|
+
$cap = min($baseMs * (2 ** $attempt), $maxMs);
|
|
234
|
+
return $jitter ? random_int(0, max(0, $cap)) : $cap;
|
|
174
235
|
}
|
|
175
236
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
237
|
+
private function parseRetryAfterMs(?string $value): ?int
|
|
238
|
+
{
|
|
239
|
+
if ($value === null || trim($value) === '') {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
if (is_numeric($value)) {
|
|
243
|
+
return max(0, (int) ceil((float) $value * 1000));
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
$time = new DateTimeImmutable($value);
|
|
247
|
+
return max(0, ($time->getTimestamp() - time()) * 1000);
|
|
248
|
+
} catch (Throwable) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @return array<string, string> */
|
|
181
254
|
private function buildAuthHeaders(): array
|
|
182
255
|
{
|
|
183
256
|
$headers = [];
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
{{#if (eq in "header")}}
|
|
188
|
-
// {{name}} — API key in header
|
|
257
|
+
{{#each securitySchemes}}
|
|
258
|
+
{{#if (eq type "apiKey")}}
|
|
259
|
+
{{#if (eq in "header")}}
|
|
189
260
|
if (!empty($this->config['{{configKey}}'])) {
|
|
190
261
|
$headers['{{paramName}}'] = $this->config['{{configKey}}'];
|
|
191
262
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
196
|
-
// {{name}} — Bearer token
|
|
263
|
+
{{/if}}
|
|
264
|
+
{{/if}}
|
|
265
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
197
266
|
if (!empty($this->config['{{configKey}}'])) {
|
|
198
267
|
$headers['Authorization'] = 'Bearer ' . $this->config['{{configKey}}'];
|
|
199
268
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
{{#if (eq type "oauth2")}}
|
|
203
|
-
// {{name}} — OAuth2 access token
|
|
269
|
+
{{/if}}
|
|
270
|
+
{{#if (eq type "oauth2")}}
|
|
204
271
|
if (!empty($this->config['{{configKey}}'])) {
|
|
205
272
|
$headers['Authorization'] = 'Bearer ' . $this->config['{{configKey}}'];
|
|
206
273
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{{#if (eq type "openIdConnect")}}
|
|
210
|
-
// {{name}} — OpenID Connect token
|
|
274
|
+
{{/if}}
|
|
275
|
+
{{#if (eq type "openIdConnect")}}
|
|
211
276
|
if (!empty($this->config['{{configKey}}'])) {
|
|
212
277
|
$headers['Authorization'] = 'Bearer ' . $this->config['{{configKey}}'];
|
|
213
278
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
279
|
+
{{/if}}
|
|
280
|
+
{{/each}}
|
|
217
281
|
return $headers;
|
|
218
282
|
}
|
|
219
283
|
|
|
220
|
-
/**
|
|
221
|
-
* Calculate exponential backoff with optional jitter.
|
|
222
|
-
*/
|
|
223
|
-
private function calculateBackoff(int $attempt, int $baseMs, bool $jitter, int $maxMs): int
|
|
224
|
-
{
|
|
225
|
-
$delay = $baseMs * (2 ** ($attempt - 1));
|
|
226
|
-
|
|
227
|
-
if ($jitter) {
|
|
228
|
-
$delay = (int) ($delay * (0.5 + (mt_rand() / mt_getrandmax()) * 0.5));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return min($delay, $maxMs);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Sleep for a given number of milliseconds.
|
|
236
|
-
*/
|
|
237
284
|
private function sleep(int $milliseconds): void
|
|
238
285
|
{
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Map a Guzzle RequestException to an SDKError.
|
|
244
|
-
*/
|
|
245
|
-
private function mapToSDKError(RequestException $exception): SDKError
|
|
246
|
-
{
|
|
247
|
-
$response = $exception->getResponse();
|
|
248
|
-
|
|
249
|
-
if ($response === null) {
|
|
250
|
-
return SDKError::fromException($exception);
|
|
286
|
+
if ($milliseconds > 0) {
|
|
287
|
+
usleep($milliseconds * 1000);
|
|
251
288
|
}
|
|
289
|
+
}
|
|
252
290
|
|
|
253
|
-
|
|
291
|
+
private function errorFromResponse(
|
|
292
|
+
ResponseInterface $response,
|
|
293
|
+
?Throwable $previous = null,
|
|
294
|
+
): SDKError {
|
|
254
295
|
$body = $this->decodeResponseBody($response);
|
|
255
|
-
|
|
256
|
-
$message = $body['message'] ?? $
|
|
257
|
-
$
|
|
258
|
-
|
|
296
|
+
$nested = is_array($body['error'] ?? null) ? $body['error'] : [];
|
|
297
|
+
$message = $body['message'] ?? $nested['message'] ?? null;
|
|
298
|
+
if ($message === null && is_string($body['error'] ?? null)) {
|
|
299
|
+
$message = $body['error'];
|
|
300
|
+
}
|
|
301
|
+
$message ??= trim((string) ($body['raw'] ?? ''));
|
|
302
|
+
$code = $body['code'] ?? $body['error_code'] ?? $nested['code'] ?? null;
|
|
303
|
+
if ($code === null && is_string($body['error'] ?? null)) {
|
|
304
|
+
$code = $body['error'];
|
|
305
|
+
}
|
|
306
|
+
$requestId = $body['request_id']
|
|
307
|
+
?? $response->getHeaderLine('x-request-id')
|
|
308
|
+
?: $response->getHeaderLine('request-id')
|
|
309
|
+
?: null;
|
|
259
310
|
|
|
260
311
|
return new SDKError(
|
|
261
|
-
message: (string) $message,
|
|
262
|
-
status: $
|
|
263
|
-
code: (string) $code,
|
|
312
|
+
message: (string) ($message ?: 'HTTP ' . $response->getStatusCode()),
|
|
313
|
+
status: $response->getStatusCode(),
|
|
314
|
+
code: (string) ($code ?: 'HTTP_ERROR'),
|
|
264
315
|
requestId: $requestId,
|
|
265
|
-
|
|
316
|
+
responseBody: $body,
|
|
317
|
+
previous: $previous,
|
|
266
318
|
);
|
|
267
319
|
}
|
|
268
320
|
|
|
269
|
-
/**
|
|
270
|
-
* Decode a PSR-7 response body, returning an associative array.
|
|
271
|
-
*
|
|
272
|
-
* @return array<string, mixed>
|
|
273
|
-
*/
|
|
321
|
+
/** @return array<string, mixed> */
|
|
274
322
|
private function decodeResponseBody(ResponseInterface $response): array
|
|
275
323
|
{
|
|
276
324
|
$contents = $response->getBody()->__toString();
|
|
277
|
-
|
|
278
325
|
try {
|
|
279
326
|
$decoded = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
|
|
280
327
|
return is_array($decoded) ? $decoded : ['raw' => $decoded];
|
package/templates/php/types.hbs
CHANGED
|
@@ -1,92 +1,95 @@
|
|
|
1
1
|
<?php
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* @generated
|
|
5
|
-
* This file was automatically generated by Tesseract.
|
|
6
|
-
* DO NOT MODIFY THIS FILE DIRECTLY.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
3
|
declare(strict_types=1);
|
|
10
4
|
|
|
11
5
|
namespace {{namespace}};
|
|
12
6
|
|
|
13
7
|
{{#each schemas}}
|
|
14
|
-
{{#if
|
|
15
|
-
/**
|
|
16
|
-
{{
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
{{#if external}}
|
|
9
|
+
/** External schema placeholder for {{name}}. */
|
|
10
|
+
class {{targetName}}
|
|
11
|
+
{
|
|
12
|
+
/** @param array<string, mixed> $data */
|
|
13
|
+
public function __construct(public array $data = [])
|
|
14
|
+
{
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @param array<string, mixed> $data */
|
|
18
|
+
public static function fromArray(array $data): self
|
|
19
|
+
{
|
|
20
|
+
return new self($data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @return array<string, mixed> */
|
|
24
|
+
public function toArray(): array
|
|
25
|
+
{
|
|
26
|
+
return $this->data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
{{else if isEnum}}
|
|
31
|
+
enum {{targetName}}: {{enumType}}
|
|
23
32
|
{
|
|
24
33
|
{{#each enumValues}}
|
|
25
|
-
case {{
|
|
34
|
+
case {{name}} = {{{literal}}};
|
|
26
35
|
{{/each}}
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
{{else if isTypeAlias}}
|
|
30
|
-
{{!-- PHP type aliases are not direct; they are used as docblock hints --}}
|
|
31
|
-
{{!-- Skipping type alias: {{name}} --}}
|
|
32
|
-
|
|
33
38
|
{{else if isUnionType}}
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
/** Union schema {{name}}. */
|
|
40
|
+
final class {{targetName}}
|
|
41
|
+
{
|
|
42
|
+
public function __construct(public mixed $value)
|
|
43
|
+
{
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
{{else if isIntersectionType}}
|
|
48
|
+
/** Intersection schema {{name}}. */
|
|
49
|
+
class {{targetName}}
|
|
50
|
+
{
|
|
51
|
+
/** @param array<string, mixed> $data */
|
|
52
|
+
public function __construct(public array $data = [])
|
|
53
|
+
{
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
{{else if isTypeAlias}}
|
|
58
|
+
// Type alias {{name}} is represented inline as {{{aliasType}}}.
|
|
36
59
|
|
|
37
60
|
{{else}}
|
|
38
|
-
/**
|
|
39
|
-
{{
|
|
40
|
-
* {{description}}
|
|
41
|
-
*
|
|
42
|
-
{{/if}}
|
|
43
|
-
* @generated
|
|
61
|
+
/**{{#if description}}
|
|
62
|
+
* {{description}}{{/if}}
|
|
44
63
|
*/
|
|
45
|
-
class {{
|
|
64
|
+
class {{targetName}}{{#if targetExtends}} extends {{targetExtends}}{{/if}}
|
|
46
65
|
{
|
|
47
66
|
{{#each properties}}
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
public {{#if nullable}}?{{/if}}{{type}} ${{name}}{{#if defaultValue}} = {{{json defaultValue}}}{{/if}};
|
|
52
|
-
|
|
67
|
+
/** @var {{docType}} */
|
|
68
|
+
public {{targetType}} ${{propertyName}}{{#if hasDefault}} = {{{defaultLiteral}}}{{else if optional}} = null{{/if}};
|
|
53
69
|
{{/each}}
|
|
54
70
|
|
|
55
|
-
/**
|
|
56
|
-
* @param array<string, mixed> $data
|
|
57
|
-
*/
|
|
71
|
+
/** @param array<string, mixed> $data */
|
|
58
72
|
public static function fromArray(array $data): self
|
|
59
73
|
{
|
|
60
74
|
$instance = new self();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{{
|
|
68
|
-
|
|
69
|
-
{{else if (eq type "bool")}}
|
|
70
|
-
$instance->{{name}} = isset($data['{{name}}']) ? (bool) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
71
|
-
{{else if (eq type "array")}}
|
|
72
|
-
$instance->{{name}} = isset($data['{{name}}']) ? (array) $data['{{name}}'] : {{#if required}}[]{{else}}null{{/if}};
|
|
73
|
-
{{else}}
|
|
74
|
-
$instance->{{name}} = $data['{{name}}'] ?? {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
75
|
-
{{/if}}
|
|
76
|
-
|
|
77
|
-
{{/each}}
|
|
75
|
+
{{#each properties}}
|
|
76
|
+
{{#if required}}
|
|
77
|
+
if (!array_key_exists('{{name}}', $data)) {
|
|
78
|
+
throw new \InvalidArgumentException('Missing required field: {{name}}');
|
|
79
|
+
}
|
|
80
|
+
{{/if}}
|
|
81
|
+
$instance->{{propertyName}} = $data['{{name}}'] ?? {{#if hasDefault}}{{{defaultLiteral}}}{{else}}null{{/if}};
|
|
82
|
+
{{/each}}
|
|
78
83
|
return $instance;
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
/**
|
|
82
|
-
* @return array<string, mixed>
|
|
83
|
-
*/
|
|
86
|
+
/** @return array<string, mixed> */
|
|
84
87
|
public function toArray(): array
|
|
85
88
|
{
|
|
86
89
|
return [
|
|
87
|
-
|
|
88
|
-
'{{name}}' => $this->{{
|
|
89
|
-
|
|
90
|
+
{{#each properties}}
|
|
91
|
+
'{{name}}' => $this->{{propertyName}},
|
|
92
|
+
{{/each}}
|
|
90
93
|
];
|
|
91
94
|
}
|
|
92
95
|
}
|
|
@@ -4,7 +4,7 @@ from typing import Optional, Dict, Any, List, Callable, Union
|
|
|
4
4
|
import httpx
|
|
5
5
|
from .transport import create_transport, SDKTransport, RequestOptions
|
|
6
6
|
{{#each groups}}
|
|
7
|
-
from .resources.{{
|
|
7
|
+
from .resources.{{fileModule}} import {{interfaceName}}
|
|
8
8
|
{{/each}}
|
|
9
9
|
|
|
10
10
|
{{#if hasSSE}}
|
|
@@ -34,6 +34,7 @@ class {{clientName}}ClientConfig:
|
|
|
34
34
|
{{/each}}
|
|
35
35
|
timeout_ms: int = 15000,
|
|
36
36
|
retries: int = 3,
|
|
37
|
+
retry_unsafe_requests: bool = False,
|
|
37
38
|
default_headers: Optional[Dict[str, str]] = None,
|
|
38
39
|
on_request: Optional[Callable] = None,
|
|
39
40
|
on_response: Optional[Callable] = None,
|
|
@@ -45,6 +46,7 @@ class {{clientName}}ClientConfig:
|
|
|
45
46
|
{{/each}}
|
|
46
47
|
self.timeout_ms = timeout_ms
|
|
47
48
|
self.retries = retries
|
|
49
|
+
self.retry_unsafe_requests = retry_unsafe_requests
|
|
48
50
|
self.default_headers = default_headers or {}
|
|
49
51
|
self.on_request = on_request
|
|
50
52
|
self.on_response = on_response
|
|
@@ -63,15 +65,26 @@ class {{clientName}}Client:
|
|
|
63
65
|
{{/each}}
|
|
64
66
|
timeout_ms=self.config.timeout_ms,
|
|
65
67
|
retries=self.config.retries,
|
|
68
|
+
retry_unsafe_requests=self.config.retry_unsafe_requests,
|
|
66
69
|
default_headers=self.config.default_headers,
|
|
67
70
|
on_request=self.config.on_request,
|
|
68
71
|
on_response=self.config.on_response,
|
|
69
72
|
on_error=self.config.on_error,
|
|
70
73
|
)
|
|
71
74
|
{{#each groups}}
|
|
72
|
-
self.{{
|
|
75
|
+
self.{{accessorName}} = {{interfaceName}}(self._transport)
|
|
73
76
|
{{/each}}
|
|
74
77
|
|
|
78
|
+
def close(self) -> None:
|
|
79
|
+
"""Close the underlying HTTP connection pool."""
|
|
80
|
+
self._transport.close()
|
|
81
|
+
|
|
82
|
+
def __enter__(self) -> "{{clientName}}Client":
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def __exit__(self, *_: Any) -> None:
|
|
86
|
+
self.close()
|
|
87
|
+
|
|
75
88
|
|
|
76
89
|
def create_{{snakeCase clientName}}_client(
|
|
77
90
|
base_url: str = "{{baseUrl}}",
|