@north-light/api 0.3.436 → 0.3.438
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/io.d.ts +8 -1
- package/dist/core/io.js +62 -12
- package/dist/core/render.js +2 -0
- package/package.json +1 -1
package/dist/core/io.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ interface ErrorPayloadBase {
|
|
|
57
57
|
* of `received` on purpose: `received` names the caller's offending VALUE, and
|
|
58
58
|
* reporting an HTTP status there is a false claim about what was sent. */
|
|
59
59
|
http_status?: number;
|
|
60
|
+
/** Seconds the provider asked the caller to wait before retrying (a rate limit). */
|
|
61
|
+
retry_after_s?: number;
|
|
60
62
|
next: string;
|
|
61
63
|
}
|
|
62
64
|
export type ErrorPayload = ErrorPayloadBase & ({
|
|
@@ -109,7 +111,12 @@ export declare function diag(message: string): void;
|
|
|
109
111
|
* reconstruct it verbatim so a gate/guard error (revive reopen-gate, push
|
|
110
112
|
* worktree/finalization guard, …) surfaces byte-for-byte as the leaf raised
|
|
111
113
|
* it before the re-plumb, preserving its SPECIFIC code + next.
|
|
112
|
-
* 3.
|
|
114
|
+
* 3. A provider-origin refusal (`err.origin === 'provider'`) carries the
|
|
115
|
+
* provider's own error in `details` (`{code,message,field?,next?,received?}`)
|
|
116
|
+
* under the daemon's coarse `provider_*` code and the provider's HTTP
|
|
117
|
+
* status. The provider's code, `next`, `field` and `Retry-After` reach the
|
|
118
|
+
* caller unchanged; the status→code map would flatten them to `conflict`.
|
|
119
|
+
* 4. Otherwise map status→code (`STATUS_TO_CLI_CODE`) with the server message
|
|
113
120
|
* and a caller-supplied-or-default `next`. */
|
|
114
121
|
export declare function apiErrorToCliError(err: ApiError, next?: string): CrtrError;
|
|
115
122
|
/** Terminal error handler. Command-level failures (bad input, not-found,
|
package/dist/core/io.js
CHANGED
|
@@ -209,6 +209,7 @@ function payloadOf(e) {
|
|
|
209
209
|
received: d.received,
|
|
210
210
|
field: d.field,
|
|
211
211
|
...(typeof d.http_status === 'number' ? { http_status: d.http_status } : {}),
|
|
212
|
+
...(typeof d.retry_after_s === 'number' ? { retry_after_s: d.retry_after_s } : {}),
|
|
212
213
|
};
|
|
213
214
|
return result === undefined
|
|
214
215
|
? { ...base, next }
|
|
@@ -274,7 +275,12 @@ const STATUS_TO_CLI_CODE = {
|
|
|
274
275
|
* reconstruct it verbatim so a gate/guard error (revive reopen-gate, push
|
|
275
276
|
* worktree/finalization guard, …) surfaces byte-for-byte as the leaf raised
|
|
276
277
|
* it before the re-plumb, preserving its SPECIFIC code + next.
|
|
277
|
-
* 3.
|
|
278
|
+
* 3. A provider-origin refusal (`err.origin === 'provider'`) carries the
|
|
279
|
+
* provider's own error in `details` (`{code,message,field?,next?,received?}`)
|
|
280
|
+
* under the daemon's coarse `provider_*` code and the provider's HTTP
|
|
281
|
+
* status. The provider's code, `next`, `field` and `Retry-After` reach the
|
|
282
|
+
* caller unchanged; the status→code map would flatten them to `conflict`.
|
|
283
|
+
* 4. Otherwise map status→code (`STATUS_TO_CLI_CODE`) with the server message
|
|
278
284
|
* and a caller-supplied-or-default `next`. */
|
|
279
285
|
export function apiErrorToCliError(err, next) {
|
|
280
286
|
// A handover is NOT a down daemon: the successor is already up (the client
|
|
@@ -297,12 +303,23 @@ export function apiErrorToCliError(err, next) {
|
|
|
297
303
|
}
|
|
298
304
|
// A responding daemon can relay an unavailable provider or storage service.
|
|
299
305
|
// HTTP 503 alone does not mean that crtrd is down.
|
|
306
|
+
const d = err.details;
|
|
307
|
+
const provider = providerSupplied(err, d);
|
|
300
308
|
if (err.status === 503) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
const code = provider.code ?? err.code;
|
|
310
|
+
const details = {
|
|
311
|
+
next: provider.next ?? next ?? 'Retry the command; if it persists, inspect the unavailable service named in the error.',
|
|
312
|
+
};
|
|
313
|
+
if (provider.code !== undefined) {
|
|
314
|
+
if (provider.field !== undefined)
|
|
315
|
+
details['field'] = provider.field;
|
|
316
|
+
if (provider.received !== undefined)
|
|
317
|
+
details['received'] = provider.received;
|
|
318
|
+
if (provider.retryAfterS !== undefined)
|
|
319
|
+
details['retry_after_s'] = provider.retryAfterS;
|
|
320
|
+
}
|
|
321
|
+
return new CrtrError(code, err.message, ExitCode.NETWORK, details);
|
|
304
322
|
}
|
|
305
|
-
const d = err.details;
|
|
306
323
|
// A `/v1` request validator reports each bad field in `details.violations`;
|
|
307
324
|
// the message alone only says the request was invalid.
|
|
308
325
|
const violations = Array.isArray(d?.violations)
|
|
@@ -312,14 +329,20 @@ export function apiErrorToCliError(err, next) {
|
|
|
312
329
|
const payload = d && typeof d.error === 'string' && typeof d.message === 'string' && typeof d.next === 'string'
|
|
313
330
|
? d
|
|
314
331
|
: {
|
|
315
|
-
error: STATUS_TO_CLI_CODE[err.status] ?? err.code,
|
|
332
|
+
error: provider.code ?? STATUS_TO_CLI_CODE[err.status] ?? err.code,
|
|
333
|
+
...(provider.field !== undefined ? { field: provider.field } : {}),
|
|
334
|
+
...(provider.received !== undefined ? { received: provider.received } : {}),
|
|
335
|
+
...(provider.retryAfterS !== undefined ? { retry_after_s: provider.retryAfterS } : {}),
|
|
316
336
|
message: violations.length > 0 ? `${err.message}: ${violations.join('; ')}` : err.message.trim() !== '' ? err.message : `request refused (HTTP ${err.status}, ${err.code}) with no message`,
|
|
317
|
-
next: next ??
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
337
|
+
next: provider.next ??
|
|
338
|
+
next ??
|
|
339
|
+
(provider.retryAfterS !== undefined
|
|
340
|
+
? `Wait ${provider.retryAfterS}s, then retry; do not retry sooner.`
|
|
341
|
+
: err.status >= 500
|
|
342
|
+
? 'This is a crtr bug, not a bad call. Retry; if it persists, report it (the daemon log holds the full cause).'
|
|
343
|
+
: err.status === 404
|
|
344
|
+
? 'List nodes with `crtr node inspect list`.'
|
|
345
|
+
: 'Inspect the error and adjust the call. See -h for the schema.'),
|
|
323
346
|
};
|
|
324
347
|
// Preserve the exit-code class the server leaf carried instead of collapsing
|
|
325
348
|
// every non-503 to USAGE(2): a not-found (404 / `not_found`) surfaces exit 3
|
|
@@ -327,6 +350,33 @@ export function apiErrorToCliError(err, next) {
|
|
|
327
350
|
// (memory/pkg/sys) still emit so the taxonomy stays uniform across the CLI.
|
|
328
351
|
return new InputError(payload, exitForCliCode(payload.error, err.status));
|
|
329
352
|
}
|
|
353
|
+
/** What a provider put in its own error body, as the daemon relays it: the
|
|
354
|
+
* provider's code (`details.code`; the envelope `code` is only the daemon's
|
|
355
|
+
* coarse `provider_*` bucket), plus `next`, `field`, `received`, and the
|
|
356
|
+
* `Retry-After` the daemon lifts into the envelope. Empty for any error that
|
|
357
|
+
* did not originate at a provider, so non-provider status→code mapping is
|
|
358
|
+
* untouched. A code that is not a snake_case identifier, or that came with no
|
|
359
|
+
* provider HTTP status, is ignored. */
|
|
360
|
+
function providerSupplied(err, d) {
|
|
361
|
+
if (err.origin !== 'provider' || d === undefined || d === null || typeof d !== 'object')
|
|
362
|
+
return {};
|
|
363
|
+
const out = {};
|
|
364
|
+
// `details.status` is the provider's own HTTP status; the runtime's transport
|
|
365
|
+
// diagnostics (`{status: null, code: 'timeout'}`) are not a provider's code.
|
|
366
|
+
if (typeof d.status === 'number' && typeof d.code === 'string' && /^[a-z][a-z0-9_]*$/.test(d.code))
|
|
367
|
+
out.code = d.code;
|
|
368
|
+
if (typeof d.next === 'string' && d.next.trim() !== '')
|
|
369
|
+
out.next = d.next;
|
|
370
|
+
if (typeof d.field === 'string' && d.field !== '')
|
|
371
|
+
out.field = d.field;
|
|
372
|
+
else if (err.param !== undefined)
|
|
373
|
+
out.field = err.param;
|
|
374
|
+
if (d.received !== undefined && d.received !== null)
|
|
375
|
+
out.received = d.received;
|
|
376
|
+
if (typeof err.retryAfterS === 'number')
|
|
377
|
+
out.retryAfterS = err.retryAfterS;
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
330
380
|
/** The `ExitCode` class a reconstructed API error should carry, keyed off the
|
|
331
381
|
* round-tripped CLI code (and the 404 status as the not-found signal — the
|
|
332
382
|
* server maps NOT_FOUND's exit class to status 404, but folds AMBIGUOUS and
|
package/dist/core/render.js
CHANGED
|
@@ -129,6 +129,8 @@ export function renderError(p) {
|
|
|
129
129
|
// the value the caller supplied.
|
|
130
130
|
if (p.http_status !== undefined)
|
|
131
131
|
lines.push(`http status: ${p.http_status}`);
|
|
132
|
+
if (p.retry_after_s !== undefined)
|
|
133
|
+
lines.push(`retry after: ${p.retry_after_s}s`);
|
|
132
134
|
if ('primaryCompleted' in p && p.primaryCompleted === true) {
|
|
133
135
|
lines.push('primaryCompleted: true');
|
|
134
136
|
lines.push(`Primary result (the command already completed):\n${fencedJson(p.result)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/api",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.438",
|
|
4
4
|
"description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/api/index.js",
|