@oxchannels/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/QUICKSTART.md +604 -0
- package/README.md +193 -0
- package/contracts/openapi-agent-bootstrap-contract-v2.json +1266 -0
- package/contracts/openapi-dashboard-contract-v2.json +98996 -0
- package/contracts/openapi-partner-contract-v2.json +14808 -0
- package/contracts/openapi-public-contract-v2.json +13489 -0
- package/dist/client.d.ts +83 -0
- package/dist/client.js +661 -0
- package/dist/generated/agent-bootstrap-contract-v2-schema.d.ts +398 -0
- package/dist/generated/agent-bootstrap-contract-v2-schema.js +1 -0
- package/dist/generated/client.d.ts +1 -0
- package/dist/generated/client.js +2 -0
- package/dist/generated/contracts/api-errors.d.ts +83 -0
- package/dist/generated/contracts/api-errors.js +296 -0
- package/dist/generated/contracts/json-data.d.ts +16 -0
- package/dist/generated/contracts/json-data.js +308 -0
- package/dist/generated/dashboard-contract-v2-schema.d.ts +45429 -0
- package/dist/generated/dashboard-contract-v2-schema.js +1 -0
- package/dist/generated/partner-contract-v2-schema.d.ts +6458 -0
- package/dist/generated/partner-contract-v2-schema.js +1 -0
- package/dist/generated/public-contract-v2-schema.d.ts +4112 -0
- package/dist/generated/public-contract-v2-schema.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -0
- package/generated/provenance.json +87 -0
- package/package.json +55 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// Generated by packages/openapi-client/scripts/generate.mjs from packages/contracts/src/api-errors.ts; do not edit.
|
|
2
|
+
import { assertExactOwnFields, deepFreezeJsonData, denseArrayValues, jsonSchemaToOpenApiFragment, ownDataRecord, ownDataValue, snapshotOwnJsonDataTree, } from './json-data.js';
|
|
3
|
+
export const PARITY_CONTRACT_SET_VERSION = '2.0.0';
|
|
4
|
+
/**
|
|
5
|
+
* The response header that names who imposed a delayed retry.
|
|
6
|
+
*
|
|
7
|
+
* It is a header rather than an envelope field on purpose. Contract-set 2.0.0 error envelopes are
|
|
8
|
+
* closed schemas, and this repository's compatibility rule treats *any* added output property —
|
|
9
|
+
* required or optional — as breaking, because the generated clients parse closed-world and would
|
|
10
|
+
* reject a wider body. A header carries the same machine-readable fact additively, so a client
|
|
11
|
+
* pinned to 1.13.52 keeps working byte-for-byte while a client that wants the distinction can read
|
|
12
|
+
* it today. Moving it into the envelope belongs to the next contract major.
|
|
13
|
+
*
|
|
14
|
+
* Why the distinction matters at all: before Task 18.2 an OxChannels policy limit and a provider
|
|
15
|
+
* throttle both arrived as a bare `RATE_LIMITED`, and the backend answered every non-public 429
|
|
16
|
+
* with a hardcoded sixty seconds. Retrying at an interval we invented, inside a window somebody
|
|
17
|
+
* else set, is how a connected account gets suspended.
|
|
18
|
+
*
|
|
19
|
+
* The operator-facing surfaces publish the same distinction in full: `GET /privacy/limits` returns
|
|
20
|
+
* our thresholds and the provider's observed limits as two separate lists, and
|
|
21
|
+
* `GET /privacy/limit-decisions` records every judgement with its authority.
|
|
22
|
+
*/
|
|
23
|
+
export const LIMIT_AUTHORITY_HEADER = 'oxchannels-limit-authority';
|
|
24
|
+
export const LIMIT_AUTHORITY_HEADER_VALUES = Object.freeze(['OXCHANNELS', 'PROVIDER']);
|
|
25
|
+
export const ACTIVE_API_ERROR_CODES = Object.freeze([
|
|
26
|
+
'INVALID_REQUEST',
|
|
27
|
+
'AUTHENTICATION_REQUIRED',
|
|
28
|
+
'FORBIDDEN',
|
|
29
|
+
'NOT_FOUND',
|
|
30
|
+
'IDEMPOTENCY_CONFLICT',
|
|
31
|
+
'RATE_LIMITED',
|
|
32
|
+
'INTERNAL_SAFE_FAILURE',
|
|
33
|
+
]);
|
|
34
|
+
export const ACTIVE_API_ERROR_CODES_V2 = Object.freeze([
|
|
35
|
+
...ACTIVE_API_ERROR_CODES,
|
|
36
|
+
'CONFLICT',
|
|
37
|
+
]);
|
|
38
|
+
export const COLLABORATION_CONFLICT_CODES = Object.freeze([
|
|
39
|
+
'DUPLICATE_INVITATION',
|
|
40
|
+
'LAST_OWNER',
|
|
41
|
+
'ROLE_CEILING',
|
|
42
|
+
'SELF_MUTATION_REQUIRES_TRANSFER',
|
|
43
|
+
'STALE_ACCESS_GENERATION',
|
|
44
|
+
'STALE_ACCOUNT_GROUP',
|
|
45
|
+
'STALE_INVITATION',
|
|
46
|
+
'STALE_MEMBERSHIP',
|
|
47
|
+
]);
|
|
48
|
+
export const RESERVED_API_ERROR_CODES = Object.freeze([
|
|
49
|
+
'SCHEMA_UNSUPPORTED',
|
|
50
|
+
'STALE_CAPABILITY',
|
|
51
|
+
'UNSUPPORTED_ACTION',
|
|
52
|
+
'CONNECTION_REQUIRED',
|
|
53
|
+
'PROVIDER_REAUTH_REQUIRED',
|
|
54
|
+
'PROVIDER_REVIEW_REQUIRED',
|
|
55
|
+
'PROVIDER_PERMISSION_DENIED',
|
|
56
|
+
'PROVIDER_REJECTED',
|
|
57
|
+
'DEPENDENCY_UNAVAILABLE',
|
|
58
|
+
'UNKNOWN_EFFECT',
|
|
59
|
+
]);
|
|
60
|
+
function entry(code, status, category, defaultHttpStatus, retry, effect) {
|
|
61
|
+
return { code, status, category, defaultHttpStatus, retry, effect };
|
|
62
|
+
}
|
|
63
|
+
const API_ERROR_TAXONOMY_ENTRIES = [
|
|
64
|
+
entry('INVALID_REQUEST', 'ACTIVE', 'VALIDATION', 400, 'NONE', 'NOT_APPLIED'),
|
|
65
|
+
entry('AUTHENTICATION_REQUIRED', 'ACTIVE', 'AUTHENTICATION', 401, 'REFRESH_AUTH', 'NOT_APPLIED'),
|
|
66
|
+
entry('FORBIDDEN', 'ACTIVE', 'AUTHORIZATION', 403, 'NONE', 'NOT_APPLIED'),
|
|
67
|
+
entry('NOT_FOUND', 'ACTIVE', 'RESOURCE', 404, 'NONE', 'NOT_APPLIED'),
|
|
68
|
+
entry('IDEMPOTENCY_CONFLICT', 'ACTIVE', 'CONFLICT', 409, 'NONE', 'NO_EFFECT_CLAIM'),
|
|
69
|
+
entry('RATE_LIMITED', 'ACTIVE', 'RATE_LIMIT', 429, 'AFTER_DELAY', 'NO_EFFECT_CLAIM'),
|
|
70
|
+
entry('INTERNAL_SAFE_FAILURE', 'ACTIVE', 'INTERNAL', 500, 'NONE', 'NO_EFFECT_CLAIM'),
|
|
71
|
+
entry('SCHEMA_UNSUPPORTED', 'RESERVED', 'VALIDATION', 400, 'NONE', 'NOT_APPLIED'),
|
|
72
|
+
entry('CONFLICT', 'ACTIVE', 'CONFLICT', 409, 'NONE', 'NO_EFFECT_CLAIM'),
|
|
73
|
+
entry('STALE_CAPABILITY', 'RESERVED', 'CAPABILITY', 409, 'NONE', 'NOT_APPLIED'),
|
|
74
|
+
entry('UNSUPPORTED_ACTION', 'RESERVED', 'CAPABILITY', 422, 'NONE', 'NOT_APPLIED'),
|
|
75
|
+
entry('CONNECTION_REQUIRED', 'RESERVED', 'AUTHENTICATION', 409, 'NONE', 'NOT_APPLIED'),
|
|
76
|
+
entry('PROVIDER_REAUTH_REQUIRED', 'RESERVED', 'AUTHENTICATION', 409, 'REFRESH_AUTH', 'NOT_APPLIED'),
|
|
77
|
+
entry('PROVIDER_REVIEW_REQUIRED', 'RESERVED', 'PROVIDER', 409, 'NONE', 'NOT_APPLIED'),
|
|
78
|
+
entry('PROVIDER_PERMISSION_DENIED', 'RESERVED', 'PROVIDER', 403, 'NONE', 'NOT_APPLIED'),
|
|
79
|
+
entry('PROVIDER_REJECTED', 'RESERVED', 'PROVIDER', 422, 'NONE', 'NOT_APPLIED'),
|
|
80
|
+
entry('DEPENDENCY_UNAVAILABLE', 'RESERVED', 'DEPENDENCY', 503, 'AFTER_DELAY', 'NO_EFFECT_CLAIM'),
|
|
81
|
+
entry('UNKNOWN_EFFECT', 'RESERVED', 'PROVIDER', 409, 'MANUAL_REVIEW', 'UNKNOWN'),
|
|
82
|
+
];
|
|
83
|
+
export const API_ERROR_TAXONOMY = deepFreezeJsonData(Object.fromEntries(API_ERROR_TAXONOMY_ENTRIES.map((definition) => [definition.code, definition])));
|
|
84
|
+
function exactObjectSchema(properties, required) {
|
|
85
|
+
return { type: 'object', additionalProperties: false, properties, required };
|
|
86
|
+
}
|
|
87
|
+
function errorViolationSchema() {
|
|
88
|
+
return exactObjectSchema({ path: { type: 'string' }, code: { type: 'string' } }, [
|
|
89
|
+
'path',
|
|
90
|
+
'code',
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
function taxonomyVariant(definition) {
|
|
94
|
+
return exactObjectSchema({
|
|
95
|
+
code: { const: definition.code },
|
|
96
|
+
status: { const: definition.status },
|
|
97
|
+
category: { const: definition.category },
|
|
98
|
+
defaultHttpStatus: { const: definition.defaultHttpStatus },
|
|
99
|
+
retry: { const: definition.retry },
|
|
100
|
+
effect: { const: definition.effect },
|
|
101
|
+
}, ['code', 'status', 'category', 'defaultHttpStatus', 'retry', 'effect']);
|
|
102
|
+
}
|
|
103
|
+
const ERROR_ENVELOPE_V1_PROPERTIES = {
|
|
104
|
+
version: { type: 'string', enum: ['v1'] },
|
|
105
|
+
code: { type: 'string', enum: ACTIVE_API_ERROR_CODES },
|
|
106
|
+
message: { type: 'string' },
|
|
107
|
+
traceId: { type: 'string' },
|
|
108
|
+
retryable: { type: 'boolean' },
|
|
109
|
+
};
|
|
110
|
+
export const API_ERROR_TAXONOMY_ENTRY_JSON_SCHEMA = deepFreezeJsonData({
|
|
111
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
112
|
+
$id: 'https://schemas.oxchannels.dev/contracts/v2/api-error-taxonomy-entry.schema.json',
|
|
113
|
+
title: 'OxChannels API error taxonomy entry',
|
|
114
|
+
oneOf: API_ERROR_TAXONOMY_ENTRIES.map(taxonomyVariant),
|
|
115
|
+
});
|
|
116
|
+
export const ERROR_ENVELOPE_V1_JSON_SCHEMA = deepFreezeJsonData({
|
|
117
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
118
|
+
$id: 'https://schemas.oxchannels.dev/contracts/v2/error-envelope-v1.schema.json',
|
|
119
|
+
title: 'OxChannels ErrorEnvelope v1',
|
|
120
|
+
...exactObjectSchema(ERROR_ENVELOPE_V1_PROPERTIES, [
|
|
121
|
+
'version',
|
|
122
|
+
'code',
|
|
123
|
+
'message',
|
|
124
|
+
'traceId',
|
|
125
|
+
'retryable',
|
|
126
|
+
]),
|
|
127
|
+
});
|
|
128
|
+
function errorEnvelopeV2Variant(code) {
|
|
129
|
+
const definition = API_ERROR_TAXONOMY[code];
|
|
130
|
+
const expectedEffectEvidence = effectEvidence(definition.effect);
|
|
131
|
+
const retryProperties = definition.retry === 'AFTER_DELAY'
|
|
132
|
+
? { retryAfterSeconds: { type: 'integer', minimum: 0, maximum: 86_400 } }
|
|
133
|
+
: {};
|
|
134
|
+
return exactObjectSchema({
|
|
135
|
+
schemaVersion: { const: PARITY_CONTRACT_SET_VERSION },
|
|
136
|
+
code: { const: code },
|
|
137
|
+
category: { const: definition.category },
|
|
138
|
+
message: { type: 'string' },
|
|
139
|
+
traceId: { type: 'string' },
|
|
140
|
+
correlationId: { type: 'string' },
|
|
141
|
+
retryable: { const: defaultRetryable(definition.retry) },
|
|
142
|
+
effectEvidence: { const: expectedEffectEvidence },
|
|
143
|
+
...retryProperties,
|
|
144
|
+
violations: {
|
|
145
|
+
type: 'array',
|
|
146
|
+
maxItems: 100,
|
|
147
|
+
items: errorViolationSchema(),
|
|
148
|
+
},
|
|
149
|
+
}, [
|
|
150
|
+
'schemaVersion',
|
|
151
|
+
'code',
|
|
152
|
+
'category',
|
|
153
|
+
'message',
|
|
154
|
+
'traceId',
|
|
155
|
+
'correlationId',
|
|
156
|
+
'retryable',
|
|
157
|
+
'effectEvidence',
|
|
158
|
+
...(definition.retry === 'AFTER_DELAY' ? ['retryAfterSeconds'] : []),
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
export const ERROR_ENVELOPE_V2_JSON_SCHEMA = deepFreezeJsonData({
|
|
162
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
163
|
+
$id: 'https://schemas.oxchannels.dev/contracts/v2/error-envelope-v2.schema.json',
|
|
164
|
+
title: 'OxChannels ErrorEnvelope v2',
|
|
165
|
+
discriminator: { propertyName: 'code' },
|
|
166
|
+
oneOf: ACTIVE_API_ERROR_CODES_V2.map(errorEnvelopeV2Variant),
|
|
167
|
+
});
|
|
168
|
+
export const API_ERROR_TAXONOMY_ENTRY_OPENAPI_SCHEMA = jsonSchemaToOpenApiFragment(API_ERROR_TAXONOMY_ENTRY_JSON_SCHEMA);
|
|
169
|
+
export const ERROR_ENVELOPE_V1_OPENAPI_SCHEMA = jsonSchemaToOpenApiFragment(ERROR_ENVELOPE_V1_JSON_SCHEMA);
|
|
170
|
+
export const ERROR_ENVELOPE_V2_OPENAPI_SCHEMA = jsonSchemaToOpenApiFragment(ERROR_ENVELOPE_V2_JSON_SCHEMA);
|
|
171
|
+
export const ERROR_ENVELOPE_V2_VARIANT_OPENAPI_SCHEMAS = deepFreezeJsonData(Object.fromEntries(ACTIVE_API_ERROR_CODES_V2.map((code) => [
|
|
172
|
+
code,
|
|
173
|
+
jsonSchemaToOpenApiFragment(errorEnvelopeV2Variant(code)),
|
|
174
|
+
])));
|
|
175
|
+
function activeV1Code(value) {
|
|
176
|
+
if (typeof value !== 'string' || !ACTIVE_API_ERROR_CODES.includes(value)) {
|
|
177
|
+
throw new TypeError('Error envelope code is not active in contract set 2.0.0');
|
|
178
|
+
}
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
function activeV2Code(value) {
|
|
182
|
+
if (typeof value !== 'string' ||
|
|
183
|
+
!ACTIVE_API_ERROR_CODES_V2.includes(value)) {
|
|
184
|
+
throw new TypeError('Error envelope code is not active in contract set 2.0.0');
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
function requiredString(value, label) {
|
|
189
|
+
if (typeof value !== 'string')
|
|
190
|
+
throw new TypeError(`${label} must be a string`);
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
export function isCollaborationConflictCode(value) {
|
|
194
|
+
return (typeof value === 'string' &&
|
|
195
|
+
COLLABORATION_CONFLICT_CODES.includes(value));
|
|
196
|
+
}
|
|
197
|
+
function defaultRetryable(retry) {
|
|
198
|
+
return retry === 'AFTER_DELAY' || retry === 'REFRESH_AUTH' || retry === 'POLL';
|
|
199
|
+
}
|
|
200
|
+
function effectEvidence(effect) {
|
|
201
|
+
return effect === 'NO_EFFECT_CLAIM' ? null : effect;
|
|
202
|
+
}
|
|
203
|
+
export function parseApiErrorEnvelopeV1(value) {
|
|
204
|
+
const snapshot = snapshotOwnJsonDataTree(value, 'ErrorEnvelope v1');
|
|
205
|
+
const envelope = ownDataRecord(snapshot, 'ErrorEnvelope v1');
|
|
206
|
+
assertExactOwnFields(envelope, ['version', 'code', 'message', 'traceId', 'retryable'], 'ErrorEnvelope v1');
|
|
207
|
+
if (ownDataValue(envelope, 'version', 'ErrorEnvelope v1') !== 'v1') {
|
|
208
|
+
throw new TypeError('ErrorEnvelope v1 has an unsupported version');
|
|
209
|
+
}
|
|
210
|
+
const retryable = ownDataValue(envelope, 'retryable', 'ErrorEnvelope v1');
|
|
211
|
+
if (typeof retryable !== 'boolean')
|
|
212
|
+
throw new TypeError('ErrorEnvelope v1 retryable is invalid');
|
|
213
|
+
return Object.freeze({
|
|
214
|
+
version: 'v1',
|
|
215
|
+
code: activeV1Code(ownDataValue(envelope, 'code', 'ErrorEnvelope v1')),
|
|
216
|
+
message: requiredString(ownDataValue(envelope, 'message', 'ErrorEnvelope v1'), 'message'),
|
|
217
|
+
traceId: requiredString(ownDataValue(envelope, 'traceId', 'ErrorEnvelope v1'), 'traceId'),
|
|
218
|
+
retryable,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
export function parseApiErrorEnvelopeV2(value) {
|
|
222
|
+
const snapshot = snapshotOwnJsonDataTree(value, 'ErrorEnvelope v2');
|
|
223
|
+
const envelope = ownDataRecord(snapshot, 'ErrorEnvelope v2');
|
|
224
|
+
const required = [
|
|
225
|
+
'schemaVersion',
|
|
226
|
+
'code',
|
|
227
|
+
'category',
|
|
228
|
+
'message',
|
|
229
|
+
'traceId',
|
|
230
|
+
'correlationId',
|
|
231
|
+
'retryable',
|
|
232
|
+
'effectEvidence',
|
|
233
|
+
];
|
|
234
|
+
const allowed = [...required, 'retryAfterSeconds', 'violations'];
|
|
235
|
+
const fields = Object.getOwnPropertyNames(envelope);
|
|
236
|
+
if (fields.some((field) => !allowed.includes(field)) ||
|
|
237
|
+
required.some((field) => !fields.includes(field))) {
|
|
238
|
+
throw new TypeError(`ErrorEnvelope v2 must contain only: ${allowed.join(', ')}`);
|
|
239
|
+
}
|
|
240
|
+
if (ownDataValue(envelope, 'schemaVersion', 'ErrorEnvelope v2') !== PARITY_CONTRACT_SET_VERSION) {
|
|
241
|
+
throw new TypeError('ErrorEnvelope v2 has an unsupported version');
|
|
242
|
+
}
|
|
243
|
+
const code = activeV2Code(ownDataValue(envelope, 'code', 'ErrorEnvelope v2'));
|
|
244
|
+
const definition = API_ERROR_TAXONOMY[code];
|
|
245
|
+
const retryable = ownDataValue(envelope, 'retryable', 'ErrorEnvelope v2');
|
|
246
|
+
if (retryable !== defaultRetryable(definition.retry)) {
|
|
247
|
+
throw new TypeError('ErrorEnvelope v2 retryability differs from the active taxonomy');
|
|
248
|
+
}
|
|
249
|
+
if (ownDataValue(envelope, 'category', 'ErrorEnvelope v2') !== definition.category) {
|
|
250
|
+
throw new TypeError('ErrorEnvelope v2 category differs from the active taxonomy');
|
|
251
|
+
}
|
|
252
|
+
if (ownDataValue(envelope, 'effectEvidence', 'ErrorEnvelope v2') !==
|
|
253
|
+
effectEvidence(definition.effect)) {
|
|
254
|
+
throw new TypeError('ErrorEnvelope v2 effectEvidence differs from the active taxonomy');
|
|
255
|
+
}
|
|
256
|
+
const retryAfterSeconds = Object.hasOwn(envelope, 'retryAfterSeconds')
|
|
257
|
+
? ownDataValue(envelope, 'retryAfterSeconds', 'ErrorEnvelope v2')
|
|
258
|
+
: undefined;
|
|
259
|
+
if ((definition.retry === 'AFTER_DELAY') !== (retryAfterSeconds !== undefined) ||
|
|
260
|
+
(retryAfterSeconds !== undefined &&
|
|
261
|
+
(!Number.isSafeInteger(retryAfterSeconds) ||
|
|
262
|
+
Number(retryAfterSeconds) < 0 ||
|
|
263
|
+
Number(retryAfterSeconds) > 86_400 ||
|
|
264
|
+
definition.retry !== 'AFTER_DELAY'))) {
|
|
265
|
+
throw new TypeError('ErrorEnvelope v2 retryAfterSeconds is invalid for the active taxonomy');
|
|
266
|
+
}
|
|
267
|
+
const violations = Object.hasOwn(envelope, 'violations')
|
|
268
|
+
? denseArrayValues(ownDataValue(envelope, 'violations', 'ErrorEnvelope v2'), 'violations')
|
|
269
|
+
: undefined;
|
|
270
|
+
if (violations !== undefined && violations.length > 100) {
|
|
271
|
+
throw new TypeError('ErrorEnvelope v2 has too many violations');
|
|
272
|
+
}
|
|
273
|
+
const parsedViolations = violations?.map((candidate, index) => {
|
|
274
|
+
const violation = ownDataRecord(candidate, `violation ${index}`);
|
|
275
|
+
assertExactOwnFields(violation, ['path', 'code'], `violation ${index}`);
|
|
276
|
+
const path = requiredString(ownDataValue(violation, 'path', `violation ${index}`), 'violation.path');
|
|
277
|
+
const violationCode = requiredString(ownDataValue(violation, 'code', `violation ${index}`), 'violation.code');
|
|
278
|
+
if (path === 'collaboration' &&
|
|
279
|
+
(code !== 'CONFLICT' || !isCollaborationConflictCode(violationCode))) {
|
|
280
|
+
throw new TypeError('ErrorEnvelope v2 collaboration violation is not active');
|
|
281
|
+
}
|
|
282
|
+
return { path, code: violationCode };
|
|
283
|
+
});
|
|
284
|
+
return deepFreezeJsonData({
|
|
285
|
+
schemaVersion: PARITY_CONTRACT_SET_VERSION,
|
|
286
|
+
code,
|
|
287
|
+
category: definition.category,
|
|
288
|
+
message: requiredString(ownDataValue(envelope, 'message', 'ErrorEnvelope v2'), 'message'),
|
|
289
|
+
traceId: requiredString(ownDataValue(envelope, 'traceId', 'ErrorEnvelope v2'), 'traceId'),
|
|
290
|
+
correlationId: requiredString(ownDataValue(envelope, 'correlationId', 'ErrorEnvelope v2'), 'correlationId'),
|
|
291
|
+
retryable,
|
|
292
|
+
effectEvidence: effectEvidence(definition.effect),
|
|
293
|
+
...(retryAfterSeconds === undefined ? {} : { retryAfterSeconds: Number(retryAfterSeconds) }),
|
|
294
|
+
...(parsedViolations === undefined ? {} : { violations: parsedViolations }),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function ownDataRecord(value: unknown, label: string): Readonly<Record<string, unknown>>;
|
|
2
|
+
export declare function ownDataEntries(value: Readonly<Record<string, unknown>>, label: string): readonly (readonly [string, unknown])[];
|
|
3
|
+
export declare function ownDataValue(value: Readonly<Record<string, unknown>>, key: string, label: string): unknown;
|
|
4
|
+
export declare function denseArrayValues(value: unknown, label: string): readonly unknown[];
|
|
5
|
+
/**
|
|
6
|
+
* Captures untrusted JSON-shaped input into detached, frozen package-owned data. Every proxy
|
|
7
|
+
* surface (prototype, own-key inventory and each property descriptor) is observed exactly once;
|
|
8
|
+
* validation after this boundary never reads the caller-owned object again.
|
|
9
|
+
*/
|
|
10
|
+
export declare function snapshotOwnJsonDataTree(value: unknown, label: string): unknown;
|
|
11
|
+
export declare function assertOwnJsonDataTree(value: unknown, label: string): void;
|
|
12
|
+
export declare function assertExactOwnFields(value: Readonly<Record<string, unknown>>, fields: readonly string[], label: string): void;
|
|
13
|
+
export declare function canonicalJsonDataEqual(left: unknown, right: unknown): boolean;
|
|
14
|
+
export declare function deepFreezeJsonData<T>(value: T): Readonly<T>;
|
|
15
|
+
/** Converts the supported closed JSON Schema fragments to OpenAPI 3.0 Schema Objects. */
|
|
16
|
+
export declare function jsonSchemaToOpenApiFragment(value: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>>;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Generated by packages/openapi-client/scripts/generate.mjs from packages/contracts/src/json-data.ts; do not edit.
|
|
2
|
+
const MAX_JSON_DATA_DEPTH = 64;
|
|
3
|
+
const MAX_JSON_DATA_NODES = 50_000;
|
|
4
|
+
function ownNames(value, label) {
|
|
5
|
+
try {
|
|
6
|
+
return Object.getOwnPropertyNames(value);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
throw new TypeError(`${label} cannot be inspected as JSON data`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function ownSymbols(value, label) {
|
|
13
|
+
try {
|
|
14
|
+
return Object.getOwnPropertySymbols(value);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new TypeError(`${label} cannot be inspected as JSON data`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function ownDescriptor(value, key, label) {
|
|
21
|
+
let descriptor;
|
|
22
|
+
try {
|
|
23
|
+
descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new TypeError(`${label}.${key} cannot be inspected as JSON data`);
|
|
27
|
+
}
|
|
28
|
+
if (descriptor === undefined ||
|
|
29
|
+
descriptor.enumerable !== true ||
|
|
30
|
+
!Object.hasOwn(descriptor, 'value')) {
|
|
31
|
+
throw new TypeError(`${label}.${key} must be an enumerable data field`);
|
|
32
|
+
}
|
|
33
|
+
return descriptor;
|
|
34
|
+
}
|
|
35
|
+
function prototypeOf(value, label) {
|
|
36
|
+
try {
|
|
37
|
+
return Object.getPrototypeOf(value);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new TypeError(`${label} cannot be inspected as JSON data`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function ownDataRecord(value, label) {
|
|
44
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
45
|
+
throw new TypeError(`${label} must be a plain object`);
|
|
46
|
+
}
|
|
47
|
+
const prototype = prototypeOf(value, label);
|
|
48
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
49
|
+
throw new TypeError(`${label} must be a plain object with no inherited fields`);
|
|
50
|
+
}
|
|
51
|
+
if (ownSymbols(value, label).length > 0) {
|
|
52
|
+
throw new TypeError(`${label} contains symbol fields`);
|
|
53
|
+
}
|
|
54
|
+
for (const key of ownNames(value, label))
|
|
55
|
+
ownDescriptor(value, key, label);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
export function ownDataEntries(value, label) {
|
|
59
|
+
ownDataRecord(value, label);
|
|
60
|
+
return ownNames(value, label).map((key) => [key, ownDescriptor(value, key, label).value]);
|
|
61
|
+
}
|
|
62
|
+
export function ownDataValue(value, key, label) {
|
|
63
|
+
const descriptor = ownDescriptor(value, key, label);
|
|
64
|
+
return descriptor.value;
|
|
65
|
+
}
|
|
66
|
+
export function denseArrayValues(value, label) {
|
|
67
|
+
if (!Array.isArray(value))
|
|
68
|
+
throw new TypeError(`${label} must be a dense array`);
|
|
69
|
+
const prototype = prototypeOf(value, label);
|
|
70
|
+
if (prototype !== Array.prototype && prototype !== null) {
|
|
71
|
+
throw new TypeError(`${label} must be a dense array with no inherited fields`);
|
|
72
|
+
}
|
|
73
|
+
if (ownSymbols(value, label).length > 0)
|
|
74
|
+
throw new TypeError(`${label} contains symbol fields`);
|
|
75
|
+
const names = ownNames(value, label);
|
|
76
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
|
|
77
|
+
if (lengthDescriptor === undefined ||
|
|
78
|
+
!Object.hasOwn(lengthDescriptor, 'value') ||
|
|
79
|
+
!Number.isSafeInteger(lengthDescriptor.value) ||
|
|
80
|
+
Number(lengthDescriptor.value) < 0) {
|
|
81
|
+
throw new TypeError(`${label} has an invalid array length`);
|
|
82
|
+
}
|
|
83
|
+
const length = Number(lengthDescriptor.value);
|
|
84
|
+
const expectedNames = [...Array.from({ length }, (_, index) => String(index)), 'length'];
|
|
85
|
+
if (names.length !== expectedNames.length ||
|
|
86
|
+
names.some((name, index) => name !== expectedNames[index])) {
|
|
87
|
+
throw new TypeError(`${label} must be a dense array without named fields`);
|
|
88
|
+
}
|
|
89
|
+
return expectedNames.slice(0, -1).map((key) => ownDescriptor(value, key, label).value);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Captures untrusted JSON-shaped input into detached, frozen package-owned data. Every proxy
|
|
93
|
+
* surface (prototype, own-key inventory and each property descriptor) is observed exactly once;
|
|
94
|
+
* validation after this boundary never reads the caller-owned object again.
|
|
95
|
+
*/
|
|
96
|
+
export function snapshotOwnJsonDataTree(value, label) {
|
|
97
|
+
const activePath = new Set();
|
|
98
|
+
let nodes = 0;
|
|
99
|
+
const snapshot = (candidate, candidateLabel, depth) => {
|
|
100
|
+
nodes += 1;
|
|
101
|
+
if (nodes > MAX_JSON_DATA_NODES)
|
|
102
|
+
throw new TypeError(`${label} exceeds the node budget`);
|
|
103
|
+
if (depth > MAX_JSON_DATA_DEPTH)
|
|
104
|
+
throw new TypeError(`${label} exceeds the depth budget`);
|
|
105
|
+
const kind = typeof candidate;
|
|
106
|
+
if (candidate === null || kind === 'string' || kind === 'boolean')
|
|
107
|
+
return candidate;
|
|
108
|
+
if (kind === 'number') {
|
|
109
|
+
if (!Number.isFinite(candidate)) {
|
|
110
|
+
throw new TypeError(`${candidateLabel} must be a finite JSON number`);
|
|
111
|
+
}
|
|
112
|
+
return candidate;
|
|
113
|
+
}
|
|
114
|
+
if (kind !== 'object')
|
|
115
|
+
throw new TypeError(`${candidateLabel} is not JSON data`);
|
|
116
|
+
const object = candidate;
|
|
117
|
+
if (activePath.has(object))
|
|
118
|
+
throw new TypeError(`${candidateLabel} contains a cycle`);
|
|
119
|
+
activePath.add(object);
|
|
120
|
+
try {
|
|
121
|
+
let array;
|
|
122
|
+
let prototype;
|
|
123
|
+
let keys;
|
|
124
|
+
try {
|
|
125
|
+
array = Array.isArray(object);
|
|
126
|
+
prototype = Reflect.getPrototypeOf(object);
|
|
127
|
+
keys = Reflect.ownKeys(object);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new TypeError(`${candidateLabel} cannot be inspected as JSON data`);
|
|
131
|
+
}
|
|
132
|
+
if (keys.length > MAX_JSON_DATA_NODES) {
|
|
133
|
+
throw new TypeError(`${candidateLabel} exceeds the property budget`);
|
|
134
|
+
}
|
|
135
|
+
const descriptors = new Map();
|
|
136
|
+
for (const key of keys) {
|
|
137
|
+
let descriptor;
|
|
138
|
+
try {
|
|
139
|
+
descriptor = Reflect.getOwnPropertyDescriptor(object, key);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new TypeError(`${candidateLabel} cannot be inspected as JSON data`);
|
|
143
|
+
}
|
|
144
|
+
if (descriptor === undefined) {
|
|
145
|
+
throw new TypeError(`${candidateLabel} has an unstable property inventory`);
|
|
146
|
+
}
|
|
147
|
+
descriptors.set(key, descriptor);
|
|
148
|
+
}
|
|
149
|
+
if (array) {
|
|
150
|
+
if (prototype !== Array.prototype && prototype !== null) {
|
|
151
|
+
throw new TypeError(`${candidateLabel} must be a plain dense array`);
|
|
152
|
+
}
|
|
153
|
+
if (keys.some((key) => typeof key !== 'string')) {
|
|
154
|
+
throw new TypeError(`${candidateLabel} contains symbol fields`);
|
|
155
|
+
}
|
|
156
|
+
const lengthDescriptor = descriptors.get('length');
|
|
157
|
+
if (lengthDescriptor === undefined ||
|
|
158
|
+
!Object.hasOwn(lengthDescriptor, 'value') ||
|
|
159
|
+
!Number.isSafeInteger(lengthDescriptor.value) ||
|
|
160
|
+
Number(lengthDescriptor.value) < 0 ||
|
|
161
|
+
Number(lengthDescriptor.value) > MAX_JSON_DATA_NODES) {
|
|
162
|
+
throw new TypeError(`${candidateLabel} has an invalid array length`);
|
|
163
|
+
}
|
|
164
|
+
const length = Number(lengthDescriptor.value);
|
|
165
|
+
if (keys.length !== length + 1) {
|
|
166
|
+
throw new TypeError(`${candidateLabel} must be a dense array without named fields`);
|
|
167
|
+
}
|
|
168
|
+
const result = new Array(length);
|
|
169
|
+
for (let index = 0; index < length; index += 1) {
|
|
170
|
+
const descriptor = descriptors.get(String(index));
|
|
171
|
+
if (descriptor === undefined ||
|
|
172
|
+
descriptor.enumerable !== true ||
|
|
173
|
+
!Object.hasOwn(descriptor, 'value')) {
|
|
174
|
+
throw new TypeError(`${candidateLabel} must be a dense array without accessors`);
|
|
175
|
+
}
|
|
176
|
+
result[index] = snapshot(descriptor.value, `${candidateLabel}[${index}]`, depth + 1);
|
|
177
|
+
}
|
|
178
|
+
return Object.freeze(result);
|
|
179
|
+
}
|
|
180
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
181
|
+
throw new TypeError(`${candidateLabel} must be a plain object with no inherited fields`);
|
|
182
|
+
}
|
|
183
|
+
if (keys.some((key) => typeof key !== 'string')) {
|
|
184
|
+
throw new TypeError(`${candidateLabel} contains symbol fields`);
|
|
185
|
+
}
|
|
186
|
+
const result = Object.create(null);
|
|
187
|
+
for (const key of keys) {
|
|
188
|
+
const descriptor = descriptors.get(key);
|
|
189
|
+
if (descriptor === undefined ||
|
|
190
|
+
descriptor.enumerable !== true ||
|
|
191
|
+
!Object.hasOwn(descriptor, 'value')) {
|
|
192
|
+
throw new TypeError(`${candidateLabel}.${key} must be an enumerable data field`);
|
|
193
|
+
}
|
|
194
|
+
Object.defineProperty(result, key, {
|
|
195
|
+
value: snapshot(descriptor.value, `${candidateLabel}.${key}`, depth + 1),
|
|
196
|
+
enumerable: true,
|
|
197
|
+
configurable: false,
|
|
198
|
+
writable: false,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return Object.freeze(result);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
activePath.delete(object);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
return snapshot(value, label, 0);
|
|
208
|
+
}
|
|
209
|
+
export function assertOwnJsonDataTree(value, label) {
|
|
210
|
+
snapshotOwnJsonDataTree(value, label);
|
|
211
|
+
}
|
|
212
|
+
export function assertExactOwnFields(value, fields, label) {
|
|
213
|
+
const actual = ownDataEntries(value, label).map(([key]) => key);
|
|
214
|
+
if (actual.length !== fields.length ||
|
|
215
|
+
actual.some((field) => !fields.includes(field)) ||
|
|
216
|
+
fields.some((field) => !actual.includes(field))) {
|
|
217
|
+
throw new TypeError(`${label} must contain exactly: ${fields.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export function canonicalJsonDataEqual(left, right) {
|
|
221
|
+
const work = [[left, right]];
|
|
222
|
+
while (work.length > 0) {
|
|
223
|
+
const pair = work.pop();
|
|
224
|
+
if (pair === undefined)
|
|
225
|
+
break;
|
|
226
|
+
const [leftValue, rightValue] = pair;
|
|
227
|
+
if (Object.is(leftValue, rightValue))
|
|
228
|
+
continue;
|
|
229
|
+
const leftIsArray = Array.isArray(leftValue);
|
|
230
|
+
if (leftValue === null ||
|
|
231
|
+
rightValue === null ||
|
|
232
|
+
typeof leftValue !== 'object' ||
|
|
233
|
+
typeof rightValue !== 'object' ||
|
|
234
|
+
leftIsArray !== Array.isArray(rightValue)) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const leftEntries = leftIsArray
|
|
238
|
+
? denseArrayValues(leftValue, 'left value').map((item, index) => [String(index), item])
|
|
239
|
+
: ownDataEntries(ownDataRecord(leftValue, 'left value'), 'left value');
|
|
240
|
+
const rightEntries = leftIsArray
|
|
241
|
+
? denseArrayValues(rightValue, 'right value').map((item, index) => [String(index), item])
|
|
242
|
+
: ownDataEntries(ownDataRecord(rightValue, 'right value'), 'right value');
|
|
243
|
+
if (leftEntries.length !== rightEntries.length)
|
|
244
|
+
return false;
|
|
245
|
+
if (leftIsArray) {
|
|
246
|
+
for (let index = leftEntries.length - 1; index >= 0; index -= 1) {
|
|
247
|
+
const leftEntry = leftEntries[index];
|
|
248
|
+
const rightEntry = rightEntries[index];
|
|
249
|
+
if (leftEntry === undefined || rightEntry === undefined || leftEntry[0] !== rightEntry[0]) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
work.push([leftEntry[1], rightEntry[1]]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const rightByName = new Map(rightEntries);
|
|
257
|
+
for (const [key, item] of leftEntries) {
|
|
258
|
+
if (!rightByName.has(key))
|
|
259
|
+
return false;
|
|
260
|
+
work.push([item, rightByName.get(key)]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
export function deepFreezeJsonData(value) {
|
|
267
|
+
const work = [];
|
|
268
|
+
if (value !== null && typeof value === 'object')
|
|
269
|
+
work.push(value);
|
|
270
|
+
const visited = new Set();
|
|
271
|
+
while (work.length > 0) {
|
|
272
|
+
const current = work.pop();
|
|
273
|
+
if (current === undefined || visited.has(current))
|
|
274
|
+
continue;
|
|
275
|
+
visited.add(current);
|
|
276
|
+
const children = Array.isArray(current)
|
|
277
|
+
? current
|
|
278
|
+
: Object.values(current);
|
|
279
|
+
for (const child of children) {
|
|
280
|
+
if (child !== null && typeof child === 'object')
|
|
281
|
+
work.push(child);
|
|
282
|
+
}
|
|
283
|
+
Object.freeze(current);
|
|
284
|
+
}
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
/** Converts the supported closed JSON Schema fragments to OpenAPI 3.0 Schema Objects. */
|
|
288
|
+
export function jsonSchemaToOpenApiFragment(value) {
|
|
289
|
+
const transform = (candidate) => {
|
|
290
|
+
if (Array.isArray(candidate))
|
|
291
|
+
return candidate.map(transform);
|
|
292
|
+
if (candidate === null || typeof candidate !== 'object')
|
|
293
|
+
return candidate;
|
|
294
|
+
const result = {};
|
|
295
|
+
for (const [key, item] of Object.entries(candidate)) {
|
|
296
|
+
if (key === '$schema' || key === '$id')
|
|
297
|
+
continue;
|
|
298
|
+
if (key === 'const') {
|
|
299
|
+
result.enum = [transform(item)];
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
result[key] = transform(item);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
};
|
|
307
|
+
return deepFreezeJsonData(transform(value));
|
|
308
|
+
}
|