@layervai/qurl 3.1.1 → 4.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/README.md +259 -46
- package/dist/cjs/client-connectors.d.ts +44 -0
- package/dist/cjs/client-connectors.js +189 -0
- package/dist/cjs/client-delegated.d.ts +30 -0
- package/dist/cjs/client-delegated.js +312 -0
- package/dist/cjs/client-errors.d.ts +44 -0
- package/dist/cjs/client-errors.js +219 -0
- package/dist/cjs/client-http-types.d.ts +37 -0
- package/dist/cjs/client-http-types.js +2 -0
- package/dist/cjs/client-input-fields.d.ts +53 -0
- package/dist/cjs/client-input-fields.js +262 -0
- package/dist/cjs/client-limits.d.ts +36 -0
- package/dist/cjs/client-limits.js +61 -0
- package/dist/cjs/client-management.d.ts +12 -0
- package/dist/cjs/client-management.js +119 -0
- package/dist/cjs/client-paths.d.ts +21 -0
- package/dist/cjs/client-paths.js +143 -0
- package/dist/cjs/client-portals.d.ts +82 -0
- package/dist/cjs/client-portals.js +244 -0
- package/dist/cjs/client-resources.d.ts +29 -0
- package/dist/cjs/client-resources.js +331 -0
- package/dist/cjs/client-validation.d.ts +35 -0
- package/dist/cjs/client-validation.js +326 -0
- package/dist/cjs/client.d.ts +45 -75
- package/dist/cjs/client.js +621 -2448
- package/dist/cjs/errors.d.ts +9 -1
- package/dist/cjs/errors.js +10 -1
- package/dist/cjs/index.d.ts +4 -2
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/response-metadata.d.ts +13 -0
- package/dist/cjs/response-metadata.js +38 -0
- package/dist/cjs/share.js +2 -0
- package/dist/cjs/target-path.d.ts +2 -0
- package/dist/cjs/target-path.js +34 -0
- package/dist/cjs/types.d.ts +83 -7
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client-connectors.d.ts +44 -0
- package/dist/esm/client-connectors.js +180 -0
- package/dist/esm/client-delegated.d.ts +30 -0
- package/dist/esm/client-delegated.js +295 -0
- package/dist/esm/client-errors.d.ts +44 -0
- package/dist/esm/client-errors.js +206 -0
- package/dist/esm/client-http-types.d.ts +37 -0
- package/dist/esm/client-http-types.js +1 -0
- package/dist/esm/client-input-fields.d.ts +53 -0
- package/dist/esm/client-input-fields.js +258 -0
- package/dist/esm/client-limits.d.ts +36 -0
- package/dist/esm/client-limits.js +58 -0
- package/dist/esm/client-management.d.ts +12 -0
- package/dist/esm/client-management.js +113 -0
- package/dist/esm/client-paths.d.ts +21 -0
- package/dist/esm/client-paths.js +138 -0
- package/dist/esm/client-portals.d.ts +82 -0
- package/dist/esm/client-portals.js +236 -0
- package/dist/esm/client-resources.d.ts +29 -0
- package/dist/esm/client-resources.js +323 -0
- package/dist/esm/client-validation.d.ts +35 -0
- package/dist/esm/client-validation.js +302 -0
- package/dist/esm/client.d.ts +45 -76
- package/dist/esm/client.js +318 -2147
- package/dist/esm/crid.d.ts +0 -1
- package/dist/esm/crid.js +0 -1
- package/dist/esm/errors.d.ts +9 -2
- package/dist/esm/errors.js +9 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.js +2 -2
- package/dist/esm/internal.d.ts +0 -1
- package/dist/esm/internal.js +0 -1
- package/dist/esm/node/agent-runtime.d.ts +0 -1
- package/dist/esm/node/agent-runtime.js +0 -1
- package/dist/esm/node/agent-state.d.ts +0 -1
- package/dist/esm/node/agent-state.js +0 -1
- package/dist/esm/node/agent-transport.d.ts +0 -1
- package/dist/esm/node/agent-transport.js +0 -1
- package/dist/esm/node/deployment.d.ts +0 -1
- package/dist/esm/node/deployment.js +0 -1
- package/dist/esm/node/file-agent-state.d.ts +0 -1
- package/dist/esm/node/file-agent-state.js +0 -1
- package/dist/esm/node/native-udp.d.ts +0 -1
- package/dist/esm/node/native-udp.js +0 -1
- package/dist/esm/node/nhp-wire.d.ts +0 -1
- package/dist/esm/node/nhp-wire.js +0 -1
- package/dist/esm/node/portal-opener.d.ts +0 -1
- package/dist/esm/node/portal-opener.js +0 -1
- package/dist/esm/node/provider.d.ts +0 -1
- package/dist/esm/node/provider.js +0 -1
- package/dist/esm/node/qv2.d.ts +0 -1
- package/dist/esm/node/qv2.js +0 -1
- package/dist/esm/node/relay.d.ts +0 -1
- package/dist/esm/node/relay.js +0 -1
- package/dist/esm/node/sealed-agent-state.d.ts +0 -1
- package/dist/esm/node/sealed-agent-state.js +0 -1
- package/dist/esm/node/strict-json.d.ts +0 -1
- package/dist/esm/node/strict-json.js +0 -1
- package/dist/esm/node.d.ts +0 -1
- package/dist/esm/node.js +0 -1
- package/dist/esm/response-metadata.d.ts +13 -0
- package/dist/esm/response-metadata.js +33 -0
- package/dist/esm/share.d.ts +0 -1
- package/dist/esm/share.js +2 -1
- package/dist/esm/target-path.d.ts +2 -0
- package/dist/esm/target-path.js +30 -0
- package/dist/esm/types.d.ts +83 -8
- package/dist/esm/types.js +0 -1
- package/dist/esm/version.d.ts +1 -2
- package/dist/esm/version.js +1 -2
- package/package.json +9 -7
- package/dist/esm/client.d.ts.map +0 -1
- package/dist/esm/client.js.map +0 -1
- package/dist/esm/crid.d.ts.map +0 -1
- package/dist/esm/crid.js.map +0 -1
- package/dist/esm/errors.d.ts.map +0 -1
- package/dist/esm/errors.js.map +0 -1
- package/dist/esm/index.d.ts.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/internal.d.ts.map +0 -1
- package/dist/esm/internal.js.map +0 -1
- package/dist/esm/node/agent-runtime.d.ts.map +0 -1
- package/dist/esm/node/agent-runtime.js.map +0 -1
- package/dist/esm/node/agent-state.d.ts.map +0 -1
- package/dist/esm/node/agent-state.js.map +0 -1
- package/dist/esm/node/agent-transport.d.ts.map +0 -1
- package/dist/esm/node/agent-transport.js.map +0 -1
- package/dist/esm/node/deployment.d.ts.map +0 -1
- package/dist/esm/node/deployment.js.map +0 -1
- package/dist/esm/node/file-agent-state.d.ts.map +0 -1
- package/dist/esm/node/file-agent-state.js.map +0 -1
- package/dist/esm/node/native-udp.d.ts.map +0 -1
- package/dist/esm/node/native-udp.js.map +0 -1
- package/dist/esm/node/nhp-wire.d.ts.map +0 -1
- package/dist/esm/node/nhp-wire.js.map +0 -1
- package/dist/esm/node/portal-opener.d.ts.map +0 -1
- package/dist/esm/node/portal-opener.js.map +0 -1
- package/dist/esm/node/provider.d.ts.map +0 -1
- package/dist/esm/node/provider.js.map +0 -1
- package/dist/esm/node/qv2.d.ts.map +0 -1
- package/dist/esm/node/qv2.js.map +0 -1
- package/dist/esm/node/relay.d.ts.map +0 -1
- package/dist/esm/node/relay.js.map +0 -1
- package/dist/esm/node/sealed-agent-state.d.ts.map +0 -1
- package/dist/esm/node/sealed-agent-state.js.map +0 -1
- package/dist/esm/node/strict-json.d.ts.map +0 -1
- package/dist/esm/node/strict-json.js.map +0 -1
- package/dist/esm/node.d.ts.map +0 -1
- package/dist/esm/node.js.map +0 -1
- package/dist/esm/share.d.ts.map +0 -1
- package/dist/esm/share.js.map +0 -1
- package/dist/esm/types.d.ts.map +0 -1
- package/dist/esm/types.js.map +0 -1
- package/dist/esm/version.d.ts.map +0 -1
- package/dist/esm/version.js.map +0 -1
package/dist/cjs/client.js
CHANGED
|
@@ -1,50 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ConnectorResource = exports.ProtectedResource = exports.
|
|
4
|
-
exports.isApiKeyRequestScope = isApiKeyRequestScope;
|
|
5
|
-
const crid_js_1 = require("./crid.js");
|
|
3
|
+
exports.ConnectorResource = exports.ProtectedResource = exports.SIGNED_FRAGMENT_RE = exports.isApiKeyRequestScope = exports.QURLClient = void 0;
|
|
6
4
|
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const response_metadata_js_1 = require("./response-metadata.js");
|
|
7
6
|
const internal_js_1 = require("./internal.js");
|
|
8
|
-
const share_js_1 = require("./share.js");
|
|
9
7
|
const version_js_1 = require("./version.js");
|
|
8
|
+
const share_js_1 = require("./share.js");
|
|
9
|
+
const client_limits_js_1 = require("./client-limits.js");
|
|
10
|
+
const client_errors_js_1 = require("./client-errors.js");
|
|
11
|
+
const client_validation_js_1 = require("./client-validation.js");
|
|
12
|
+
const client_input_fields_js_1 = require("./client-input-fields.js");
|
|
13
|
+
const client_resources_js_1 = require("./client-resources.js");
|
|
14
|
+
const client_portals_js_1 = require("./client-portals.js");
|
|
15
|
+
const client_connectors_js_1 = require("./client-connectors.js");
|
|
16
|
+
const client_paths_js_1 = require("./client-paths.js");
|
|
17
|
+
const client_delegated_js_1 = require("./client-delegated.js");
|
|
18
|
+
const client_management_js_1 = require("./client-management.js");
|
|
10
19
|
const DEFAULT_BASE_URL = "https://api.layerv.ai";
|
|
11
20
|
const DEFAULT_MAX_RETRIES = 3;
|
|
12
21
|
const DEFAULT_TIMEOUT = 30_000;
|
|
13
|
-
// Match qurl-go c4f2f98's exact API response cap. Current qurl-service list
|
|
14
|
-
// routes cap pages at 100 items. A caller can still exceed this cap with a
|
|
15
|
-
// large, heavily escaped webhook-delivery page, so list callers must request a
|
|
16
|
-
// smaller page if the service reports the body-limit error. This cap is fixed:
|
|
17
|
-
// do not add an override or widen one SDK independently from the other.
|
|
18
|
-
// The limit is enforced from Content-Length when usable and again while
|
|
19
|
-
// consuming the response stream.
|
|
20
|
-
const MAX_RESPONSE_BODY_BYTES = 1 << 20;
|
|
21
|
-
const MAX_ERROR_SNIPPET_BYTES = 512;
|
|
22
|
-
// UTF-8 uses at least one byte per UTF-16 code unit. Reading more source units
|
|
23
|
-
// cannot add a byte that fits in a 512-byte retained diagnostic.
|
|
24
|
-
const MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS = MAX_ERROR_SNIPPET_BYTES;
|
|
25
|
-
const MAX_INVALID_FIELD_ENTRIES = 100;
|
|
26
|
-
const MAX_DEBUG_BODY_KEYS = 100;
|
|
27
|
-
// Bound each retained diagnostic collection as well as each string. Without
|
|
28
|
-
// this aggregate cap, 100 valid 512-byte keys and values could turn a small
|
|
29
|
-
// server error into a large caller-visible object. This is a diagnostic budget,
|
|
30
|
-
// not part of the API wire contract.
|
|
31
|
-
const MAX_DIAGNOSTIC_COLLECTION_BYTES = 8 << 10;
|
|
32
|
-
const TEXT_ENCODER = new TextEncoder();
|
|
33
|
-
const ERROR_SNIPPET_ELLIPSIS = "...";
|
|
34
|
-
const ERROR_SNIPPET_ELLIPSIS_BYTES = 3;
|
|
35
|
-
// RFC 8259 JSON exchanged between systems must be valid UTF-8. A non-fatal
|
|
36
|
-
// decoder would replace malformed bytes with U+FFFD before JSON.parse and hide
|
|
37
|
-
// the producer's exact contract violation.
|
|
38
|
-
const TEXT_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
39
|
-
const RETRY_BASE_DELAY_MS = 500;
|
|
40
|
-
// Bounds local exponential backoff (NOT server-asserted Retry-After —
|
|
41
|
-
// see `RETRY_AFTER_HARD_CAP_MS` for that).
|
|
42
|
-
const RETRY_MAX_DELAY_MS = 30_000;
|
|
43
|
-
// Hard cap on server-asserted Retry-After. Guards against `Retry-After`
|
|
44
|
-
// values >2^31-1 ms overflowing Node's setTimeout (silently truncated to
|
|
45
|
-
// 1ms — turns a "wait a month" directive into a hot retry loop).
|
|
46
|
-
const RETRY_AFTER_HARD_CAP_MS = 60 * 60 * 1000;
|
|
47
|
-
const RETRY_AFTER_PARSE_LIMIT_S = RETRY_AFTER_HARD_CAP_MS / 1000;
|
|
48
22
|
const RETRYABLE_STATUS = new Set([429, 502, 503, 504]);
|
|
49
23
|
// Keep mutating status retries 429-only until the service-side idempotency
|
|
50
24
|
// cache contract is proven across 5xx, TTL, and replay-window edge cases.
|
|
@@ -54,12 +28,11 @@ const REDIRECT_RESPONSE_STATUSES = new Set([300, 301, 302, 303, 305, 307, 308]);
|
|
|
54
28
|
const NO_RETRYABLE_STATUSES = new Set();
|
|
55
29
|
const IDEMPOTENCY_KEY_METHODS = new Set(["POST", "PATCH"]);
|
|
56
30
|
const MUTATING_RETRY_METHODS = new Set(["POST", "PATCH"]);
|
|
57
|
-
const MAX_IDEMPOTENCY_KEY = 256;
|
|
58
31
|
const IDEMPOTENCY_KEY_VALUE_RE = /^[\x21-\x7e](?:[\x20-\x7e]*[\x21-\x7e])?$/;
|
|
59
32
|
const UUID_HEX = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
60
33
|
class ResponseBodyTooLargeError extends Error {
|
|
61
|
-
constructor() {
|
|
62
|
-
super(`Response body exceeds ${
|
|
34
|
+
constructor(limit) {
|
|
35
|
+
super(`Response body exceeds ${limit}-byte limit`);
|
|
63
36
|
this.name = "ResponseBodyTooLargeError";
|
|
64
37
|
}
|
|
65
38
|
}
|
|
@@ -78,288 +51,22 @@ class ResponseBodyMaterializationError extends Error {
|
|
|
78
51
|
const NO_PASSTHROUGH_STATUSES = [];
|
|
79
52
|
const BATCH_PASSTHROUGH_STATUSES = [400];
|
|
80
53
|
const DELEGATED_BATCH_PASSTHROUGH_STATUSES = [304];
|
|
81
|
-
/** Allowlist of known query-param keys for the `list()` endpoint. */
|
|
82
|
-
const LIST_PARAM_KEYS = [
|
|
83
|
-
"limit",
|
|
84
|
-
"cursor",
|
|
85
|
-
"status",
|
|
86
|
-
"q",
|
|
87
|
-
"sort",
|
|
88
|
-
"created_after",
|
|
89
|
-
"created_before",
|
|
90
|
-
"expires_before",
|
|
91
|
-
"expires_after",
|
|
92
|
-
];
|
|
93
|
-
const REQUEST_OPTION_KEYS = ["idempotencyKey"];
|
|
94
|
-
const DELEGATED_BATCH_INPUT_KEYS = [
|
|
95
|
-
"mint_capability",
|
|
96
|
-
"grants",
|
|
97
|
-
];
|
|
98
|
-
const DELEGATED_BATCH_GRANT_KEYS = [
|
|
99
|
-
"expires_in",
|
|
100
|
-
"label",
|
|
101
|
-
"one_time_use",
|
|
102
|
-
"max_sessions",
|
|
103
|
-
"session_duration",
|
|
104
|
-
"access_policy",
|
|
105
|
-
];
|
|
106
|
-
const DELEGATED_BATCH_CREATE_OPTION_KEYS = [
|
|
107
|
-
"idempotencyKey",
|
|
108
|
-
];
|
|
109
|
-
const DELEGATED_BATCH_GET_OPTION_KEYS = [
|
|
110
|
-
"etag",
|
|
111
|
-
];
|
|
112
|
-
// Compile-time witness: `Exclude<keyof X, (typeof KEYS)[number]>` is
|
|
113
|
-
// `never` iff KEYS lists every key of X. Paired with the
|
|
114
|
-
// `satisfies readonly (keyof X)[]` clause on the array (which catches
|
|
115
|
-
// the other direction), this gives bidirectional drift detection on
|
|
116
|
-
// the *_KEYS allowlists at zero runtime cost.
|
|
117
|
-
function assertExhaustive(_) {
|
|
118
|
-
/* type-only check */
|
|
119
|
-
}
|
|
120
|
-
assertExhaustive(true);
|
|
121
|
-
assertExhaustive(true);
|
|
122
|
-
assertExhaustive(true);
|
|
123
|
-
assertExhaustive(true);
|
|
124
|
-
assertExhaustive(true);
|
|
125
|
-
assertExhaustive(true);
|
|
126
|
-
const CREATE_FIELD_KEYS = [
|
|
127
|
-
"private",
|
|
128
|
-
"type",
|
|
129
|
-
"target_url",
|
|
130
|
-
"expires_in",
|
|
131
|
-
"one_time_use",
|
|
132
|
-
"max_sessions",
|
|
133
|
-
"session_duration",
|
|
134
|
-
"label",
|
|
135
|
-
"access_policy",
|
|
136
|
-
"custom_domain",
|
|
137
|
-
];
|
|
138
|
-
assertExhaustive(true);
|
|
139
|
-
/**
|
|
140
|
-
* Fields accepted by `update()`. The empty-input pre-flight check in
|
|
141
|
-
* {@link QURLClient.update} iterates this const — so adding a new field to
|
|
142
|
-
* {@link UpdateInput} without listing it here will fail the
|
|
143
|
-
* paired `assertExhaustive` witness at compile time rather than silently
|
|
144
|
-
* sneaking through an empty-input request.
|
|
145
|
-
*/
|
|
146
|
-
const UPDATE_FIELD_KEYS = [
|
|
147
|
-
"extend_by",
|
|
148
|
-
"expires_at",
|
|
149
|
-
"description",
|
|
150
|
-
"tags",
|
|
151
|
-
];
|
|
152
|
-
assertExhaustive(true);
|
|
153
|
-
/**
|
|
154
|
-
* Fields accepted by `mintLink()`. Iterated by the null-stripping
|
|
155
|
-
* normalization loop in {@link QURLClient.mintLink} so unknown keys
|
|
156
|
-
* from untyped-JS callers don't leak through to the wire body.
|
|
157
|
-
* Symmetric with `LIST_PARAM_KEYS` / `UPDATE_FIELD_KEYS`.
|
|
158
|
-
*/
|
|
159
|
-
const MINT_FIELD_KEYS = [
|
|
160
|
-
"expires_in",
|
|
161
|
-
"expires_at",
|
|
162
|
-
"label",
|
|
163
|
-
"one_time_use",
|
|
164
|
-
"max_sessions",
|
|
165
|
-
"session_duration",
|
|
166
|
-
"access_policy",
|
|
167
|
-
];
|
|
168
|
-
assertExhaustive(true);
|
|
169
|
-
const RESOURCE_LIST_PARAM_KEYS = [
|
|
170
|
-
"cursor",
|
|
171
|
-
"limit",
|
|
172
|
-
"alias",
|
|
173
|
-
"slug",
|
|
174
|
-
"status",
|
|
175
|
-
"type",
|
|
176
|
-
];
|
|
177
|
-
assertExhaustive(true);
|
|
178
|
-
const CREATE_RESOURCE_FIELD_KEYS = [
|
|
179
|
-
"private",
|
|
180
|
-
"consumer_access",
|
|
181
|
-
"type",
|
|
182
|
-
"target_url",
|
|
183
|
-
"description",
|
|
184
|
-
"tags",
|
|
185
|
-
"custom_domain",
|
|
186
|
-
"alias",
|
|
187
|
-
"slug",
|
|
188
|
-
"find_or_create",
|
|
189
|
-
];
|
|
190
|
-
assertExhaustive(true);
|
|
191
|
-
const UPDATE_RESOURCE_FIELD_KEYS = [
|
|
192
|
-
"allowed_device_keys_add",
|
|
193
|
-
"allowed_device_keys_remove",
|
|
194
|
-
"consumer_access",
|
|
195
|
-
"expected_consumer_access_version",
|
|
196
|
-
"description",
|
|
197
|
-
"tags",
|
|
198
|
-
"custom_domain",
|
|
199
|
-
"preserve_host",
|
|
200
|
-
"alias",
|
|
201
|
-
];
|
|
202
|
-
assertExhaustive(true);
|
|
203
|
-
const CREATE_QURL_FOR_RESOURCE_FIELD_KEYS = [
|
|
204
|
-
"expires_in",
|
|
205
|
-
"one_time_use",
|
|
206
|
-
"max_sessions",
|
|
207
|
-
"session_duration",
|
|
208
|
-
"label",
|
|
209
|
-
"access_policy",
|
|
210
|
-
"target_path",
|
|
211
|
-
];
|
|
212
|
-
const SHARE_RESOURCE_OPTION_KEYS = [
|
|
213
|
-
"ttlSeconds",
|
|
214
|
-
];
|
|
215
|
-
assertExhaustive(true);
|
|
216
|
-
assertExhaustive(true);
|
|
217
|
-
/**
|
|
218
|
-
* Options accepted by the portal-verb `protectUrl` / `createPortal`
|
|
219
|
-
* surface. These are camelCase caller-facing options (the portal verbs are
|
|
220
|
-
* the idiomatic layer); the build helpers map them onto the same wire
|
|
221
|
-
* fields the REST-shaped methods send.
|
|
222
|
-
*/
|
|
223
|
-
const PROTECT_URL_OPTION_KEYS = [
|
|
224
|
-
"private",
|
|
225
|
-
"consumerAccess",
|
|
226
|
-
"description",
|
|
227
|
-
"tags",
|
|
228
|
-
"customDomain",
|
|
229
|
-
"alias",
|
|
230
|
-
];
|
|
231
|
-
assertExhaustive(true);
|
|
232
|
-
const CREATE_PORTAL_OPTION_KEYS = [
|
|
233
|
-
"validFor",
|
|
234
|
-
"label",
|
|
235
|
-
"oneTimeUse",
|
|
236
|
-
"maxSessions",
|
|
237
|
-
"sessionDuration",
|
|
238
|
-
"targetPath",
|
|
239
|
-
];
|
|
240
|
-
assertExhaustive(true);
|
|
241
|
-
const UPDATE_RESOURCE_QURL_FIELD_KEYS = [
|
|
242
|
-
"extend_by",
|
|
243
|
-
"expires_at",
|
|
244
|
-
"label",
|
|
245
|
-
"access_policy",
|
|
246
|
-
"max_sessions",
|
|
247
|
-
"session_duration",
|
|
248
|
-
];
|
|
249
|
-
assertExhaustive(true);
|
|
250
|
-
const UPDATE_CUSTOMER_FIELD_KEYS = [
|
|
251
|
-
"spending_cap_cents",
|
|
252
|
-
];
|
|
253
|
-
assertExhaustive(true);
|
|
254
|
-
const CREATE_BILLING_CHECKOUT_FIELD_KEYS = [
|
|
255
|
-
"plan",
|
|
256
|
-
];
|
|
257
|
-
assertExhaustive(true);
|
|
258
|
-
const REGISTER_DOMAIN_FIELD_KEYS = [
|
|
259
|
-
"domain",
|
|
260
|
-
];
|
|
261
|
-
assertExhaustive(true);
|
|
262
|
-
const CREATE_WEBHOOK_FIELD_KEYS = [
|
|
263
|
-
"url",
|
|
264
|
-
"events",
|
|
265
|
-
"description",
|
|
266
|
-
];
|
|
267
|
-
assertExhaustive(true);
|
|
268
|
-
const UPDATE_WEBHOOK_FIELD_KEYS = [
|
|
269
|
-
"url",
|
|
270
|
-
"events",
|
|
271
|
-
"description",
|
|
272
|
-
"status",
|
|
273
|
-
];
|
|
274
|
-
assertExhaustive(true);
|
|
275
|
-
const CREATE_API_KEY_FIELD_KEYS = [
|
|
276
|
-
"kind",
|
|
277
|
-
"name",
|
|
278
|
-
"scopes",
|
|
279
|
-
"target",
|
|
280
|
-
"claims",
|
|
281
|
-
"expires_in",
|
|
282
|
-
];
|
|
283
|
-
assertExhaustive(true);
|
|
284
|
-
const UPDATE_API_KEY_FIELD_KEYS = [
|
|
285
|
-
"name",
|
|
286
|
-
"scopes",
|
|
287
|
-
];
|
|
288
|
-
assertExhaustive(true);
|
|
289
|
-
const REDEEM_ACCESS_CODE_FIELD_KEYS = [
|
|
290
|
-
"code",
|
|
291
|
-
"honeypot",
|
|
292
|
-
"elapsed_ms",
|
|
293
|
-
];
|
|
294
|
-
assertExhaustive(true);
|
|
295
|
-
const CREATE_ACCESS_CODE_FIELD_KEYS = [
|
|
296
|
-
"resource_id",
|
|
297
|
-
"name",
|
|
298
|
-
"max_uses",
|
|
299
|
-
"expires_at",
|
|
300
|
-
];
|
|
301
|
-
assertExhaustive(true);
|
|
302
|
-
const CONNECTOR_INSTALLATION_LIST_PARAM_KEYS = [
|
|
303
|
-
"cursor",
|
|
304
|
-
"limit",
|
|
305
|
-
];
|
|
306
|
-
assertExhaustive(true);
|
|
307
|
-
const BILLING_INVOICE_LIST_PARAM_KEYS = [
|
|
308
|
-
"limit",
|
|
309
|
-
"cursor",
|
|
310
|
-
];
|
|
311
|
-
assertExhaustive(true);
|
|
312
|
-
const DOMAIN_LIST_PARAM_KEYS = [
|
|
313
|
-
"limit",
|
|
314
|
-
"cursor",
|
|
315
|
-
];
|
|
316
|
-
assertExhaustive(true);
|
|
317
|
-
const WEBHOOK_LIST_PARAM_KEYS = [
|
|
318
|
-
"limit",
|
|
319
|
-
"cursor",
|
|
320
|
-
];
|
|
321
|
-
assertExhaustive(true);
|
|
322
|
-
const WEBHOOK_DELIVERY_LIST_PARAM_KEYS = [
|
|
323
|
-
"limit",
|
|
324
|
-
"cursor",
|
|
325
|
-
];
|
|
326
|
-
assertExhaustive(true);
|
|
327
|
-
const API_KEY_LIST_PARAM_KEYS = [
|
|
328
|
-
"limit",
|
|
329
|
-
"cursor",
|
|
330
|
-
"status",
|
|
331
|
-
];
|
|
332
|
-
assertExhaustive(true);
|
|
333
|
-
/**
|
|
334
|
-
* Construct a {@link ValidationError} for a client-side pre-flight check.
|
|
335
|
-
* Uses `status: 0` (matching {@link NetworkError}/{@link TimeoutError}) and
|
|
336
|
-
* `code: "client_validation"` so catch-by-class still works and callers can
|
|
337
|
-
* tell the error originated inside the SDK rather than from the API.
|
|
338
|
-
*/
|
|
339
|
-
function clientValidationError(detail) {
|
|
340
|
-
return new errors_js_1.ValidationError({
|
|
341
|
-
status: 0,
|
|
342
|
-
code: errors_js_1.ERROR_CODE_CLIENT_VALIDATION,
|
|
343
|
-
title: "Invalid Argument",
|
|
344
|
-
detail,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
54
|
function validateRequestOptions(options) {
|
|
348
55
|
if (options === undefined) {
|
|
349
56
|
return;
|
|
350
57
|
}
|
|
351
58
|
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
352
|
-
throw clientValidationError(`request options: must be an object (got ${options === null ? "null" : describeShape(options)})`);
|
|
59
|
+
throw (0, client_errors_js_1.clientValidationError)(`request options: must be an object (got ${options === null ? "null" : (0, client_validation_js_1.describeShape)(options)})`);
|
|
353
60
|
}
|
|
354
|
-
requireNoUnknownFields(options, REQUEST_OPTION_KEYS, "request options");
|
|
61
|
+
(0, client_validation_js_1.requireNoUnknownFields)(options, client_input_fields_js_1.REQUEST_OPTION_KEYS, "request options");
|
|
355
62
|
const key = options.idempotencyKey;
|
|
356
63
|
if (key !== undefined) {
|
|
357
64
|
if (typeof key !== "string" || key.length === 0) {
|
|
358
|
-
throw clientValidationError("idempotencyKey: must be a non-empty string");
|
|
65
|
+
throw (0, client_errors_js_1.clientValidationError)("idempotencyKey: must be a non-empty string");
|
|
359
66
|
}
|
|
360
|
-
requireMaxLength(key, "idempotencyKey", MAX_IDEMPOTENCY_KEY);
|
|
67
|
+
(0, client_validation_js_1.requireMaxLength)(key, "idempotencyKey", client_limits_js_1.MAX_IDEMPOTENCY_KEY);
|
|
361
68
|
if (!IDEMPOTENCY_KEY_VALUE_RE.test(key)) {
|
|
362
|
-
throw clientValidationError("idempotencyKey: must contain only printable ASCII characters and must not start or end with spaces");
|
|
69
|
+
throw (0, client_errors_js_1.clientValidationError)("idempotencyKey: must contain only printable ASCII characters and must not start or end with spaces");
|
|
363
70
|
}
|
|
364
71
|
}
|
|
365
72
|
}
|
|
@@ -415,171 +122,6 @@ function fillRandomBytes(bytes) {
|
|
|
415
122
|
}
|
|
416
123
|
throw new errors_js_1.RuntimeError("globalThis.crypto.getRandomValues is required to generate Idempotency-Key");
|
|
417
124
|
}
|
|
418
|
-
/**
|
|
419
|
-
* Construct a {@link ValidationError} for the case where the API returned
|
|
420
|
-
* a response that parsed as JSON but didn't match the expected schema
|
|
421
|
-
* shape (e.g. 400 with a body that isn't a {@link BatchCreateOutput}).
|
|
422
|
-
*
|
|
423
|
-
* Distinct from {@link clientValidationError} so callers can `.code`-branch
|
|
424
|
-
* between "I passed bad input locally" (`"client_validation"`) and "the
|
|
425
|
-
* server returned a body I can't interpret" (`"unexpected_response"`).
|
|
426
|
-
* Uses `status: 0` by default because a logical shape mismatch is not tied to
|
|
427
|
-
* the HTTP classification. Callers that enforce an observed HTTP contract
|
|
428
|
-
* pass that status explicitly.
|
|
429
|
-
*
|
|
430
|
-
* Threads `request_id` through to {@link QURLError.requestId} when the
|
|
431
|
-
* server-side correlation ID is available — operators debugging
|
|
432
|
-
* "unexpected response" tickets need it on the *error* path too, not
|
|
433
|
-
* just on success/passthrough returns.
|
|
434
|
-
*/
|
|
435
|
-
function unexpectedResponseError(detail, options = {}) {
|
|
436
|
-
// Every detail passed here is an SDK-authored contract message whose dynamic
|
|
437
|
-
// inputs were validated by the caller-side shape guards. Do not pass raw
|
|
438
|
-
// server strings to this helper. Server diagnostics use boundedErrorSnippet.
|
|
439
|
-
const safeRequestId = boundedErrorIdentifier(options.requestId);
|
|
440
|
-
return new errors_js_1.ValidationError({
|
|
441
|
-
status: options.status ?? 0,
|
|
442
|
-
code: errors_js_1.ERROR_CODE_UNEXPECTED_RESPONSE,
|
|
443
|
-
title: "Unexpected Response",
|
|
444
|
-
detail,
|
|
445
|
-
request_id: safeRequestId,
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
function withErrorCause(error, cause) {
|
|
449
|
-
// Match the native Error `cause` descriptor. In particular, do not leak a
|
|
450
|
-
// transport object through Object.keys(), object spread, or JSON logging.
|
|
451
|
-
Object.defineProperty(error, "cause", {
|
|
452
|
-
value: cause,
|
|
453
|
-
writable: true,
|
|
454
|
-
configurable: true,
|
|
455
|
-
});
|
|
456
|
-
return error;
|
|
457
|
-
}
|
|
458
|
-
function isIndependentAbort(error, requestSignal) {
|
|
459
|
-
return (error instanceof Error &&
|
|
460
|
-
(error.name === "AbortError" || error.name === "TimeoutError") &&
|
|
461
|
-
!requestSignal.aborted);
|
|
462
|
-
}
|
|
463
|
-
function isSdkTimeout(error, requestSignal) {
|
|
464
|
-
return (error instanceof Error &&
|
|
465
|
-
requestSignal.aborted &&
|
|
466
|
-
(error.name === "TimeoutError" || error.name === "AbortError"));
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Normalize a server-provided problem title/detail into a single-line,
|
|
470
|
-
* UTF-8-safe snippet. Error messages must stay bounded even when a proxy or
|
|
471
|
-
* compromised upstream returns very large structured problem fields.
|
|
472
|
-
*/
|
|
473
|
-
function normalizedErrorSnippet(value) {
|
|
474
|
-
if (typeof value !== "string")
|
|
475
|
-
return undefined;
|
|
476
|
-
// Bound source work before the Unicode regular expressions allocate
|
|
477
|
-
// normalized copies. The response body cap is much larger than one retained
|
|
478
|
-
// diagnostic, so processing the full server string would waste memory.
|
|
479
|
-
const normalized = value
|
|
480
|
-
.slice(0, MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS)
|
|
481
|
-
// Strip control characters plus bidi override/isolate controls that can
|
|
482
|
-
// visually reorder diagnostics. Preserve other formatting characters
|
|
483
|
-
// such as ZWJ/ZWNJ, which are required by legitimate scripts and emoji.
|
|
484
|
-
.replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ")
|
|
485
|
-
.trim()
|
|
486
|
-
.replace(/\s+/g, " ");
|
|
487
|
-
return normalized === "" ? undefined : normalized;
|
|
488
|
-
}
|
|
489
|
-
function boundedErrorSnippet(value) {
|
|
490
|
-
const sourceTruncated = typeof value === "string" && value.length > MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS;
|
|
491
|
-
const normalized = normalizedErrorSnippet(value);
|
|
492
|
-
if (normalized === undefined)
|
|
493
|
-
return undefined;
|
|
494
|
-
const contentLimit = sourceTruncated
|
|
495
|
-
? MAX_ERROR_SNIPPET_BYTES - ERROR_SNIPPET_ELLIPSIS_BYTES
|
|
496
|
-
: MAX_ERROR_SNIPPET_BYTES;
|
|
497
|
-
// UTF-8 uses at most three bytes per UTF-16 code unit. Most diagnostics are
|
|
498
|
-
// short ASCII, so avoid an allocation only to prove they fit.
|
|
499
|
-
if (normalized.length * 3 <= contentLimit) {
|
|
500
|
-
return sourceTruncated ? `${normalized}${ERROR_SNIPPET_ELLIPSIS}` : normalized;
|
|
501
|
-
}
|
|
502
|
-
const encoded = TEXT_ENCODER.encode(normalized);
|
|
503
|
-
if (encoded.byteLength <= contentLimit) {
|
|
504
|
-
return sourceTruncated ? `${normalized}${ERROR_SNIPPET_ELLIPSIS}` : normalized;
|
|
505
|
-
}
|
|
506
|
-
let end = MAX_ERROR_SNIPPET_BYTES - ERROR_SNIPPET_ELLIPSIS_BYTES;
|
|
507
|
-
// Do not split a multi-byte UTF-8 sequence at the cap.
|
|
508
|
-
while (end > 0 && (encoded[end] & 0xc0) === 0x80)
|
|
509
|
-
end--;
|
|
510
|
-
return `${TEXT_DECODER.decode(encoded.subarray(0, end))}${ERROR_SNIPPET_ELLIPSIS}`;
|
|
511
|
-
}
|
|
512
|
-
/** Keep server identifiers useful as exact machine-readable discriminants. */
|
|
513
|
-
function boundedErrorIdentifier(value) {
|
|
514
|
-
if (typeof value !== "string" || value.length > MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS) {
|
|
515
|
-
return undefined;
|
|
516
|
-
}
|
|
517
|
-
const normalized = normalizedErrorSnippet(value);
|
|
518
|
-
if (normalized === undefined ||
|
|
519
|
-
normalized !== value ||
|
|
520
|
-
TEXT_ENCODER.encode(value).byteLength > MAX_ERROR_SNIPPET_BYTES) {
|
|
521
|
-
return undefined;
|
|
522
|
-
}
|
|
523
|
-
return value;
|
|
524
|
-
}
|
|
525
|
-
/** Collect at most the first N enumerable own string keys without a full key-array copy. */
|
|
526
|
-
function firstEnumerableOwnKeys(value, limit) {
|
|
527
|
-
const keys = [];
|
|
528
|
-
for (const key in value) {
|
|
529
|
-
if (!Object.prototype.hasOwnProperty.call(value, key))
|
|
530
|
-
continue;
|
|
531
|
-
keys.push(key);
|
|
532
|
-
if (keys.length === limit)
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
return keys;
|
|
536
|
-
}
|
|
537
|
-
/** Bound the structured per-field diagnostics exposed on ValidationError. */
|
|
538
|
-
function boundedInvalidFields(value) {
|
|
539
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
540
|
-
return undefined;
|
|
541
|
-
const entries = [];
|
|
542
|
-
const retainedKeys = new Set();
|
|
543
|
-
let retainedBytes = 0;
|
|
544
|
-
// Bound inspected entries as well as retained entries. A hostile envelope
|
|
545
|
-
// whose values all have the wrong type must not make diagnostics scan every
|
|
546
|
-
// key in the response body.
|
|
547
|
-
for (const rawKey of firstEnumerableOwnKeys(value, MAX_INVALID_FIELD_ENTRIES)) {
|
|
548
|
-
const rawValue = value[rawKey];
|
|
549
|
-
const key = boundedErrorSnippet(rawKey);
|
|
550
|
-
const fieldDetail = boundedErrorSnippet(rawValue);
|
|
551
|
-
if (key !== undefined && fieldDetail !== undefined && !retainedKeys.has(key)) {
|
|
552
|
-
const entryBytes = TEXT_ENCODER.encode(key).byteLength + TEXT_ENCODER.encode(fieldDetail).byteLength;
|
|
553
|
-
// A later, smaller entry can still fit even when this one cannot.
|
|
554
|
-
if (retainedBytes + entryBytes > MAX_DIAGNOSTIC_COLLECTION_BYTES)
|
|
555
|
-
continue;
|
|
556
|
-
retainedKeys.add(key);
|
|
557
|
-
entries.push([key, fieldDetail]);
|
|
558
|
-
retainedBytes += entryBytes;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
562
|
-
}
|
|
563
|
-
/** Bound server-controlled object keys before forwarding them to a debug sink. */
|
|
564
|
-
function boundedObjectKeys(value) {
|
|
565
|
-
// Stop before normalization so a hostile envelope cannot force work over
|
|
566
|
-
// every enumerable own key. Some early keys may normalize away; bounded
|
|
567
|
-
// diagnostics are more important here than filling every available slot.
|
|
568
|
-
const retained = [];
|
|
569
|
-
let retainedBytes = 0;
|
|
570
|
-
for (const rawKey of firstEnumerableOwnKeys(value, MAX_DEBUG_BODY_KEYS)) {
|
|
571
|
-
const key = boundedErrorSnippet(rawKey);
|
|
572
|
-
if (key === undefined)
|
|
573
|
-
continue;
|
|
574
|
-
const keyBytes = TEXT_ENCODER.encode(key).byteLength;
|
|
575
|
-
// A later, smaller key can still fit even when this one cannot.
|
|
576
|
-
if (retainedBytes + keyBytes > MAX_DIAGNOSTIC_COLLECTION_BYTES)
|
|
577
|
-
continue;
|
|
578
|
-
retained.push(key);
|
|
579
|
-
retainedBytes += keyBytes;
|
|
580
|
-
}
|
|
581
|
-
return retained;
|
|
582
|
-
}
|
|
583
125
|
async function cancelResponseBody(body) {
|
|
584
126
|
if (!body)
|
|
585
127
|
return;
|
|
@@ -590,24 +132,27 @@ async function cancelResponseBody(body) {
|
|
|
590
132
|
// Best-effort resource cleanup must not replace the deterministic SDK error.
|
|
591
133
|
}
|
|
592
134
|
}
|
|
593
|
-
function materializedResponseBodyExceedsLimit(value) {
|
|
594
|
-
if (value.length >
|
|
135
|
+
function materializedResponseBodyExceedsLimit(value, limit) {
|
|
136
|
+
if (value.length > limit)
|
|
595
137
|
return true;
|
|
596
138
|
// Encoding creates another buffer. UTF-8 uses at most three bytes per
|
|
597
139
|
// UTF-16 code unit, so shorter strings are provably safe without that copy.
|
|
598
|
-
return
|
|
599
|
-
TEXT_ENCODER.encode(value).byteLength > MAX_RESPONSE_BODY_BYTES);
|
|
140
|
+
return value.length * 3 > limit && client_errors_js_1.TEXT_ENCODER.encode(value).byteLength > limit;
|
|
600
141
|
}
|
|
601
|
-
function contentLengthExceedsLimit(response) {
|
|
142
|
+
function contentLengthExceedsLimit(response, limit) {
|
|
602
143
|
// 204/205 never carry content, and a 304 Content-Length describes the
|
|
603
144
|
// selected representation rather than a response body. Do not reject those
|
|
604
145
|
// statuses for metadata about bytes that fetch will not deliver.
|
|
605
146
|
if (response.status === 204 || response.status === 205 || response.status === 304)
|
|
606
147
|
return false;
|
|
148
|
+
// Encoded Content-Length describes wire bytes; fetch exposes decoded bytes.
|
|
149
|
+
const encoding = response.headers.get("content-encoding");
|
|
150
|
+
if (encoding && encoding.trim().toLowerCase() !== "identity")
|
|
151
|
+
return false;
|
|
607
152
|
const value = response.headers.get("content-length");
|
|
608
153
|
if (value === null || !/^\d+$/.test(value.trim()))
|
|
609
154
|
return false;
|
|
610
|
-
return Number(value) >
|
|
155
|
+
return Number(value) > limit;
|
|
611
156
|
}
|
|
612
157
|
/** Detect a followed redirect from fetch implementations that expose only the final URL. */
|
|
613
158
|
function responseUrlState(responseUrl, requestUrl) {
|
|
@@ -635,16 +180,16 @@ function responseUrlState(responseUrl, requestUrl) {
|
|
|
635
180
|
* optimization only; streaming byte accounting remains authoritative because
|
|
636
181
|
* that header may be absent or false.
|
|
637
182
|
*/
|
|
638
|
-
async function readBoundedResponseBody(response) {
|
|
183
|
+
async function readBoundedResponseBody(response, limit) {
|
|
639
184
|
// `headers.get` is part of the documented injected-fetch contract. Detect a
|
|
640
185
|
// malformed local shim before it can be mistaken for a retryable body-stream
|
|
641
186
|
// failure and replay an otherwise deterministic GET programming error.
|
|
642
187
|
if (typeof response.headers?.get !== "function") {
|
|
643
188
|
throw new ResponseBodyMaterializationError("Response-like fetch must provide headers.get(name)");
|
|
644
189
|
}
|
|
645
|
-
if (contentLengthExceedsLimit(response)) {
|
|
190
|
+
if (contentLengthExceedsLimit(response, limit)) {
|
|
646
191
|
await cancelResponseBody(response.body);
|
|
647
|
-
throw new ResponseBodyTooLargeError();
|
|
192
|
+
throw new ResponseBodyTooLargeError(limit);
|
|
648
193
|
}
|
|
649
194
|
const body = response.body;
|
|
650
195
|
if (body === null)
|
|
@@ -666,8 +211,8 @@ async function readBoundedResponseBody(response) {
|
|
|
666
211
|
}
|
|
667
212
|
}
|
|
668
213
|
const text = typeof materializedText === "string" ? materializedText : "";
|
|
669
|
-
if (materializedResponseBodyExceedsLimit(text)) {
|
|
670
|
-
throw new ResponseBodyTooLargeError();
|
|
214
|
+
if (materializedResponseBodyExceedsLimit(text, limit)) {
|
|
215
|
+
throw new ResponseBodyTooLargeError(limit);
|
|
671
216
|
}
|
|
672
217
|
if (text !== "" ||
|
|
673
218
|
response.status === 204 ||
|
|
@@ -695,8 +240,8 @@ async function readBoundedResponseBody(response) {
|
|
|
695
240
|
}
|
|
696
241
|
if (serialized === undefined)
|
|
697
242
|
return "";
|
|
698
|
-
if (materializedResponseBodyExceedsLimit(serialized)) {
|
|
699
|
-
throw new ResponseBodyTooLargeError();
|
|
243
|
+
if (materializedResponseBodyExceedsLimit(serialized, limit)) {
|
|
244
|
+
throw new ResponseBodyTooLargeError(limit);
|
|
700
245
|
}
|
|
701
246
|
return serialized;
|
|
702
247
|
}
|
|
@@ -712,14 +257,14 @@ async function readBoundedResponseBody(response) {
|
|
|
712
257
|
throw new ResponseBodyMaterializationError("Response-like fetch body stream must yield Uint8Array chunks");
|
|
713
258
|
}
|
|
714
259
|
const nextTotal = total + value.byteLength;
|
|
715
|
-
if (nextTotal >
|
|
260
|
+
if (nextTotal > limit) {
|
|
716
261
|
try {
|
|
717
262
|
await reader.cancel();
|
|
718
263
|
}
|
|
719
264
|
catch {
|
|
720
265
|
// Preserve the body-limit failure even if stream cancellation fails.
|
|
721
266
|
}
|
|
722
|
-
throw new ResponseBodyTooLargeError();
|
|
267
|
+
throw new ResponseBodyTooLargeError(limit);
|
|
723
268
|
}
|
|
724
269
|
chunks.push(value);
|
|
725
270
|
total = nextTotal;
|
|
@@ -741,139 +286,12 @@ async function readBoundedResponseBody(response) {
|
|
|
741
286
|
}
|
|
742
287
|
}
|
|
743
288
|
try {
|
|
744
|
-
return TEXT_DECODER.decode(bytes);
|
|
289
|
+
return client_errors_js_1.TEXT_DECODER.decode(bytes);
|
|
745
290
|
}
|
|
746
291
|
catch {
|
|
747
292
|
throw new InvalidResponseEncodingError();
|
|
748
293
|
}
|
|
749
294
|
}
|
|
750
|
-
// ---- Spec-derived validation helpers ------------------------------------
|
|
751
|
-
// These mirror constraints documented on each request schema in
|
|
752
|
-
// `openapi.yaml` so obvious mistakes fail fast instead of round-tripping
|
|
753
|
-
// to the API and coming back as a generic 400.
|
|
754
|
-
const MAX_TARGET_URL = 2048;
|
|
755
|
-
const MAX_LABEL = 500;
|
|
756
|
-
const MAX_BATCH_ITEMS = 100;
|
|
757
|
-
const MAX_DESCRIPTION = 500;
|
|
758
|
-
const MAX_CUSTOM_DOMAIN = 253;
|
|
759
|
-
const MAX_MAX_SESSIONS = 1000;
|
|
760
|
-
const MAX_API_KEY_NAME = 100;
|
|
761
|
-
const API_KEY_REQUEST_SCOPE_VALUES = [
|
|
762
|
-
"qurl:read",
|
|
763
|
-
"qurl:write",
|
|
764
|
-
"qurl:resolve",
|
|
765
|
-
"qurl:agent",
|
|
766
|
-
];
|
|
767
|
-
assertExhaustive(true);
|
|
768
|
-
const API_KEY_REQUEST_SCOPES = new Set(API_KEY_REQUEST_SCOPE_VALUES);
|
|
769
|
-
/** Narrow a response scope before reusing it in a create/update request. */
|
|
770
|
-
function isApiKeyRequestScope(scope) {
|
|
771
|
-
return API_KEY_REQUEST_SCOPES.has(scope);
|
|
772
|
-
}
|
|
773
|
-
const CREDENTIAL_TARGET_VALUES = [
|
|
774
|
-
"agent",
|
|
775
|
-
"connector",
|
|
776
|
-
];
|
|
777
|
-
assertExhaustive(true);
|
|
778
|
-
const CREDENTIAL_TARGETS = new Set(CREDENTIAL_TARGET_VALUES);
|
|
779
|
-
const CREDENTIAL_CLAIM_TYPE_VALUES = [
|
|
780
|
-
"connector",
|
|
781
|
-
];
|
|
782
|
-
assertExhaustive(true);
|
|
783
|
-
const CREDENTIAL_CLAIM_TYPES = new Set(CREDENTIAL_CLAIM_TYPE_VALUES);
|
|
784
|
-
// Mirrors CreateApiKeyRequest.expires_in in the service OpenAPI. Composite
|
|
785
|
-
// durations are deliberately invalid on this endpoint. The 24h cap permits
|
|
786
|
-
// 1d, but no positive whole-week duration.
|
|
787
|
-
const CREDENTIAL_DURATION_PATTERN = /^([0-9]+)([smhdw])$/;
|
|
788
|
-
const CREDENTIAL_DURATION_MULTIPLIERS = {
|
|
789
|
-
s: 1n,
|
|
790
|
-
m: 60n,
|
|
791
|
-
h: 3600n,
|
|
792
|
-
d: 86400n,
|
|
793
|
-
w: 604800n,
|
|
794
|
-
};
|
|
795
|
-
const MAX_ENROLLMENT_TOKEN_TTL_SECONDS = 86400n;
|
|
796
|
-
const MAX_ALIAS = 64;
|
|
797
|
-
const MAX_SLUG = 64;
|
|
798
|
-
const MAX_TARGET_PATH = 2048;
|
|
799
|
-
const MAX_TAGS = 10;
|
|
800
|
-
const MAX_TAG_LENGTH = 50;
|
|
801
|
-
const MAX_AUTO_PAGINATION_PAGES = 10_000;
|
|
802
|
-
const MIN_DELEGATED_BATCH_IDEMPOTENCY_KEY = 32;
|
|
803
|
-
const MAX_DELEGATED_MINT_CAPABILITY = 8192;
|
|
804
|
-
const MAX_DELEGATED_ETAG = 96;
|
|
805
|
-
const MAX_DELEGATED_BATCH_ITEMS = 100;
|
|
806
|
-
const DELEGATED_BATCH_ID_PATTERN = /^dqb_[A-Za-z0-9_-]{22}$/;
|
|
807
|
-
const DELEGATED_QURL_ID_PATTERN = /^q_[0-9a-f]{11}$/;
|
|
808
|
-
const STRONG_ETAG_PATTERN = /^"[\x21\x23-\x7e]+"$/;
|
|
809
|
-
const UTC_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
810
|
-
// Keep these identity contracts aligned with qurl-go and the public API.
|
|
811
|
-
const CONNECTOR_SLUG_PATTERN = /^[a-z][a-z0-9-]{1,62}[a-z0-9]$/;
|
|
812
|
-
// Current service schemas intentionally share this grammar, but keep the
|
|
813
|
-
// mutable alias contract separate from the immutable lookup identity.
|
|
814
|
-
const CONNECTOR_ALIAS_PATTERN = /^[a-z][a-z0-9-]{1,62}[a-z0-9]$/;
|
|
815
|
-
const CONNECTOR_RESOURCE_ID_PATTERN = /^[A-Za-z0-9_-]{122}$/;
|
|
816
|
-
const CONNECTOR_RESOURCE_ID_DER_PREFIX = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE";
|
|
817
|
-
const CONNECTOR_ROUTING_ID_PATTERN = /^c-[a-z2-7]{51}[aq]$/;
|
|
818
|
-
// CreateQurlRequest.target_url pattern is loose (just a URI) but
|
|
819
|
-
// UpdateQurlRequest.tags pattern is specific — enforce it here.
|
|
820
|
-
const TAG_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9 _-]*$/;
|
|
821
|
-
function requireMaxLength(value, field, max) {
|
|
822
|
-
if (value === undefined)
|
|
823
|
-
return;
|
|
824
|
-
// Untyped-JS safety: a non-string `value` would silently pass since
|
|
825
|
-
// `(42).length === undefined > max` is `false` and the bad value would
|
|
826
|
-
// sail through to the wire. Match the typeof guard pattern used by
|
|
827
|
-
// requireValidTags / requireValidTargetUrl.
|
|
828
|
-
if (typeof value !== "string") {
|
|
829
|
-
throw clientValidationError(`${field}: must be a string (got ${value === null ? "null" : typeof value})`);
|
|
830
|
-
}
|
|
831
|
-
if (value.length > max) {
|
|
832
|
-
throw clientValidationError(`${field}: must be ${max} characters or fewer (got ${value.length})`);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
function requireMaxUtf8Bytes(value, field, max) {
|
|
836
|
-
if (value === undefined)
|
|
837
|
-
return;
|
|
838
|
-
if (typeof value !== "string") {
|
|
839
|
-
throw clientValidationError(`${field}: must be a string (got ${value === null ? "null" : typeof value})`);
|
|
840
|
-
}
|
|
841
|
-
const bytes = TEXT_ENCODER.encode(value).byteLength;
|
|
842
|
-
if (bytes > max) {
|
|
843
|
-
throw clientValidationError(`${field}: must be ${max} UTF-8 bytes or fewer (got ${bytes})`);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
function requireNonEmptyIfPresent(value, field) {
|
|
847
|
-
// Reject `""` so uninitialized form state doesn't reach the wire.
|
|
848
|
-
if (value === "") {
|
|
849
|
-
throw clientValidationError(`${field}: must not be an empty string`);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
function requireMaxSessionsInRange(value, field = "max_sessions") {
|
|
853
|
-
if (value === undefined)
|
|
854
|
-
return;
|
|
855
|
-
if (!Number.isInteger(value) || value < 0 || value > MAX_MAX_SESSIONS) {
|
|
856
|
-
throw clientValidationError(`${field}: must be an integer between 0 and ${MAX_MAX_SESSIONS} (got ${value})`);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
function requireBooleanIfPresent(value, field) {
|
|
860
|
-
if (value === undefined)
|
|
861
|
-
return;
|
|
862
|
-
if (typeof value !== "boolean") {
|
|
863
|
-
throw clientValidationError(`${field}: must be a boolean (got ${describeShape(value)})`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
function requireListLimitInRange(value, methodName) {
|
|
867
|
-
if (value === undefined || value === null)
|
|
868
|
-
return;
|
|
869
|
-
// All current paginated OpenAPI query params share 1..100. If a future
|
|
870
|
-
// endpoint diverges, add a per-endpoint bound rather than changing this
|
|
871
|
-
// shared guard silently.
|
|
872
|
-
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 100) {
|
|
873
|
-
const rendered = typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
874
|
-
throw clientValidationError(`${methodName}: limit: must be an integer between 1 and 100 (got ${rendered})`);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
295
|
function appendQuery(path, input, keys, methodName) {
|
|
878
296
|
validateQueryInput(input, keys, methodName);
|
|
879
297
|
const params = new URLSearchParams();
|
|
@@ -887,9 +305,9 @@ function appendQuery(path, input, keys, methodName) {
|
|
|
887
305
|
return query ? `${path}?${query}` : path;
|
|
888
306
|
}
|
|
889
307
|
function validateQueryInput(input, keys, methodName) {
|
|
890
|
-
requireNoUnknownFields(input, keys, methodName);
|
|
308
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, keys, methodName);
|
|
891
309
|
if (keys.includes("limit")) {
|
|
892
|
-
requireListLimitInRange(input.limit, methodName);
|
|
310
|
+
(0, client_validation_js_1.requireListLimitInRange)(input.limit, methodName);
|
|
893
311
|
}
|
|
894
312
|
for (const key of keys) {
|
|
895
313
|
const value = input[key];
|
|
@@ -898,7 +316,7 @@ function validateQueryInput(input, keys, methodName) {
|
|
|
898
316
|
if (key === "limit")
|
|
899
317
|
continue;
|
|
900
318
|
if (typeof value !== "string") {
|
|
901
|
-
throw clientValidationError(`${methodName}: ${key}: must be a string (got ${describeShape(value)})`);
|
|
319
|
+
throw (0, client_errors_js_1.clientValidationError)(`${methodName}: ${key}: must be a string (got ${(0, client_validation_js_1.describeShape)(value)})`);
|
|
902
320
|
}
|
|
903
321
|
}
|
|
904
322
|
}
|
|
@@ -909,1330 +327,17 @@ function validateListAllInput(input, keys, methodName) {
|
|
|
909
327
|
// each request revalidates the threaded cursor/filter set before dispatch.
|
|
910
328
|
validateQueryInput(input, keys, methodName);
|
|
911
329
|
}
|
|
330
|
+
function withEnvelopeMetadata(value, meta) {
|
|
331
|
+
return (0, response_metadata_js_1.withResponseMetadata)(value, (0, client_errors_js_1.boundedErrorIdentifier)(meta?.request_id));
|
|
332
|
+
}
|
|
912
333
|
function pageFromMeta(out, meta) {
|
|
913
|
-
return {
|
|
334
|
+
return withEnvelopeMetadata({
|
|
914
335
|
...out,
|
|
915
336
|
next_cursor: meta?.next_cursor,
|
|
916
337
|
has_more: meta?.has_more ?? Boolean(meta?.next_cursor),
|
|
917
338
|
request_id: meta?.request_id,
|
|
918
339
|
page_size: meta?.page_size,
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
// Best-effort defence-in-depth against today's access-token grammar. The
|
|
922
|
-
// service remains authoritative because resource IDs and credentials are both
|
|
923
|
-
// opaque and future credential formats may not retain the `at_` prefix. The
|
|
924
|
-
// delimiter requirement cannot collide with current base64url public keys,
|
|
925
|
-
// standard-base64 keys, or base32 CRIDs.
|
|
926
|
-
const PATH_EMBEDDED_ACCESS_TOKEN_RE = /[?&#=:\s]at_/i;
|
|
927
|
-
const PATH_PREFIXED_ACCESS_TOKEN_RE = /\/at_/i;
|
|
928
|
-
const QURL_LINK_URL_RE = /^(?:https?:\/\/)?(?:[a-z0-9-]+\.)*qurl\.link(?::\d{1,5})?(?:\/|[?#])/i;
|
|
929
|
-
// ponytail: today's 22-character token suffix only improves URL-paste diagnostics;
|
|
930
|
-
// other lengths are still rejected as URLs without echoing the input.
|
|
931
|
-
const URL_EMBEDDED_ACCESS_TOKEN_RE = /[?&#=:/]at_[a-z0-9_-]{22}(?:$|[?&#:/])/i;
|
|
932
|
-
// A host without a slash can be a legitimate domain identifier. Require the
|
|
933
|
-
// path boundary so shared validation does not break the domain namespace.
|
|
934
|
-
const PATH_SCHEMELESS_URL_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)+(?::\d{1,5})?\//i;
|
|
935
|
-
const QURL_DISPLAY_ID_PREFIX_RE = /^q_/i;
|
|
936
|
-
const MAX_PATH_ID_DECODE_PASSES = 3;
|
|
937
|
-
const MAX_PATH_ID_LENGTH = 4096;
|
|
938
|
-
function isUrlLikePathId(value) {
|
|
939
|
-
return /^https?:\/\//i.test(value) || PATH_SCHEMELESS_URL_RE.test(value);
|
|
940
|
-
}
|
|
941
|
-
const REVOKE_INDIVIDUAL_QURL_RECOVERY = "revoke individual tokens with revokeResourceQurl(crid, qurlId)";
|
|
942
|
-
const DELETE_RESOURCE_RECOVERY = "pass a CRID returned by the API; to revoke one qURL, call revokeResourceQurl(crid, qurlId)";
|
|
943
|
-
const RESOURCE_OR_QURL_ID_PATH_OPTIONS = {
|
|
944
|
-
requireCridUnlessQurlDisplayId: true,
|
|
945
|
-
};
|
|
946
|
-
const QURL_ID_PATH_OPTIONS = {
|
|
947
|
-
rejectBareAccessToken: true,
|
|
948
|
-
accessTokenRecovery: "pass the qURL display ID, not its access token",
|
|
949
|
-
};
|
|
950
|
-
/**
|
|
951
|
-
* Validates that a path-parameter argument is a non-empty string. Some callers
|
|
952
|
-
* pass resource/qURL display IDs, while newer endpoints also pass domains,
|
|
953
|
-
* session IDs, webhook IDs, and API-key IDs, so this intentionally enforces
|
|
954
|
-
* only basic shape plus transport-safety rules and leaves endpoint-specific
|
|
955
|
-
* identifier grammar to the service.
|
|
956
|
-
*/
|
|
957
|
-
function validatePathId(id, method, field = "id", options = {}) {
|
|
958
|
-
if (options.requireCridUnlessQurlDisplayId && typeof id === "string" && !id.startsWith("q_")) {
|
|
959
|
-
requireResourceCrid(id, method, field);
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
// `.trim()` catches whitespace-only and padded IDs before they round-trip as
|
|
963
|
-
// `%20...%20` paths that the server can only reject with a less useful 404.
|
|
964
|
-
// The pre-flight error is more actionable than the 404.
|
|
965
|
-
if (typeof id !== "string") {
|
|
966
|
-
const observedType = id === null ? "null" : typeof id;
|
|
967
|
-
throw clientValidationError(`${method}: ${field} is required (got ${observedType})`);
|
|
968
|
-
}
|
|
969
|
-
if (id.trim() === "") {
|
|
970
|
-
throw clientValidationError(`${method}: ${field} is required`);
|
|
971
|
-
}
|
|
972
|
-
if (id.trim() !== id) {
|
|
973
|
-
throw clientValidationError(`${method}: ${field} must not include leading or trailing whitespace`);
|
|
974
|
-
}
|
|
975
|
-
if (id.length > MAX_PATH_ID_LENGTH) {
|
|
976
|
-
throw clientValidationError(`${method}: ${field} must be ${MAX_PATH_ID_LENGTH} characters or fewer`);
|
|
977
|
-
}
|
|
978
|
-
const decodedIds = decodedPathIdForms(id);
|
|
979
|
-
// URL resolvers and intermediaries can normalize raw or pre-encoded dot
|
|
980
|
-
// segments before routing. Reject every decoded form we inspected so an
|
|
981
|
-
// item operation cannot be retargeted to a collection endpoint. Embedded
|
|
982
|
-
// `a/../b` remains an opaque single segment because the later URL builder
|
|
983
|
-
// percent-encodes its slash.
|
|
984
|
-
if (decodedIds.some((value) => value === "." || value === "..")) {
|
|
985
|
-
throw clientValidationError(`${method}: ${field} is an invalid URL path segment`);
|
|
986
|
-
}
|
|
987
|
-
// `/` is deliberately outside the general delimiter class so a URL path
|
|
988
|
-
// such as `/at_a_glance` is not misread as a credential. For non-URL input,
|
|
989
|
-
// `/at_` remains a strong pasted-path signal. Fragment, query, header, and
|
|
990
|
-
// pasted `Bearer at_...` / `token:at_...` forms are also strong signals.
|
|
991
|
-
// Bare `at_...` values are rejected only for resource/qURL identifiers:
|
|
992
|
-
// those current formats have fixed non-colliding prefixes, while domain,
|
|
993
|
-
// webhook, session, and API-key IDs are deliberately opaque/open contracts.
|
|
994
|
-
// Full URLs and delimiter/path-prefixed credentials remain rejected in
|
|
995
|
-
// every namespace.
|
|
996
|
-
const containsUrl = decodedIds.some(isUrlLikePathId);
|
|
997
|
-
const containsAccessToken = decodedIds.some((value) => {
|
|
998
|
-
const valueIsUrl = isUrlLikePathId(value);
|
|
999
|
-
return ((QURL_LINK_URL_RE.test(value) &&
|
|
1000
|
-
(PATH_EMBEDDED_ACCESS_TOKEN_RE.test(value) || PATH_PREFIXED_ACCESS_TOKEN_RE.test(value))) ||
|
|
1001
|
-
(valueIsUrl
|
|
1002
|
-
? URL_EMBEDDED_ACCESS_TOKEN_RE.test(value)
|
|
1003
|
-
: PATH_EMBEDDED_ACCESS_TOKEN_RE.test(value) || PATH_PREFIXED_ACCESS_TOKEN_RE.test(value)) ||
|
|
1004
|
-
(options.rejectBareAccessToken === true && value.slice(0, 3).toLowerCase() === "at_"));
|
|
1005
|
-
});
|
|
1006
|
-
if (containsAccessToken) {
|
|
1007
|
-
const recovery = options.accessTokenRecovery ?? "pass the identifier returned by the API";
|
|
1008
|
-
throw clientValidationError(`${method}: ${field} must not contain an access token; ${recovery}`);
|
|
1009
|
-
}
|
|
1010
|
-
// A full target/qURL is a common argument mix-up, but it is distinct from
|
|
1011
|
-
// passing an access-token credential. Diagnose it accurately without
|
|
1012
|
-
// echoing the URL, which may itself contain sensitive query parameters.
|
|
1013
|
-
if (containsUrl) {
|
|
1014
|
-
throw clientValidationError(`${method}: ${field} must be an identifier, not a URL`);
|
|
1015
|
-
}
|
|
1016
|
-
if (options.rejectQurlDisplayId === true &&
|
|
1017
|
-
decodedIds.some((value) => QURL_DISPLAY_ID_PREFIX_RE.test(value))) {
|
|
1018
|
-
throw clientValidationError(`${method}: ${field} must not be a qURL display ID; ${REVOKE_INDIVIDUAL_QURL_RECOVERY}`);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
function decodedPathIdForms(id) {
|
|
1022
|
-
// Probe repeatedly encoded input too. Callers sometimes pre-encode a copied
|
|
1023
|
-
// link before passing it through a URL builder that encodes it again. Three
|
|
1024
|
-
// passes cover the common accidental cases without allowing hostile input
|
|
1025
|
-
// to create unbounded work. More deeply encoded or malformed-percent input
|
|
1026
|
-
// deliberately fails open to the authoritative service after safe segment
|
|
1027
|
-
// encoding; this heuristic is not an identifier parser.
|
|
1028
|
-
const decodedIds = [id];
|
|
1029
|
-
let decodedId = id;
|
|
1030
|
-
for (let pass = 0; pass < MAX_PATH_ID_DECODE_PASSES; pass += 1) {
|
|
1031
|
-
try {
|
|
1032
|
-
const next = decodeURIComponent(decodedId);
|
|
1033
|
-
if (next === decodedId)
|
|
1034
|
-
break;
|
|
1035
|
-
decodedIds.push(next);
|
|
1036
|
-
decodedId = next;
|
|
1037
|
-
}
|
|
1038
|
-
catch {
|
|
1039
|
-
// Malformed percent escapes remain opaque and are left to the service.
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
return decodedIds;
|
|
1044
|
-
}
|
|
1045
|
-
function requireConnectorSlug(slug, method) {
|
|
1046
|
-
if (typeof slug !== "string" || !CONNECTOR_SLUG_PATTERN.test(slug)) {
|
|
1047
|
-
throw clientValidationError(`${method}: slug must be 3-64 lowercase alphanumeric or hyphen characters, start with a letter, and end alphanumeric`);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
function classifyConnectorMutationFailure(operation, error) {
|
|
1051
|
-
if (!(error instanceof errors_js_1.QURLError)) {
|
|
1052
|
-
throw new errors_js_1.ConnectorResourceOutcomeUnknownError(new errors_js_1.RuntimeError("Unexpected failure after Connector mutation dispatch", { cause: error }));
|
|
1053
|
-
}
|
|
1054
|
-
// Most authoritative 4xx responses prove that the mutation was rejected.
|
|
1055
|
-
// bootstrap_key_consumed is the exception: the service returns it from the
|
|
1056
|
-
// consume phase after the resource may have been bound or found.
|
|
1057
|
-
// Callers perform argument/runtime preflight outside their mutation try
|
|
1058
|
-
// blocks; every other surfaced failure therefore follows dispatch or a
|
|
1059
|
-
// nominal success whose resource contract could not be consumed, so callers
|
|
1060
|
-
// must reconcile before retrying. Ensure's exact 201 + valid resource but
|
|
1061
|
-
// missing found_existing is handled after this classifier: the row proves
|
|
1062
|
-
// the selected resource, while only its required metadata is unavailable.
|
|
1063
|
-
if (operation === "ensure" && error.status === 409 && error.code === "bootstrap_key_consumed") {
|
|
1064
|
-
throw new errors_js_1.ConnectorResourceOutcomeUnknownError(error);
|
|
1065
|
-
}
|
|
1066
|
-
if (error.status >= 400 && error.status < 500) {
|
|
1067
|
-
throw error;
|
|
1068
|
-
}
|
|
1069
|
-
throw new errors_js_1.ConnectorResourceOutcomeUnknownError(error);
|
|
1070
|
-
}
|
|
1071
|
-
function decodeCanonicalBase64Url(value) {
|
|
1072
|
-
if (!CONNECTOR_RESOURCE_ID_PATTERN.test(value) ||
|
|
1073
|
-
!value.startsWith(CONNECTOR_RESOURCE_ID_DER_PREFIX)) {
|
|
1074
|
-
return undefined;
|
|
1075
|
-
}
|
|
1076
|
-
try {
|
|
1077
|
-
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "==";
|
|
1078
|
-
const binary = atob(padded);
|
|
1079
|
-
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
1080
|
-
const canonical = btoa(String.fromCharCode(...bytes))
|
|
1081
|
-
.replace(/\+/g, "-")
|
|
1082
|
-
.replace(/\//g, "_")
|
|
1083
|
-
.replace(/=+$/, "");
|
|
1084
|
-
return canonical === value ? bytes : undefined;
|
|
1085
|
-
}
|
|
1086
|
-
catch {
|
|
1087
|
-
return undefined;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
function requireConnectorSubtleCrypto(method) {
|
|
1091
|
-
if (!globalThis.crypto?.subtle) {
|
|
1092
|
-
throw new errors_js_1.RuntimeError(`${method}: requires the Web Crypto SubtleCrypto API`);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
function requireResourceCrid(crid, method, field = "id") {
|
|
1096
|
-
if ((0, crid_js_1.parseCrid)(crid))
|
|
1097
|
-
return;
|
|
1098
|
-
// Keep actionable paste errors without inspecting valid CRIDs twice.
|
|
1099
|
-
validatePathId(crid, method, field, {
|
|
1100
|
-
rejectBareAccessToken: true,
|
|
1101
|
-
rejectQurlDisplayId: true,
|
|
1102
|
-
accessTokenRecovery: method === "delete" || method === "deleteResource"
|
|
1103
|
-
? DELETE_RESOURCE_RECOVERY
|
|
1104
|
-
: "pass a CRID returned by the API",
|
|
1105
|
-
});
|
|
1106
|
-
throw clientValidationError(`${method}: requires a valid CRID`);
|
|
1107
|
-
}
|
|
1108
|
-
async function importsAsConnectorPublicKey(der) {
|
|
1109
|
-
try {
|
|
1110
|
-
const keyData = new ArrayBuffer(der.byteLength);
|
|
1111
|
-
new Uint8Array(keyData).set(der);
|
|
1112
|
-
await globalThis.crypto.subtle.importKey("spki", keyData, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
|
|
1113
|
-
return true;
|
|
1114
|
-
}
|
|
1115
|
-
catch {
|
|
1116
|
-
return false;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
function containsControlCharacter(value) {
|
|
1120
|
-
return Array.from(value).some((character) => {
|
|
1121
|
-
const codePoint = character.codePointAt(0);
|
|
1122
|
-
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
async function verifyResourceIdentity(resource, method, expectedCrid) {
|
|
1126
|
-
if (!resource || typeof resource.resource_id !== "string") {
|
|
1127
|
-
throw unexpectedResponseError(`${method}: response has missing or invalid resource_id`);
|
|
1128
|
-
}
|
|
1129
|
-
const resourceKey = decodeCanonicalBase64Url(resource.resource_id);
|
|
1130
|
-
requireConnectorSubtleCrypto(method);
|
|
1131
|
-
if (!resourceKey || !(await importsAsConnectorPublicKey(resourceKey))) {
|
|
1132
|
-
throw unexpectedResponseError(`${method}: response has missing or invalid resource_id`);
|
|
1133
|
-
}
|
|
1134
|
-
if (expectedCrid !== undefined && resource.crid !== expectedCrid) {
|
|
1135
|
-
throw unexpectedResponseError(`${method}: response crid does not match the request`);
|
|
1136
|
-
}
|
|
1137
|
-
if (!(await (0, crid_js_1.cridKeyMatches)(resource.crid, resourceKey))) {
|
|
1138
|
-
throw unexpectedResponseError(`${method}: response has missing, invalid, or public-key-mismatched crid`);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
async function parseConnectorResource(client, value, method, expectation = {}) {
|
|
1142
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1143
|
-
throw unexpectedResponseError(`${method}: response is missing connector resource data`);
|
|
1144
|
-
}
|
|
1145
|
-
const resource = value;
|
|
1146
|
-
await verifyResourceIdentity(resource, method, expectation.crid);
|
|
1147
|
-
if (typeof resource.connector_routing_id !== "string" ||
|
|
1148
|
-
!CONNECTOR_ROUTING_ID_PATTERN.test(resource.connector_routing_id)) {
|
|
1149
|
-
throw unexpectedResponseError(`${method}: response has missing or invalid connector_routing_id`);
|
|
1150
|
-
}
|
|
1151
|
-
if (typeof resource.knock_resource_id !== "string" ||
|
|
1152
|
-
resource.knock_resource_id.trim() === "" ||
|
|
1153
|
-
resource.knock_resource_id.trim() !== resource.knock_resource_id ||
|
|
1154
|
-
containsControlCharacter(resource.knock_resource_id)) {
|
|
1155
|
-
throw unexpectedResponseError(`${method}: response has missing or invalid knock_resource_id`);
|
|
1156
|
-
}
|
|
1157
|
-
if (resource.resource_id === resource.knock_resource_id ||
|
|
1158
|
-
resource.connector_routing_id === resource.knock_resource_id) {
|
|
1159
|
-
throw unexpectedResponseError(`${method}: response cross-wires resource identity, routing, and admission values`);
|
|
1160
|
-
}
|
|
1161
|
-
if (resource.type !== "tunnel") {
|
|
1162
|
-
throw unexpectedResponseError(`${method}: response resource type is not tunnel`);
|
|
1163
|
-
}
|
|
1164
|
-
if (typeof resource.slug !== "string" || !CONNECTOR_SLUG_PATTERN.test(resource.slug)) {
|
|
1165
|
-
throw unexpectedResponseError(`${method}: response has missing or invalid slug`);
|
|
1166
|
-
}
|
|
1167
|
-
if (expectation.slug !== undefined && resource.slug !== expectation.slug) {
|
|
1168
|
-
throw unexpectedResponseError(`${method}: response slug does not match the request`);
|
|
1169
|
-
}
|
|
1170
|
-
if (resource.alias !== undefined &&
|
|
1171
|
-
resource.alias !== null &&
|
|
1172
|
-
(typeof resource.alias !== "string" || !CONNECTOR_ALIAS_PATTERN.test(resource.alias))) {
|
|
1173
|
-
// The public API intentionally gives mutable aliases and immutable slugs the
|
|
1174
|
-
// same canonical wire grammar (domain aliasPattern/slugPattern). Keeping
|
|
1175
|
-
// response validation aligned avoids accepting a row the service itself
|
|
1176
|
-
// could not create or update.
|
|
1177
|
-
throw unexpectedResponseError(`${method}: response has invalid alias`);
|
|
1178
|
-
}
|
|
1179
|
-
if (resource.status === "revoked") {
|
|
1180
|
-
if (expectation.revokedIsLifecycleError !== true) {
|
|
1181
|
-
throw unexpectedResponseError(`${method}: active-only operation returned a revoked connector resource`);
|
|
1182
|
-
}
|
|
1183
|
-
throw new errors_js_1.QURLError({
|
|
1184
|
-
status: 0,
|
|
1185
|
-
code: errors_js_1.ERROR_CODE_CONNECTOR_RESOURCE_REVOKED,
|
|
1186
|
-
title: "Connector Resource Revoked",
|
|
1187
|
-
detail: `${method}: qURL Connector resource is revoked`,
|
|
1188
|
-
});
|
|
1189
|
-
}
|
|
1190
|
-
if (resource.status !== "active") {
|
|
1191
|
-
throw unexpectedResponseError(`${method}: response has invalid resource status`);
|
|
1192
|
-
}
|
|
1193
|
-
return new ConnectorResource(client, resource, CONNECTOR_RESOURCE_CONSTRUCTOR_TOKEN);
|
|
1194
|
-
}
|
|
1195
|
-
function requireValidTags(tags) {
|
|
1196
|
-
// null tolerance for untyped-JS callers — treated as "don't touch."
|
|
1197
|
-
// Duplicates pass through to the server (authoritative on dedupe policy).
|
|
1198
|
-
if (tags === undefined || tags === null)
|
|
1199
|
-
return;
|
|
1200
|
-
if (!Array.isArray(tags)) {
|
|
1201
|
-
throw clientValidationError(`tags: must be an array of strings (got ${typeof tags})`);
|
|
1202
|
-
}
|
|
1203
|
-
if (tags.length > MAX_TAGS) {
|
|
1204
|
-
throw clientValidationError(`tags: max ${MAX_TAGS} items allowed (got ${tags.length})`);
|
|
1205
|
-
}
|
|
1206
|
-
const errors = [];
|
|
1207
|
-
for (let i = 0; i < tags.length; i++) {
|
|
1208
|
-
const tag = tags[i];
|
|
1209
|
-
// Per-element type guard for untyped-JS callers. Without this, a
|
|
1210
|
-
// non-string element would silently pass (`.length` on a number is
|
|
1211
|
-
// `undefined`; `TAG_PATTERN.test(42)` coerces to `"42"`) or TypeError
|
|
1212
|
-
// (`null.length`).
|
|
1213
|
-
if (typeof tag !== "string") {
|
|
1214
|
-
errors.push(`tags[${i}]: must be a string (got ${tag === null ? "null" : typeof tag})`);
|
|
1215
|
-
continue;
|
|
1216
|
-
}
|
|
1217
|
-
if (tag.length < 1 || tag.length > MAX_TAG_LENGTH) {
|
|
1218
|
-
errors.push(`tags[${i}]: must be 1-${MAX_TAG_LENGTH} characters (got ${tag.length})`);
|
|
1219
|
-
continue;
|
|
1220
|
-
}
|
|
1221
|
-
if (!TAG_PATTERN.test(tag)) {
|
|
1222
|
-
errors.push(`tags[${i}]: must start with an alphanumeric and contain only letters, numbers, spaces, underscores, or hyphens`);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
if (errors.length > 0) {
|
|
1226
|
-
throw clientValidationError(errors.join("; "));
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
function describeShape(value) {
|
|
1230
|
-
if (value === null)
|
|
1231
|
-
return "null";
|
|
1232
|
-
if (Array.isArray(value))
|
|
1233
|
-
return "array";
|
|
1234
|
-
return typeof value;
|
|
1235
|
-
}
|
|
1236
|
-
const ACCESS_POLICY_LIST_FIELDS = [
|
|
1237
|
-
"ip_allowlist",
|
|
1238
|
-
"ip_denylist",
|
|
1239
|
-
"geo_allowlist",
|
|
1240
|
-
"geo_denylist",
|
|
1241
|
-
];
|
|
1242
|
-
const ACCESS_POLICY_STRING_FIELDS = [
|
|
1243
|
-
"user_agent_allow_regex",
|
|
1244
|
-
"user_agent_deny_regex",
|
|
1245
|
-
];
|
|
1246
|
-
const AI_AGENT_POLICY_LIST_FIELDS = [
|
|
1247
|
-
"deny_categories",
|
|
1248
|
-
"allow_categories",
|
|
1249
|
-
];
|
|
1250
|
-
function requireValidAccessPolicy(policy) {
|
|
1251
|
-
// null/undefined → "don't touch". Server is authoritative on CIDR /
|
|
1252
|
-
// ISO-3166 / regex grammar; this guard catches only the shape errors
|
|
1253
|
-
// an untyped-JS caller is likely to make (e.g. `geo_allowlist: "US"`
|
|
1254
|
-
// instead of `["US"]`) so they get a structured ValidationError
|
|
1255
|
-
// instead of a server 400 they have to debug.
|
|
1256
|
-
if (policy === undefined || policy === null)
|
|
1257
|
-
return;
|
|
1258
|
-
if (typeof policy !== "object" || Array.isArray(policy)) {
|
|
1259
|
-
throw clientValidationError(`access_policy: must be an object (got ${describeShape(policy)})`);
|
|
1260
|
-
}
|
|
1261
|
-
for (const field of ACCESS_POLICY_LIST_FIELDS) {
|
|
1262
|
-
const value = policy[field];
|
|
1263
|
-
if (value === undefined)
|
|
1264
|
-
continue;
|
|
1265
|
-
if (!Array.isArray(value)) {
|
|
1266
|
-
throw clientValidationError(`access_policy.${field}: must be an array (got ${describeShape(value)})`);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
for (const field of ACCESS_POLICY_STRING_FIELDS) {
|
|
1270
|
-
const value = policy[field];
|
|
1271
|
-
if (value === undefined)
|
|
1272
|
-
continue;
|
|
1273
|
-
if (typeof value !== "string") {
|
|
1274
|
-
throw clientValidationError(`access_policy.${field}: must be a string (got ${describeShape(value)})`);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
const aip = policy.ai_agent_policy;
|
|
1278
|
-
if (aip !== undefined) {
|
|
1279
|
-
if (aip === null || typeof aip !== "object" || Array.isArray(aip)) {
|
|
1280
|
-
throw clientValidationError(`access_policy.ai_agent_policy: must be an object (got ${describeShape(aip)})`);
|
|
1281
|
-
}
|
|
1282
|
-
if (aip.block_all !== undefined && typeof aip.block_all !== "boolean") {
|
|
1283
|
-
throw clientValidationError(`access_policy.ai_agent_policy.block_all: must be a boolean (got ${describeShape(aip.block_all)})`);
|
|
1284
|
-
}
|
|
1285
|
-
for (const field of AI_AGENT_POLICY_LIST_FIELDS) {
|
|
1286
|
-
const value = aip[field];
|
|
1287
|
-
if (value === undefined)
|
|
1288
|
-
continue;
|
|
1289
|
-
if (!Array.isArray(value)) {
|
|
1290
|
-
throw clientValidationError(`access_policy.ai_agent_policy.${field}: must be an array (got ${describeShape(value)})`);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
function validateCreateDelegatedQurlBatchInput(input) {
|
|
1296
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
1297
|
-
throw clientValidationError(`createDelegatedQurlBatch: input must be an object (got ${describeShape(input)})`);
|
|
1298
|
-
}
|
|
1299
|
-
const typed = input;
|
|
1300
|
-
requireNoUnknownFields(typed, DELEGATED_BATCH_INPUT_KEYS, "createDelegatedQurlBatch");
|
|
1301
|
-
if (typeof typed.mint_capability !== "string" ||
|
|
1302
|
-
typed.mint_capability.length < 1 ||
|
|
1303
|
-
typed.mint_capability.length > MAX_DELEGATED_MINT_CAPABILITY) {
|
|
1304
|
-
throw clientValidationError(`createDelegatedQurlBatch: mint_capability must be a string of 1-${MAX_DELEGATED_MINT_CAPABILITY} characters`);
|
|
1305
|
-
}
|
|
1306
|
-
if (!Array.isArray(typed.grants) ||
|
|
1307
|
-
typed.grants.length < 1 ||
|
|
1308
|
-
typed.grants.length > MAX_DELEGATED_BATCH_ITEMS) {
|
|
1309
|
-
throw clientValidationError(`createDelegatedQurlBatch: grants must contain 1-${MAX_DELEGATED_BATCH_ITEMS} items`);
|
|
1310
|
-
}
|
|
1311
|
-
for (const [index, grant] of typed.grants.entries()) {
|
|
1312
|
-
const field = `createDelegatedQurlBatch: grants[${index}]`;
|
|
1313
|
-
if (typeof grant !== "object" || grant === null || Array.isArray(grant)) {
|
|
1314
|
-
throw clientValidationError(`${field}: must be an object (got ${describeShape(grant)})`);
|
|
1315
|
-
}
|
|
1316
|
-
requireNoUnknownFields(grant, DELEGATED_BATCH_GRANT_KEYS, field);
|
|
1317
|
-
requireMaxUtf8Bytes(grant.label, `${field}.label`, MAX_LABEL);
|
|
1318
|
-
requireBooleanIfPresent(grant.one_time_use, `${field}.one_time_use`);
|
|
1319
|
-
requireMaxSessionsInRange(grant.max_sessions, `${field}.max_sessions`);
|
|
1320
|
-
for (const durationField of ["expires_in", "session_duration"]) {
|
|
1321
|
-
const value = grant[durationField];
|
|
1322
|
-
if (value !== undefined && (typeof value !== "string" || value.length === 0)) {
|
|
1323
|
-
throw clientValidationError(`${field}.${durationField}: must be a non-empty string`);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
if (grant.access_policy !== undefined) {
|
|
1327
|
-
requireValidAccessPolicy(grant.access_policy);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
function validateDelegatedBatchCreateOptions(options) {
|
|
1332
|
-
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
1333
|
-
throw clientValidationError("createDelegatedQurlBatch: options are required");
|
|
1334
|
-
}
|
|
1335
|
-
requireNoUnknownFields(options, DELEGATED_BATCH_CREATE_OPTION_KEYS, "createDelegatedQurlBatch options");
|
|
1336
|
-
const key = options.idempotencyKey;
|
|
1337
|
-
if (typeof key !== "string" ||
|
|
1338
|
-
key.length < MIN_DELEGATED_BATCH_IDEMPOTENCY_KEY ||
|
|
1339
|
-
key.length > MAX_IDEMPOTENCY_KEY ||
|
|
1340
|
-
!/^[!-~]+$/.test(key)) {
|
|
1341
|
-
throw clientValidationError(`createDelegatedQurlBatch: idempotencyKey must contain ${MIN_DELEGATED_BATCH_IDEMPOTENCY_KEY}-${MAX_IDEMPOTENCY_KEY} visible ASCII characters`);
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
function validateGetDelegatedQurlBatchOptions(options) {
|
|
1345
|
-
if (options === undefined)
|
|
1346
|
-
return;
|
|
1347
|
-
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
1348
|
-
throw clientValidationError(`getDelegatedQurlBatch: options must be an object (got ${describeShape(options)})`);
|
|
1349
|
-
}
|
|
1350
|
-
requireNoUnknownFields(options, DELEGATED_BATCH_GET_OPTION_KEYS, "getDelegatedQurlBatch options");
|
|
1351
|
-
const etag = options.etag;
|
|
1352
|
-
if (etag !== undefined &&
|
|
1353
|
-
(typeof etag !== "string" ||
|
|
1354
|
-
etag.length > MAX_DELEGATED_ETAG ||
|
|
1355
|
-
!STRONG_ETAG_PATTERN.test(etag))) {
|
|
1356
|
-
throw clientValidationError("getDelegatedQurlBatch: etag must be a valid strong ETag");
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
function delegatedResponseError(status, detail, requestId) {
|
|
1360
|
-
return unexpectedResponseError(`Delegated qURL batch ${detail}`, { status, requestId });
|
|
1361
|
-
}
|
|
1362
|
-
function delegatedResponseObject(value, status, label, requestId) {
|
|
1363
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1364
|
-
throw delegatedResponseError(status, `${label} is not an object`, requestId);
|
|
1365
|
-
}
|
|
1366
|
-
return value;
|
|
1367
|
-
}
|
|
1368
|
-
function delegatedEnvelope(envelope, status) {
|
|
1369
|
-
if (typeof envelope.meta !== "object" || envelope.meta === null || Array.isArray(envelope.meta)) {
|
|
1370
|
-
throw delegatedResponseError(status, "response meta is not an object");
|
|
1371
|
-
}
|
|
1372
|
-
const meta = envelope.meta;
|
|
1373
|
-
if (typeof meta.request_id !== "string") {
|
|
1374
|
-
throw delegatedResponseError(status, "response meta has invalid request_id");
|
|
1375
|
-
}
|
|
1376
|
-
const data = delegatedResponseObject(envelope.data, status, "response data", meta.request_id);
|
|
1377
|
-
return { data, requestId: meta.request_id };
|
|
1378
|
-
}
|
|
1379
|
-
function requiredDelegatedHeader(envelope, name, status, requestId) {
|
|
1380
|
-
const headers = envelope.__http_headers;
|
|
1381
|
-
if (!headers || typeof headers.get !== "function") {
|
|
1382
|
-
throw delegatedResponseError(status, "response headers are unavailable", requestId);
|
|
1383
|
-
}
|
|
1384
|
-
const value = headers.get(name);
|
|
1385
|
-
if (value === null) {
|
|
1386
|
-
throw delegatedResponseError(status, `response is missing ${name}`, requestId);
|
|
1387
|
-
}
|
|
1388
|
-
return value;
|
|
1389
|
-
}
|
|
1390
|
-
function hasDelegatedNoStore(value) {
|
|
1391
|
-
if (value === null)
|
|
1392
|
-
return false;
|
|
1393
|
-
const directives = new Set(value.split(",").map((directive) => directive.trim().split("=", 1)[0].toLowerCase()));
|
|
1394
|
-
return directives.has("private") && directives.has("no-store");
|
|
1395
|
-
}
|
|
1396
|
-
function delegatedResponseHeaders(envelope, status, requestId) {
|
|
1397
|
-
const cacheControl = envelope.__http_headers?.get("Cache-Control") ?? null;
|
|
1398
|
-
if (cacheControl !== null) {
|
|
1399
|
-
if (!hasDelegatedNoStore(cacheControl)) {
|
|
1400
|
-
throw delegatedResponseError(status, "response has invalid Cache-Control", requestId);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
else if (status !== 304) {
|
|
1404
|
-
requiredDelegatedHeader(envelope, "Cache-Control", status, requestId);
|
|
1405
|
-
}
|
|
1406
|
-
const etag = envelope.__http_headers?.get("ETag") ?? undefined;
|
|
1407
|
-
if (etag !== undefined && (etag.length > MAX_DELEGATED_ETAG || !STRONG_ETAG_PATTERN.test(etag))) {
|
|
1408
|
-
throw delegatedResponseError(status, "response has invalid strong ETag", requestId);
|
|
1409
|
-
}
|
|
1410
|
-
const rawRetryAfter = envelope.__http_headers?.get("Retry-After") ?? "";
|
|
1411
|
-
const trimmed = rawRetryAfter.trim();
|
|
1412
|
-
const seconds = /^\d+$/.test(trimmed)
|
|
1413
|
-
? Number(trimmed)
|
|
1414
|
-
: /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(trimmed)
|
|
1415
|
-
? Math.ceil((Date.parse(trimmed) - Date.now()) / 1000)
|
|
1416
|
-
: NaN;
|
|
1417
|
-
const retryAfter = Number.isSafeInteger(seconds) && seconds > 0 && seconds <= RETRY_AFTER_PARSE_LIMIT_S
|
|
1418
|
-
? seconds
|
|
1419
|
-
: undefined;
|
|
1420
|
-
return { etag, retryAfter };
|
|
1421
|
-
}
|
|
1422
|
-
function isUtcTimestamp(value) {
|
|
1423
|
-
return (typeof value === "string" &&
|
|
1424
|
-
UTC_TIMESTAMP_PATTERN.test(value) &&
|
|
1425
|
-
Number.isFinite(Date.parse(value)));
|
|
1426
|
-
}
|
|
1427
|
-
function delegatedBatchBase(data, status, requestId, expectedBatchId) {
|
|
1428
|
-
if (typeof data.batch_id !== "string" ||
|
|
1429
|
-
!DELEGATED_BATCH_ID_PATTERN.test(data.batch_id) ||
|
|
1430
|
-
(expectedBatchId !== undefined && data.batch_id !== expectedBatchId)) {
|
|
1431
|
-
throw delegatedResponseError(status, "response has invalid batch_id", requestId);
|
|
1432
|
-
}
|
|
1433
|
-
if (!Number.isInteger(data.item_count) ||
|
|
1434
|
-
Number(data.item_count) < 1 ||
|
|
1435
|
-
Number(data.item_count) > MAX_DELEGATED_BATCH_ITEMS) {
|
|
1436
|
-
throw delegatedResponseError(status, "response has invalid item_count", requestId);
|
|
1437
|
-
}
|
|
1438
|
-
if (!isUtcTimestamp(data.submitted_at)) {
|
|
1439
|
-
throw delegatedResponseError(status, "response has invalid submitted_at", requestId);
|
|
1440
|
-
}
|
|
1441
|
-
return {
|
|
1442
|
-
batchId: data.batch_id,
|
|
1443
|
-
itemCount: Number(data.item_count),
|
|
1444
|
-
submittedAt: data.submitted_at,
|
|
1445
|
-
};
|
|
1446
|
-
}
|
|
1447
|
-
function validateDelegatedLocation(value, baseUrl, batchId, status, requestId) {
|
|
1448
|
-
let location;
|
|
1449
|
-
try {
|
|
1450
|
-
const base = new URL(`${baseUrl}/`);
|
|
1451
|
-
const relative = value.startsWith("/v1/delegated-qurl-batches/")
|
|
1452
|
-
? `${base.pathname.replace(/\/$/, "")}${value}`
|
|
1453
|
-
: value;
|
|
1454
|
-
location = new URL(relative, base);
|
|
1455
|
-
}
|
|
1456
|
-
catch {
|
|
1457
|
-
throw delegatedResponseError(status, "response has invalid Location", requestId);
|
|
1458
|
-
}
|
|
1459
|
-
const expected = new URL(`${baseUrl}/v1/delegated-qurl-batches/${batchId}`);
|
|
1460
|
-
if (location.username !== "" ||
|
|
1461
|
-
location.password !== "" ||
|
|
1462
|
-
location.search !== "" ||
|
|
1463
|
-
location.hash !== "" ||
|
|
1464
|
-
location.href !== expected.href) {
|
|
1465
|
-
throw delegatedResponseError(status, "response has invalid Location", requestId);
|
|
1466
|
-
}
|
|
1467
|
-
return location.href;
|
|
1468
|
-
}
|
|
1469
|
-
function validateDelegatedQurlLink(value) {
|
|
1470
|
-
if (typeof value !== "string")
|
|
1471
|
-
return false;
|
|
1472
|
-
try {
|
|
1473
|
-
const link = new URL(value);
|
|
1474
|
-
return (link.protocol === "https:" &&
|
|
1475
|
-
link.username === "" &&
|
|
1476
|
-
link.password === "" &&
|
|
1477
|
-
link.hash.length > 1);
|
|
1478
|
-
}
|
|
1479
|
-
catch {
|
|
1480
|
-
return false;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
function validateDelegatedQurl(value, expectedQurlId, status, requestId) {
|
|
1484
|
-
const data = delegatedResponseObject(value, status, "response data", requestId);
|
|
1485
|
-
if (data.qurl_id !== expectedQurlId) {
|
|
1486
|
-
throw delegatedResponseError(status, "response has invalid qurl_id", requestId);
|
|
1487
|
-
}
|
|
1488
|
-
if (data.status !== "active" &&
|
|
1489
|
-
data.status !== "consumed" &&
|
|
1490
|
-
data.status !== "expired" &&
|
|
1491
|
-
data.status !== "revoked") {
|
|
1492
|
-
throw delegatedResponseError(status, "response has invalid status", requestId);
|
|
1493
|
-
}
|
|
1494
|
-
if (!isUtcTimestamp(data.expires_at) ||
|
|
1495
|
-
typeof data.one_time_use !== "boolean" ||
|
|
1496
|
-
!Number.isInteger(data.max_sessions) ||
|
|
1497
|
-
Number(data.max_sessions) < 0 ||
|
|
1498
|
-
!Number.isInteger(data.session_duration) ||
|
|
1499
|
-
Number(data.session_duration) <= 0 ||
|
|
1500
|
-
(data.label !== undefined && typeof data.label !== "string") ||
|
|
1501
|
-
(data.access_policy !== undefined &&
|
|
1502
|
-
(typeof data.access_policy !== "object" ||
|
|
1503
|
-
data.access_policy === null ||
|
|
1504
|
-
Array.isArray(data.access_policy)))) {
|
|
1505
|
-
throw delegatedResponseError(status, "response has invalid qURL metadata", requestId);
|
|
1506
|
-
}
|
|
1507
|
-
return { ...data, request_id: requestId };
|
|
1508
|
-
}
|
|
1509
|
-
function validateDelegatedBatchResults(value, itemCount, batchStatus, status, requestId) {
|
|
1510
|
-
if (!Array.isArray(value) || value.length !== itemCount) {
|
|
1511
|
-
throw delegatedResponseError(status, "terminal response has invalid results", requestId);
|
|
1512
|
-
}
|
|
1513
|
-
let succeeded = 0;
|
|
1514
|
-
const qurlIds = new Set();
|
|
1515
|
-
const links = new Set();
|
|
1516
|
-
const results = value.map((raw, index) => {
|
|
1517
|
-
const item = delegatedResponseObject(raw, status, `result ${index}`, requestId);
|
|
1518
|
-
if (item.index !== index) {
|
|
1519
|
-
throw delegatedResponseError(status, `result ${index} has invalid index`, requestId);
|
|
1520
|
-
}
|
|
1521
|
-
if (item.status === "succeeded") {
|
|
1522
|
-
if (item.error !== undefined && item.error !== null) {
|
|
1523
|
-
throw delegatedResponseError(status, `result ${index} has conflicting fields`, requestId);
|
|
1524
|
-
}
|
|
1525
|
-
const qurl = delegatedResponseObject(item.qurl, status, `result ${index} qurl`, requestId);
|
|
1526
|
-
if (typeof qurl.qurl_id !== "string" ||
|
|
1527
|
-
!DELEGATED_QURL_ID_PATTERN.test(qurl.qurl_id) ||
|
|
1528
|
-
!validateDelegatedQurlLink(qurl.qurl_link) ||
|
|
1529
|
-
!isUtcTimestamp(qurl.expires_at) ||
|
|
1530
|
-
qurlIds.has(qurl.qurl_id) ||
|
|
1531
|
-
links.has(qurl.qurl_link)) {
|
|
1532
|
-
throw delegatedResponseError(status, `result ${index} has invalid qurl`, requestId);
|
|
1533
|
-
}
|
|
1534
|
-
qurlIds.add(qurl.qurl_id);
|
|
1535
|
-
links.add(qurl.qurl_link);
|
|
1536
|
-
succeeded++;
|
|
1537
|
-
return {
|
|
1538
|
-
index,
|
|
1539
|
-
status: "succeeded",
|
|
1540
|
-
qurl: {
|
|
1541
|
-
qurl_id: qurl.qurl_id,
|
|
1542
|
-
qurl_link: qurl.qurl_link,
|
|
1543
|
-
expires_at: qurl.expires_at,
|
|
1544
|
-
},
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
if (item.status !== "failed" || (item.qurl !== undefined && item.qurl !== null)) {
|
|
1548
|
-
throw delegatedResponseError(status, `result ${index} has invalid status`, requestId);
|
|
1549
|
-
}
|
|
1550
|
-
const error = delegatedResponseObject(item.error, status, `result ${index} error`, requestId);
|
|
1551
|
-
if (typeof error.code !== "string" ||
|
|
1552
|
-
error.code.length === 0 ||
|
|
1553
|
-
typeof error.message !== "string") {
|
|
1554
|
-
throw delegatedResponseError(status, `result ${index} has invalid error`, requestId);
|
|
1555
|
-
}
|
|
1556
|
-
return {
|
|
1557
|
-
index,
|
|
1558
|
-
status: "failed",
|
|
1559
|
-
error: { code: error.code, message: error.message },
|
|
1560
|
-
};
|
|
1561
|
-
});
|
|
1562
|
-
if ((batchStatus === "succeeded" && succeeded !== itemCount) ||
|
|
1563
|
-
(batchStatus === "failed" && succeeded !== 0) ||
|
|
1564
|
-
(batchStatus === "partially_failed" && (succeeded === 0 || succeeded === itemCount))) {
|
|
1565
|
-
throw delegatedResponseError(status, "terminal status does not match results", requestId);
|
|
1566
|
-
}
|
|
1567
|
-
return results;
|
|
1568
|
-
}
|
|
1569
|
-
function classifyDelegatedBatchCreateFailure(error, batchId) {
|
|
1570
|
-
if (!(error instanceof errors_js_1.QURLError)) {
|
|
1571
|
-
throw new errors_js_1.DelegatedBatchOutcomeUnknownError(new errors_js_1.RuntimeError("Unexpected failure after delegated batch dispatch", { cause: error }), batchId);
|
|
1572
|
-
}
|
|
1573
|
-
if ((error.status >= 400 && error.status < 500 && error.status !== 408) || error.status === 501) {
|
|
1574
|
-
throw error;
|
|
1575
|
-
}
|
|
1576
|
-
throw new errors_js_1.DelegatedBatchOutcomeUnknownError(error, batchId);
|
|
1577
|
-
}
|
|
1578
|
-
// `format: uri` in the OpenAPI spec allows schemes the SDK doesn't
|
|
1579
|
-
// usefully support (`ftp://`, `file://`, `javascript:`, …). This is a
|
|
1580
|
-
// cheap client-side sanity check — the server is still the
|
|
1581
|
-
// authoritative validator (e.g. it rejects localhost, cloud metadata,
|
|
1582
|
-
// and private-range hosts; the SDK doesn't need to duplicate that).
|
|
1583
|
-
const ALLOWED_URL_SCHEMES = ["http://", "https://"];
|
|
1584
|
-
function requireValidHttpUrl(value, field) {
|
|
1585
|
-
// Three checks in priority order: type guard, scheme, length.
|
|
1586
|
-
// Stop at the first failure so a non-string URL doesn't
|
|
1587
|
-
// produce both "must be a string" AND "must be ≤ 2048 characters"
|
|
1588
|
-
// (the length check on a non-string would also typeof-fail and
|
|
1589
|
-
// duplicate the type message). The collect-all loop in
|
|
1590
|
-
// validateCreateInput runs each FIELD's validator independently — within
|
|
1591
|
-
// a single URL field itself we want fail-fast.
|
|
1592
|
-
if (typeof value !== "string") {
|
|
1593
|
-
throw clientValidationError(`${field}: must be a string (got ${value === null ? "null" : typeof value})`);
|
|
1594
|
-
}
|
|
1595
|
-
if (!ALLOWED_URL_SCHEMES.some((scheme) => value.startsWith(scheme))) {
|
|
1596
|
-
// Truncate to keep the error message compact and avoid
|
|
1597
|
-
// pathologically long schemes from filling logs.
|
|
1598
|
-
const repr = JSON.stringify(value).slice(0, 40);
|
|
1599
|
-
throw clientValidationError(`${field}: must start with http:// or https:// (got ${repr})`);
|
|
1600
|
-
}
|
|
1601
|
-
if (value.length > MAX_TARGET_URL) {
|
|
1602
|
-
throw clientValidationError(`${field}: must be ${MAX_TARGET_URL} characters or fewer (got ${value.length})`);
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
function requireValidTargetUrl(target_url) {
|
|
1606
|
-
requireValidHttpUrl(target_url, "target_url");
|
|
1607
|
-
}
|
|
1608
|
-
function validateCreateInput(input) {
|
|
1609
|
-
// Untyped-JS guard: surface as ValidationError instead of raw TypeError.
|
|
1610
|
-
if (typeof input !== "object" || input === null) {
|
|
1611
|
-
throw clientValidationError(`create: input must be an object (got ${input === null ? "null" : typeof input})`);
|
|
1612
|
-
}
|
|
1613
|
-
// Collect-all: run every validator and aggregate the messages so a
|
|
1614
|
-
// caller fixing multiple bad fields sees them all at once instead
|
|
1615
|
-
// of fix-re-run-repeat. Matches the documented batchCreate UX
|
|
1616
|
-
// ("All failures are collected into a single ValidationError")
|
|
1617
|
-
// and mirrors the requireValidTags collect-all pattern.
|
|
1618
|
-
const errors = [];
|
|
1619
|
-
const collect = (fn) => {
|
|
1620
|
-
try {
|
|
1621
|
-
fn();
|
|
1622
|
-
}
|
|
1623
|
-
catch (err) {
|
|
1624
|
-
if (err instanceof errors_js_1.ValidationError) {
|
|
1625
|
-
errors.push(err.detail);
|
|
1626
|
-
return;
|
|
1627
|
-
}
|
|
1628
|
-
throw err;
|
|
1629
|
-
}
|
|
1630
|
-
};
|
|
1631
|
-
collect(() => requireNoUnknownFields(input, CREATE_FIELD_KEYS, "create"));
|
|
1632
|
-
const resourceType = input.type;
|
|
1633
|
-
if (resourceType !== undefined && typeof resourceType !== "string") {
|
|
1634
|
-
errors.push(`type: must be a string (got ${resourceType === null ? "null" : typeof resourceType})`);
|
|
1635
|
-
}
|
|
1636
|
-
collect(() => {
|
|
1637
|
-
if (input.target_url === undefined) {
|
|
1638
|
-
if (resourceType === undefined || resourceType === "url") {
|
|
1639
|
-
throw clientValidationError("target_url: is required for url qURLs");
|
|
1640
|
-
}
|
|
1641
|
-
return;
|
|
1642
|
-
}
|
|
1643
|
-
requireValidTargetUrl(input.target_url);
|
|
1644
|
-
});
|
|
1645
|
-
collect(() => requireMaxLength(input.label, "label", MAX_LABEL));
|
|
1646
|
-
collect(() => requireNonEmptyIfPresent(input.label, "label"));
|
|
1647
|
-
collect(() => requireMaxLength(input.custom_domain, "custom_domain", MAX_CUSTOM_DOMAIN));
|
|
1648
|
-
collect(() => requireNonEmptyIfPresent(input.custom_domain, "custom_domain"));
|
|
1649
|
-
collect(() => requireBooleanIfPresent(input.private, "private"));
|
|
1650
|
-
collect(() => requireBooleanIfPresent(input.one_time_use, "one_time_use"));
|
|
1651
|
-
collect(() => requireMaxSessionsInRange(input.max_sessions));
|
|
1652
|
-
collect(() => requireNonEmptyIfPresent(input.expires_in, "expires_in"));
|
|
1653
|
-
collect(() => requireNonEmptyIfPresent(input.session_duration, "session_duration"));
|
|
1654
|
-
collect(() => requireValidAccessPolicy(input.access_policy));
|
|
1655
|
-
if (errors.length > 0) {
|
|
1656
|
-
// Inner `; ` is contractual (paired with batchCreate's outer ` | `).
|
|
1657
|
-
// Future field-level validators must not aggregate via `; ` internally.
|
|
1658
|
-
throw clientValidationError(errors.join("; "));
|
|
1659
|
-
}
|
|
1660
|
-
// Intentional omission: duration *grammar* (`expires_in`,
|
|
1661
|
-
// `session_duration`) is server-authoritative. Empty strings are
|
|
1662
|
-
// rejected up front (see collect() calls above) but unit/whitespace
|
|
1663
|
-
// edge cases ("24hh", "1w 3d") surface as a clean server 400 — a
|
|
1664
|
-
// client-side regex would just create a drift surface against the
|
|
1665
|
-
// server's rego/duration parser.
|
|
1666
|
-
//
|
|
1667
|
-
// `access_policy` contents (CIDRs, ISO-3166 codes, regex patterns)
|
|
1668
|
-
// are pass-through for the same reason. The shape-guard above only
|
|
1669
|
-
// catches structural mistakes (string-instead-of-array on list fields)
|
|
1670
|
-
// an untyped-JS caller is likely to make.
|
|
1671
|
-
}
|
|
1672
|
-
function validateQurlTokenOptions(input) {
|
|
1673
|
-
if (input === undefined)
|
|
1674
|
-
return;
|
|
1675
|
-
requireMaxLength(input.label, "label", MAX_LABEL);
|
|
1676
|
-
requireNonEmptyIfPresent(input.label, "label");
|
|
1677
|
-
requireNonEmptyIfPresent(input.expires_in, "expires_in");
|
|
1678
|
-
requireNonEmptyIfPresent(input.session_duration, "session_duration");
|
|
1679
|
-
// Mirror the service-documented size cap, but keep host-safety grammar
|
|
1680
|
-
// (single leading slash, no scheme/host/backslash/etc.) and the tunnel-only
|
|
1681
|
-
// gate server-authoritative (`invalid_target_path`); a client-side regex
|
|
1682
|
-
// would create a drift surface. Keep max-before-empty consistent with label;
|
|
1683
|
-
// empty strings pass the length check and are rejected by the next guard.
|
|
1684
|
-
requireMaxUtf8Bytes(input.target_path, "target_path", MAX_TARGET_PATH);
|
|
1685
|
-
requireNonEmptyIfPresent(input.target_path, "target_path");
|
|
1686
|
-
requireBooleanIfPresent(input.one_time_use, "one_time_use");
|
|
1687
|
-
requireMaxSessionsInRange(input.max_sessions);
|
|
1688
|
-
requireValidAccessPolicy(input.access_policy);
|
|
1689
|
-
}
|
|
1690
|
-
function validateResourceQurlUpdateInput(input) {
|
|
1691
|
-
requireMaxLength(input.label, "label", MAX_LABEL);
|
|
1692
|
-
requireNonEmptyIfPresent(input.label, "label");
|
|
1693
|
-
requireNonEmptyIfPresent(input.extend_by, "extend_by");
|
|
1694
|
-
requireNonEmptyIfPresent(input.expires_at, "expires_at");
|
|
1695
|
-
requireNonEmptyIfPresent(input.session_duration, "session_duration");
|
|
1696
|
-
requireMaxSessionsInRange(input.max_sessions);
|
|
1697
|
-
requireValidAccessPolicy(input.access_policy);
|
|
1698
|
-
if (input.extend_by !== undefined && input.expires_at !== undefined) {
|
|
1699
|
-
throw clientValidationError("updateResourceQurl: `extend_by` and `expires_at` are mutually exclusive — provide at most one");
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
function requireObjectInput(input, method) {
|
|
1703
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
1704
|
-
throw clientValidationError(`${method}: input must be an object (got ${input === null ? "null" : typeof input})`);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
function requireNonEmptyStringField(input, field, method) {
|
|
1708
|
-
const value = input[field];
|
|
1709
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
1710
|
-
throw clientValidationError(`${method}: ${field} must be a non-empty string`);
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
function requireNonEmptyArrayField(input, field, method) {
|
|
1714
|
-
const value = input[field];
|
|
1715
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
1716
|
-
throw clientValidationError(`${method}: ${field} must be a non-empty array`);
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
function requireStringArrayElements(value, field, method) {
|
|
1720
|
-
if (!Array.isArray(value)) {
|
|
1721
|
-
throw clientValidationError(`${method}: ${field} must be a non-empty array`);
|
|
1722
|
-
}
|
|
1723
|
-
value.forEach((entry, index) => {
|
|
1724
|
-
if (typeof entry !== "string" || entry.length === 0) {
|
|
1725
|
-
throw clientValidationError(`${method}: ${field}[${index}] must be a non-empty string`);
|
|
1726
|
-
}
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
function requireAtLeastOneField(input, fields, method) {
|
|
1730
|
-
if (!fields.some((field) => input[field] !== undefined)) {
|
|
1731
|
-
throw clientValidationError(`${method}: at least one field (${fields.join(", ")}) must be provided`);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
function requireNoUnknownFields(input, fields, method) {
|
|
1735
|
-
const unknown = Object.keys(input).filter((field) => !fields.includes(field));
|
|
1736
|
-
if (unknown.length > 0) {
|
|
1737
|
-
const rendered = unknown.map((field) => `"${field}"`).join(", ");
|
|
1738
|
-
const label = unknown.length === 1 ? "unknown field" : "unknown fields";
|
|
1739
|
-
throw clientValidationError(`${method}: ${label} ${rendered}`);
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
function normalizePatchFields(input, fields, options = {}) {
|
|
1743
|
-
const preserveNull = new Set(options.preserveNullFields ?? []);
|
|
1744
|
-
const normalized = {};
|
|
1745
|
-
for (const key of fields) {
|
|
1746
|
-
const value = input[key];
|
|
1747
|
-
if (value !== undefined && (value !== null || preserveNull.has(key))) {
|
|
1748
|
-
normalized[key] = value;
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
return normalized;
|
|
1752
|
-
}
|
|
1753
|
-
function requireConsumerDecision(id, version, allowNew = false) {
|
|
1754
|
-
if (typeof id !== "string" ||
|
|
1755
|
-
(!/^[a-f0-9]{64}$/.test(id) && !(allowNew && id === "new")) ||
|
|
1756
|
-
!Number.isSafeInteger(version) ||
|
|
1757
|
-
version < (allowNew ? 0 : 1))
|
|
1758
|
-
throw clientValidationError("consumer decision: invalid id or expected version");
|
|
1759
|
-
}
|
|
1760
|
-
function validateResourceWriteFields(input, options = {}) {
|
|
1761
|
-
const errors = [];
|
|
1762
|
-
const collect = (fn) => {
|
|
1763
|
-
try {
|
|
1764
|
-
fn();
|
|
1765
|
-
}
|
|
1766
|
-
catch (err) {
|
|
1767
|
-
if (err instanceof errors_js_1.ValidationError) {
|
|
1768
|
-
errors.push(err.detail);
|
|
1769
|
-
return;
|
|
1770
|
-
}
|
|
1771
|
-
throw err;
|
|
1772
|
-
}
|
|
1773
|
-
};
|
|
1774
|
-
collect(() => {
|
|
1775
|
-
if (input.private !== undefined && typeof input.private !== "boolean") {
|
|
1776
|
-
throw clientValidationError("private: must be a boolean");
|
|
1777
|
-
}
|
|
1778
|
-
if (input.consumer_access !== undefined) {
|
|
1779
|
-
const policy = input.consumer_access;
|
|
1780
|
-
requireObjectInput(policy, "consumer_access");
|
|
1781
|
-
const p = policy;
|
|
1782
|
-
requireNoUnknownFields(p, ["mode", "version", "contract_id"], "consumer_access");
|
|
1783
|
-
if (!["keys", "invite_only", "email_capture", "review", "contract"].includes(p.mode) ||
|
|
1784
|
-
!Number.isSafeInteger(p.version) ||
|
|
1785
|
-
p.version < 1) {
|
|
1786
|
-
throw clientValidationError("consumer_access: invalid mode or version");
|
|
1787
|
-
}
|
|
1788
|
-
if (p.mode === "contract") {
|
|
1789
|
-
if (typeof p.contract_id !== "string" ||
|
|
1790
|
-
!p.contract_id.trim() ||
|
|
1791
|
-
new TextEncoder().encode(p.contract_id).length > 128) {
|
|
1792
|
-
throw clientValidationError("consumer_access: contract mode requires a bounded contract_id");
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
else if (p.contract_id !== undefined) {
|
|
1796
|
-
throw clientValidationError("consumer_access: contract_id requires contract mode");
|
|
1797
|
-
}
|
|
1798
|
-
if (input.private === false)
|
|
1799
|
-
throw clientValidationError("consumer_access: requires a private resource");
|
|
1800
|
-
if (options.requireUrlTarget) {
|
|
1801
|
-
if (p.version !== 1 || input.private !== true)
|
|
1802
|
-
throw clientValidationError("consumer_access: creation requires private true and version one");
|
|
1803
|
-
}
|
|
1804
|
-
else if (options.allowAliasClear) {
|
|
1805
|
-
const expected = input.expected_consumer_access_version;
|
|
1806
|
-
if (!Number.isSafeInteger(expected) ||
|
|
1807
|
-
expected < 0 ||
|
|
1808
|
-
p.version !== expected + 1 ||
|
|
1809
|
-
Object.keys(input).some((key) => key !== "consumer_access" && key !== "expected_consumer_access_version")) {
|
|
1810
|
-
throw clientValidationError("consumer_access: update requires its expected version and no metadata changes");
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
else if (input.expected_consumer_access_version !== undefined) {
|
|
1815
|
-
throw clientValidationError("expected_consumer_access_version: requires consumer_access");
|
|
1816
|
-
}
|
|
1817
|
-
});
|
|
1818
|
-
collect(() => {
|
|
1819
|
-
const fields = ["allowed_device_keys_add", "allowed_device_keys_remove"];
|
|
1820
|
-
for (const field of fields) {
|
|
1821
|
-
const keys = input[field];
|
|
1822
|
-
if (keys === undefined)
|
|
1823
|
-
continue;
|
|
1824
|
-
requireStringArrayElements(keys, field, "updateResource");
|
|
1825
|
-
if (keys.length > 256 || new Set(keys).size !== keys.length) {
|
|
1826
|
-
throw clientValidationError(`${field}: must contain at most 256 unique keys`);
|
|
1827
|
-
}
|
|
1828
|
-
for (const key of keys) {
|
|
1829
|
-
let canonical = false;
|
|
1830
|
-
try {
|
|
1831
|
-
canonical = key.length === 44 && atob(key).length === 32 && btoa(atob(key)) === key;
|
|
1832
|
-
}
|
|
1833
|
-
catch {
|
|
1834
|
-
/* Invalid base64 is rejected below. */
|
|
1835
|
-
}
|
|
1836
|
-
if (!canonical) {
|
|
1837
|
-
throw clientValidationError(`${field}: keys must be canonical padded base64 of 32 bytes`);
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
if (input.custom_domain !== undefined) {
|
|
1841
|
-
throw clientValidationError(`${field}: update separately from custom_domain`);
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
const added = new Set(input.allowed_device_keys_add);
|
|
1845
|
-
if (input.allowed_device_keys_remove?.some((key) => added.has(key))) {
|
|
1846
|
-
throw clientValidationError("allowed device key additions and removals must not overlap");
|
|
1847
|
-
}
|
|
1848
|
-
});
|
|
1849
|
-
const resourceType = input.type;
|
|
1850
|
-
if (resourceType !== undefined && typeof resourceType !== "string") {
|
|
1851
|
-
errors.push(`type: must be a string (got ${resourceType === null ? "null" : typeof resourceType})`);
|
|
1852
|
-
}
|
|
1853
|
-
collect(() => {
|
|
1854
|
-
if (input.target_url === undefined) {
|
|
1855
|
-
if (options.requireUrlTarget && (resourceType === undefined || resourceType === "url")) {
|
|
1856
|
-
throw clientValidationError("target_url: is required for url resources");
|
|
1857
|
-
}
|
|
1858
|
-
return;
|
|
1859
|
-
}
|
|
1860
|
-
requireValidTargetUrl(input.target_url);
|
|
1861
|
-
});
|
|
1862
|
-
collect(() => requireMaxLength(input.description, "description", MAX_DESCRIPTION));
|
|
1863
|
-
collect(() => requireMaxLength(input.custom_domain, "custom_domain", MAX_CUSTOM_DOMAIN));
|
|
1864
|
-
collect(() => {
|
|
1865
|
-
if (options.allowCustomDomainClear && input.custom_domain === "")
|
|
1866
|
-
return;
|
|
1867
|
-
requireNonEmptyIfPresent(input.custom_domain, "custom_domain");
|
|
1868
|
-
});
|
|
1869
|
-
collect(() => requireValidTags(input.tags));
|
|
1870
|
-
collect(() => {
|
|
1871
|
-
if (input.alias !== undefined) {
|
|
1872
|
-
if (input.alias === null) {
|
|
1873
|
-
if (!options.allowAliasClear) {
|
|
1874
|
-
throw clientValidationError("alias: must be a string (got null)");
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
else {
|
|
1878
|
-
requireMaxLength(input.alias, "alias", MAX_ALIAS);
|
|
1879
|
-
requireNonEmptyIfPresent(input.alias, "alias");
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
});
|
|
1883
|
-
collect(() => {
|
|
1884
|
-
if (input.slug === undefined)
|
|
1885
|
-
return;
|
|
1886
|
-
requireMaxLength(input.slug, "slug", MAX_SLUG);
|
|
1887
|
-
requireNonEmptyIfPresent(input.slug, "slug");
|
|
1888
|
-
});
|
|
1889
|
-
collect(() => {
|
|
1890
|
-
if (options.validateFindOrCreate &&
|
|
1891
|
-
input.find_or_create !== undefined &&
|
|
1892
|
-
typeof input.find_or_create !== "boolean") {
|
|
1893
|
-
throw clientValidationError(`find_or_create: must be a boolean (got ${describeShape(input.find_or_create)})`);
|
|
1894
|
-
}
|
|
1895
|
-
});
|
|
1896
|
-
collect(() => {
|
|
1897
|
-
if (options.validatePreserveHost &&
|
|
1898
|
-
input.preserve_host !== undefined &&
|
|
1899
|
-
typeof input.preserve_host !== "boolean") {
|
|
1900
|
-
throw clientValidationError(`preserve_host: must be a boolean (got ${describeShape(input.preserve_host)})`);
|
|
1901
|
-
}
|
|
1902
|
-
});
|
|
1903
|
-
if (errors.length > 0) {
|
|
1904
|
-
throw clientValidationError(errors.join("; "));
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
function validateWebhookWriteFields(input, method, requiredFields = {}) {
|
|
1908
|
-
if (requiredFields.url || input.url !== undefined) {
|
|
1909
|
-
requireNonEmptyStringField(input, "url", method);
|
|
1910
|
-
requireValidHttpUrl(input.url, "url");
|
|
1911
|
-
}
|
|
1912
|
-
if (requiredFields.events || input.events !== undefined) {
|
|
1913
|
-
requireNonEmptyArrayField(input, "events", method);
|
|
1914
|
-
requireStringArrayElements(input.events, "events", method);
|
|
1915
|
-
}
|
|
1916
|
-
requireMaxLength(input.description, "description", MAX_DESCRIPTION);
|
|
1917
|
-
if (input.status !== undefined) {
|
|
1918
|
-
requireNonEmptyStringField(input, "status", method);
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
function validateApiKeyWriteFields(input, method, requiredFields = {}) {
|
|
1922
|
-
if (requiredFields.name || input.name !== undefined) {
|
|
1923
|
-
requireNonEmptyStringField(input, "name", method);
|
|
1924
|
-
requireMaxLength(input.name, "name", MAX_API_KEY_NAME);
|
|
1925
|
-
}
|
|
1926
|
-
if (requiredFields.scopes || input.scopes !== undefined) {
|
|
1927
|
-
requireNonEmptyArrayField(input, "scopes", method);
|
|
1928
|
-
requireStringArrayElements(input.scopes, "scopes", method);
|
|
1929
|
-
const unsupported = input.scopes.find((scope) => !API_KEY_REQUEST_SCOPES.has(scope));
|
|
1930
|
-
if (unsupported !== undefined) {
|
|
1931
|
-
throw clientValidationError(`${method}: scopes contains an unsupported permission ${JSON.stringify(unsupported)}; this SDK accepts ${API_KEY_REQUEST_SCOPE_VALUES.join(", ")}`);
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
function validateEnrollmentTokenFields(input, method) {
|
|
1936
|
-
if (input.target !== undefined &&
|
|
1937
|
-
(typeof input.target !== "string" || !CREDENTIAL_TARGETS.has(input.target))) {
|
|
1938
|
-
throw clientValidationError(`${method}: target must be 'agent' or 'connector'`);
|
|
1939
|
-
}
|
|
1940
|
-
let claimCount = 0;
|
|
1941
|
-
if (input.claims !== undefined) {
|
|
1942
|
-
if (!Array.isArray(input.claims)) {
|
|
1943
|
-
throw clientValidationError(`${method}: claims must be an array`);
|
|
1944
|
-
}
|
|
1945
|
-
if (input.claims.length > 1) {
|
|
1946
|
-
throw clientValidationError(`${method}: claims must contain at most one claim`);
|
|
1947
|
-
}
|
|
1948
|
-
claimCount = input.claims.length;
|
|
1949
|
-
for (const [index, claim] of input.claims.entries()) {
|
|
1950
|
-
if (!claim || typeof claim !== "object" || Array.isArray(claim)) {
|
|
1951
|
-
throw clientValidationError(`${method}: claims[${index}] must be an object`);
|
|
1952
|
-
}
|
|
1953
|
-
requireNoUnknownFields(claim, ["type", "id"], `${method}: claims[${index}]`);
|
|
1954
|
-
const record = claim;
|
|
1955
|
-
if (typeof record.type !== "string" || !CREDENTIAL_CLAIM_TYPES.has(record.type)) {
|
|
1956
|
-
throw clientValidationError(`${method}: claims[${index}].type must be 'connector'`);
|
|
1957
|
-
}
|
|
1958
|
-
if (typeof record.id !== "string" || !CONNECTOR_SLUG_PATTERN.test(record.id)) {
|
|
1959
|
-
throw clientValidationError(`${method}: claims[${index}].id is not a valid connector slug; use 3-64 lowercase letters, digits, or hyphens, starting with a letter and ending with a letter or digit`);
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
// CreateApiKeyRequest.target permits an agent with zero or one connector claim.
|
|
1964
|
-
if (input.target === "connector" && claimCount !== 1) {
|
|
1965
|
-
throw clientValidationError(`${method}: target 'connector' requires exactly one claim`);
|
|
1966
|
-
}
|
|
1967
|
-
if (input.expires_in !== undefined) {
|
|
1968
|
-
if (typeof input.expires_in !== "string") {
|
|
1969
|
-
throw clientValidationError(`${method}: expires_in must be a duration string`);
|
|
1970
|
-
}
|
|
1971
|
-
const match = CREDENTIAL_DURATION_PATTERN.exec(input.expires_in);
|
|
1972
|
-
if (!match) {
|
|
1973
|
-
throw clientValidationError(`${method}: expires_in must match <integer><s|m|h|d|w>`);
|
|
1974
|
-
}
|
|
1975
|
-
const seconds = BigInt(match[1]) * CREDENTIAL_DURATION_MULTIPLIERS[match[2]];
|
|
1976
|
-
if (seconds <= 0n || seconds > MAX_ENROLLMENT_TOKEN_TTL_SECONDS) {
|
|
1977
|
-
throw clientValidationError(`${method}: expires_in must be greater than zero and at most 24h`);
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
/**
|
|
1982
|
-
* Per-entry shape guard for {@link BatchItemResult}. Verifies every
|
|
1983
|
-
* non-optional field on the branch of the discriminated union selected
|
|
1984
|
-
* by the `success` discriminant. Protects consumers who narrow on
|
|
1985
|
-
* `success` and then access `resource_id` / `qurl_link` / `error` as
|
|
1986
|
-
* guaranteed-present — if the API ever omits one, we trip the guard
|
|
1987
|
-
* rather than returning `undefined` where the type says `string`.
|
|
1988
|
-
*
|
|
1989
|
-
* Returns `null` when the entry is valid, or a human-readable reason
|
|
1990
|
-
* string describing the *specific* field that's missing or wrong. The
|
|
1991
|
-
* reason never echoes the entry's values — only field names and shape
|
|
1992
|
-
* descriptors — so the caller can safely surface it in error messages
|
|
1993
|
-
* that may end up in observability pipelines. Contrast with the outer
|
|
1994
|
-
* guard's intentional silence about response-body content for the
|
|
1995
|
-
* same info-leak reason.
|
|
1996
|
-
*/
|
|
1997
|
-
function batchItemResultValidationReason(entry) {
|
|
1998
|
-
if (!entry || typeof entry !== "object")
|
|
1999
|
-
return "not an object";
|
|
2000
|
-
const e = entry;
|
|
2001
|
-
// `Number.isInteger` rejects NaN, Infinity, and floats; combined
|
|
2002
|
-
// with `>= 0`, the check rules out pathological proxy bodies that
|
|
2003
|
-
// would attribute to a useless index. `index` is informational, so
|
|
2004
|
-
// this isn't a correctness hazard, but tightening the constraint
|
|
2005
|
-
// matches the same posture as the count integer guards above.
|
|
2006
|
-
if (!Number.isInteger(e.index) || e.index < 0) {
|
|
2007
|
-
return "missing/invalid 'index' (expected non-negative integer)";
|
|
2008
|
-
}
|
|
2009
|
-
if (e.success === true) {
|
|
2010
|
-
if (typeof e.resource_id !== "string") {
|
|
2011
|
-
return "success-branch missing required field 'resource_id' (expected string)";
|
|
2012
|
-
}
|
|
2013
|
-
if (typeof e.qurl_link !== "string") {
|
|
2014
|
-
return "success-branch missing required field 'qurl_link' (expected string)";
|
|
2015
|
-
}
|
|
2016
|
-
if (typeof e.qurl_site !== "string") {
|
|
2017
|
-
return "success-branch missing required field 'qurl_site' (expected string)";
|
|
2018
|
-
}
|
|
2019
|
-
return null;
|
|
2020
|
-
}
|
|
2021
|
-
if (e.success === false) {
|
|
2022
|
-
if (!e.error || typeof e.error !== "object") {
|
|
2023
|
-
return "failure-branch missing required field 'error' (expected object)";
|
|
2024
|
-
}
|
|
2025
|
-
const err = e.error;
|
|
2026
|
-
if (typeof err.code !== "string") {
|
|
2027
|
-
return "failure-branch missing required field 'error.code' (expected string)";
|
|
2028
|
-
}
|
|
2029
|
-
if (typeof err.message !== "string") {
|
|
2030
|
-
return "failure-branch missing required field 'error.message' (expected string)";
|
|
2031
|
-
}
|
|
2032
|
-
return null;
|
|
2033
|
-
}
|
|
2034
|
-
return "missing/invalid 'success' discriminant (expected boolean)";
|
|
2035
|
-
}
|
|
2036
|
-
// ---- Portal-verb helpers (mirrors qurl-go) -------------------------------
|
|
2037
|
-
// Client-side guardrails for the portal flow (protectUrl → createPortal →
|
|
2038
|
-
// enterPortal), kept in lockstep with qurl-go's option validation and
|
|
2039
|
-
// qurl-python's port of it.
|
|
2040
|
-
/**
|
|
2041
|
-
* Client-side floor for portal lifetimes, mirroring qurl-go's `ValidFor`.
|
|
2042
|
-
* The LayerV API remains the source of truth for account limits; there is
|
|
2043
|
-
* intentionally no SDK-side maximum because an account may allow
|
|
2044
|
-
* longer-lived portals.
|
|
2045
|
-
*/
|
|
2046
|
-
const MIN_PORTAL_VALID_FOR_SECONDS = 60;
|
|
2047
|
-
/**
|
|
2048
|
-
* Client-side floor for session durations, mirroring qurl-go's
|
|
2049
|
-
* `WithSessionDuration` (at least one second; zero is rejected rather than
|
|
2050
|
-
* treated as the server default).
|
|
2051
|
-
*/
|
|
2052
|
-
const MIN_SESSION_DURATION_SECONDS = 1;
|
|
2053
|
-
/**
|
|
2054
|
-
* Shape of qurl-go's offline signed-fragment links: three or more
|
|
2055
|
-
* dot-separated base64url parts (<version>.<claims>.<secret>.<sig>).
|
|
2056
|
-
* Detected only to give those links a precise error in extractAccessToken.
|
|
2057
|
-
*
|
|
2058
|
-
* @internal Exported for in-package tests; not re-exported from index.ts.
|
|
2059
|
-
*/
|
|
2060
|
-
exports.SIGNED_FRAGMENT_RE = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+){2,}$/;
|
|
2061
|
-
// Access-token grammar accepted by enterPortal (base64url charset).
|
|
2062
|
-
const ACCESS_TOKEN_RE = /^[A-Za-z0-9_-]+$/;
|
|
2063
|
-
/**
|
|
2064
|
-
* Serialize a duration option to the API's h/m/s duration grammar.
|
|
2065
|
-
*
|
|
2066
|
-
* Strings pass through unvalidated — the server is authoritative, same as
|
|
2067
|
-
* the REST-shaped `expires_in` fields. Numbers are milliseconds and get
|
|
2068
|
-
* qurl-go's client-side guardrails: whole seconds only and at least
|
|
2069
|
-
* `minimumSeconds` (checked in that order so e.g. 30.5s reports "at least
|
|
2070
|
-
* 60s" in every SDK). Serialized with hours as the largest unit so all API
|
|
2071
|
-
* duration fields use the same grammar across SDKs.
|
|
2072
|
-
*/
|
|
2073
|
-
function serializeApiDuration(value, field, minimumSeconds) {
|
|
2074
|
-
if (value === undefined)
|
|
2075
|
-
return undefined;
|
|
2076
|
-
if (typeof value === "string") {
|
|
2077
|
-
if (value.trim() === "") {
|
|
2078
|
-
throw clientValidationError(`${field}: must be a non-empty duration string`);
|
|
2079
|
-
}
|
|
2080
|
-
return value;
|
|
2081
|
-
}
|
|
2082
|
-
if (typeof value === "number") {
|
|
2083
|
-
// NaN fails no comparison, so an explicit finite check keeps the
|
|
2084
|
-
// error actionable instead of a misleading "whole seconds" report.
|
|
2085
|
-
if (!Number.isFinite(value)) {
|
|
2086
|
-
throw clientValidationError(`${field}: must be a finite number of milliseconds`);
|
|
2087
|
-
}
|
|
2088
|
-
if (value < minimumSeconds * 1000) {
|
|
2089
|
-
throw clientValidationError(`${field}: must be at least ${minimumSeconds}s`);
|
|
2090
|
-
}
|
|
2091
|
-
if (value % 1000 !== 0) {
|
|
2092
|
-
throw clientValidationError(`${field}: must be whole seconds`);
|
|
2093
|
-
}
|
|
2094
|
-
const seconds = value / 1000;
|
|
2095
|
-
if (seconds % 3600 === 0)
|
|
2096
|
-
return `${seconds / 3600}h`;
|
|
2097
|
-
if (seconds % 60 === 0)
|
|
2098
|
-
return `${seconds / 60}m`;
|
|
2099
|
-
return `${seconds}s`;
|
|
2100
|
-
}
|
|
2101
|
-
throw clientValidationError(`${field}: must be a duration string (e.g. "5m") or a number of milliseconds`);
|
|
2102
|
-
}
|
|
2103
|
-
/**
|
|
2104
|
-
* Portal-verb target URL validation (qurl-go `ProtectURL` parity). On top of
|
|
2105
|
-
* the shared scheme/length check, the URL must carry a host and must parse,
|
|
2106
|
-
* and embedded credentials (userinfo) are rejected. The added checks never
|
|
2107
|
-
* echo the URL — a userinfo URL contains credentials that must stay out of
|
|
2108
|
-
* logs and error messages. The REST-shaped compatibility layer keeps its
|
|
2109
|
-
* original prefix-only check so its accepted inputs don't change.
|
|
2110
|
-
*/
|
|
2111
|
-
function requirePortalTargetUrl(targetUrl) {
|
|
2112
|
-
requireValidTargetUrl(targetUrl);
|
|
2113
|
-
const url = targetUrl; // requireValidTargetUrl only returns for http(s) strings
|
|
2114
|
-
// WHATWG URL normalizes away an empty authority ("https:///x" parses
|
|
2115
|
-
// with host "x"), so the host-presence check runs on the raw string,
|
|
2116
|
-
// mirroring qurl-go's url.Parse which rejects authority-less URLs.
|
|
2117
|
-
const afterScheme = url.slice(url.indexOf("://") + 3);
|
|
2118
|
-
if (afterScheme === "" || "/?#".includes(afterScheme[0])) {
|
|
2119
|
-
throw clientValidationError("target_url: must include a host");
|
|
2120
|
-
}
|
|
2121
|
-
let parsed;
|
|
2122
|
-
try {
|
|
2123
|
-
parsed = new URL(url);
|
|
2124
|
-
}
|
|
2125
|
-
catch {
|
|
2126
|
-
throw clientValidationError("target_url: malformed URL");
|
|
2127
|
-
}
|
|
2128
|
-
if (parsed.username !== "" || parsed.password !== "") {
|
|
2129
|
-
throw clientValidationError("target_url: must not include embedded credentials (userinfo)");
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
/**
|
|
2133
|
-
* Extract the access token from a qURL link, or pass a bare token through.
|
|
2134
|
-
*
|
|
2135
|
-
* Platform-issued links carry the access token in the URL fragment
|
|
2136
|
-
* (`https://qurl.link/#at_...`). Error messages deliberately never echo the
|
|
2137
|
-
* input — qURL links are credentials and must stay out of logs.
|
|
2138
|
-
*/
|
|
2139
|
-
function extractAccessToken(qurlLink) {
|
|
2140
|
-
if (typeof qurlLink !== "string" || qurlLink.trim() === "") {
|
|
2141
|
-
throw clientValidationError("qurlLink: must be a non-empty string");
|
|
2142
|
-
}
|
|
2143
|
-
const hashIndex = qurlLink.indexOf("#");
|
|
2144
|
-
const token = hashIndex >= 0 ? qurlLink.slice(hashIndex + 1) : qurlLink;
|
|
2145
|
-
if (exports.SIGNED_FRAGMENT_RE.test(token)) {
|
|
2146
|
-
throw clientValidationError("qurlLink: this is a signed qURL link (offline fragment format), which cannot be " +
|
|
2147
|
-
"opened through the API resolver — enterPortal only accepts platform links " +
|
|
2148
|
-
"(https://qurl.link/#at_...) or bare access tokens");
|
|
2149
|
-
}
|
|
2150
|
-
if (token === "" || !ACCESS_TOKEN_RE.test(token)) {
|
|
2151
|
-
throw clientValidationError("qurlLink: no access token found — pass a platform qURL link " +
|
|
2152
|
-
"(https://qurl.link/#at_...) or a bare access token");
|
|
2153
|
-
}
|
|
2154
|
-
return token;
|
|
2155
|
-
}
|
|
2156
|
-
/**
|
|
2157
|
-
* Parse an RFC 3339 timestamp from the API into a `Date`, or `undefined`
|
|
2158
|
-
* when absent or unparseable. The portal surface exposes `Date` (matching
|
|
2159
|
-
* qurl-go's `time.Time` and qurl-python's `datetime`); the REST-shaped
|
|
2160
|
-
* layer keeps raw wire strings.
|
|
2161
|
-
*/
|
|
2162
|
-
function parseApiDate(value) {
|
|
2163
|
-
if (typeof value !== "string" || value === "")
|
|
2164
|
-
return undefined;
|
|
2165
|
-
const parsed = new Date(value);
|
|
2166
|
-
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* Parse a {@link Portal} from a create-portal API response. `resource_id`
|
|
2170
|
-
* and `qurl_link` are required, mirroring qurl-go's fail-closed response
|
|
2171
|
-
* check; everything else is optional because the two mint endpoints differ
|
|
2172
|
-
* in which ancillary fields they return.
|
|
2173
|
-
*/
|
|
2174
|
-
async function parsePortal(data, method, expectedCrid) {
|
|
2175
|
-
if (typeof data?.resource_id !== "string" || data.resource_id.trim() === "") {
|
|
2176
|
-
throw unexpectedResponseError(`${method}: response is missing resource_id`);
|
|
2177
|
-
}
|
|
2178
|
-
if (typeof data.qurl_link !== "string" || data.qurl_link.trim() === "") {
|
|
2179
|
-
throw unexpectedResponseError(`${method}: response is missing qurl_link`);
|
|
2180
|
-
}
|
|
2181
|
-
await verifyResourceIdentity(data, method, expectedCrid);
|
|
2182
|
-
return {
|
|
2183
|
-
crid: data.crid,
|
|
2184
|
-
resourcePublicKey: data.resource_id,
|
|
2185
|
-
link: data.qurl_link,
|
|
2186
|
-
site: data.qurl_site,
|
|
2187
|
-
expiresAt: parseApiDate(data.expires_at),
|
|
2188
|
-
// Same empty-string → absent normalization the REST-shaped layer's
|
|
2189
|
-
// consumers apply to qurl_id.
|
|
2190
|
-
qurlId: data.qurl_id || undefined,
|
|
2191
|
-
label: data.label,
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
/**
|
|
2195
|
-
* Validate portal options and build the create-portal wire body. The service
|
|
2196
|
-
* requires a JSON object even when every option is omitted; `{}` selects the
|
|
2197
|
-
* owner's plan defaults and matches qurl-go's zero-value request struct.
|
|
2198
|
-
*/
|
|
2199
|
-
function buildCreatePortalBody(opts, method) {
|
|
2200
|
-
requireObjectInput(opts, method);
|
|
2201
|
-
requireNoUnknownFields(opts, CREATE_PORTAL_OPTION_KEYS, method);
|
|
2202
|
-
// null → omitted, via the SDK-wide normalizePatchFields convention.
|
|
2203
|
-
const { validFor, label, oneTimeUse, maxSessions, sessionDuration, targetPath } = normalizePatchFields(opts, CREATE_PORTAL_OPTION_KEYS);
|
|
2204
|
-
if (label !== undefined) {
|
|
2205
|
-
requireMaxLength(label, "label", MAX_LABEL);
|
|
2206
|
-
// qurl-go's WithLabel rejects blank labels; the REST-shaped mintLink
|
|
2207
|
-
// keeps its permissive whitespace behavior, so the check lives here.
|
|
2208
|
-
if (label.trim() === "") {
|
|
2209
|
-
throw clientValidationError("label: must not be empty");
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
requireBooleanIfPresent(oneTimeUse, "oneTimeUse");
|
|
2213
|
-
requireMaxSessionsInRange(maxSessions, "maxSessions");
|
|
2214
|
-
requireMaxUtf8Bytes(targetPath, "targetPath", MAX_TARGET_PATH);
|
|
2215
|
-
requireNonEmptyIfPresent(targetPath, "targetPath");
|
|
2216
|
-
if (targetPath !== undefined && method === "createPortalForUrl") {
|
|
2217
|
-
throw clientValidationError("targetPath: requires an existing resource");
|
|
2218
|
-
}
|
|
2219
|
-
const body = {};
|
|
2220
|
-
const expiresIn = serializeApiDuration(validFor, "validFor", MIN_PORTAL_VALID_FOR_SECONDS);
|
|
2221
|
-
if (expiresIn !== undefined)
|
|
2222
|
-
body.expires_in = expiresIn;
|
|
2223
|
-
if (label !== undefined)
|
|
2224
|
-
body.label = label;
|
|
2225
|
-
if (oneTimeUse !== undefined)
|
|
2226
|
-
body.one_time_use = oneTimeUse;
|
|
2227
|
-
// Explicit 0 means unlimited and must survive body construction.
|
|
2228
|
-
if (maxSessions !== undefined)
|
|
2229
|
-
body.max_sessions = maxSessions;
|
|
2230
|
-
const wireSessionDuration = serializeApiDuration(sessionDuration, "sessionDuration", MIN_SESSION_DURATION_SECONDS);
|
|
2231
|
-
if (wireSessionDuration !== undefined)
|
|
2232
|
-
body.session_duration = wireSessionDuration;
|
|
2233
|
-
if (targetPath !== undefined)
|
|
2234
|
-
body.target_path = targetPath;
|
|
2235
|
-
return body;
|
|
340
|
+
}, meta);
|
|
2236
341
|
}
|
|
2237
342
|
/** qURL API client. */
|
|
2238
343
|
class QURLClient {
|
|
@@ -2240,19 +345,29 @@ class QURLClient {
|
|
|
2240
345
|
apiKey;
|
|
2241
346
|
fetchFn;
|
|
2242
347
|
maxRetries;
|
|
348
|
+
maxResponseBodyBytes;
|
|
2243
349
|
timeout;
|
|
2244
350
|
userAgent;
|
|
2245
351
|
debugFn;
|
|
2246
352
|
constructor(options) {
|
|
2247
353
|
if (!options.apiKey) {
|
|
2248
|
-
throw clientValidationError("apiKey is required");
|
|
354
|
+
throw (0, client_errors_js_1.clientValidationError)("apiKey is required");
|
|
2249
355
|
}
|
|
2250
356
|
// Header-injection guard: `Authorization: Bearer ${apiKey}` would
|
|
2251
357
|
// otherwise be exploitable on a CR/LF-bearing key. Surface as
|
|
2252
358
|
// ValidationError at construction, not as TypeError on first request.
|
|
2253
359
|
if (/[\r\n]/.test(options.apiKey)) {
|
|
2254
|
-
throw clientValidationError("apiKey: must not contain CR/LF characters");
|
|
360
|
+
throw (0, client_errors_js_1.clientValidationError)("apiKey: must not contain CR/LF characters");
|
|
2255
361
|
}
|
|
362
|
+
const responseLimit = options.maxResponseBodyBytes === undefined
|
|
363
|
+
? client_limits_js_1.MAX_RESPONSE_BODY_BYTES
|
|
364
|
+
: options.maxResponseBodyBytes;
|
|
365
|
+
if (!Number.isSafeInteger(responseLimit) ||
|
|
366
|
+
responseLimit < 1 ||
|
|
367
|
+
responseLimit > client_limits_js_1.MAX_CONFIGURABLE_RESPONSE_BODY_BYTES) {
|
|
368
|
+
throw (0, client_errors_js_1.clientValidationError)("maxResponseBodyBytes: must be an integer from 1 through 16777216 bytes");
|
|
369
|
+
}
|
|
370
|
+
this.maxResponseBodyBytes = responseLimit;
|
|
2256
371
|
this.apiKey = options.apiKey;
|
|
2257
372
|
const rawBaseUrl = (0, internal_js_1.stripTrailingSlashes)(options.baseUrl ?? DEFAULT_BASE_URL);
|
|
2258
373
|
// Parse the URL so the loopback exemption matches on the hostname,
|
|
@@ -2265,13 +380,13 @@ class QURLClient {
|
|
|
2265
380
|
parsedBaseUrl = new URL(rawBaseUrl);
|
|
2266
381
|
}
|
|
2267
382
|
catch {
|
|
2268
|
-
throw clientValidationError(`baseUrl: must be a valid URL (got ${JSON.stringify(rawBaseUrl).slice(0, 60)})`);
|
|
383
|
+
throw (0, client_errors_js_1.clientValidationError)(`baseUrl: must be a valid URL (got ${JSON.stringify(rawBaseUrl).slice(0, 60)})`);
|
|
2269
384
|
}
|
|
2270
385
|
// Reject any non-http/https scheme outright (ftp://, file://,
|
|
2271
386
|
// javascript:, ...). The loopback exemption logic below handles
|
|
2272
387
|
// IPv6 bracket form on `URL.hostname` — see the inner comment.
|
|
2273
388
|
if (parsedBaseUrl.protocol !== "http:" && parsedBaseUrl.protocol !== "https:") {
|
|
2274
|
-
throw clientValidationError(`baseUrl: must use http:// or https:// scheme (got ${JSON.stringify(rawBaseUrl).slice(0, 60)})`);
|
|
389
|
+
throw (0, client_errors_js_1.clientValidationError)(`baseUrl: must use http:// or https:// scheme (got ${JSON.stringify(rawBaseUrl).slice(0, 60)})`);
|
|
2275
390
|
}
|
|
2276
391
|
if (parsedBaseUrl.protocol === "http:") {
|
|
2277
392
|
// Node's `URL.hostname` returns IPv6 hosts WITH brackets (e.g.
|
|
@@ -2282,7 +397,7 @@ class QURLClient {
|
|
|
2282
397
|
// `0.0.0.0` is bind-all, not loopback — would route the bearer to any local listener.
|
|
2283
398
|
const isLoopback = host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
2284
399
|
if (!isLoopback) {
|
|
2285
|
-
throw clientValidationError(`baseUrl: must use https:// scheme (got ${JSON.stringify(rawBaseUrl).slice(0, 60)})`);
|
|
400
|
+
throw (0, client_errors_js_1.clientValidationError)(`baseUrl: must use https:// scheme (got ${JSON.stringify(rawBaseUrl).slice(0, 60)})`);
|
|
2286
401
|
}
|
|
2287
402
|
}
|
|
2288
403
|
this.baseUrl = rawBaseUrl;
|
|
@@ -2302,7 +417,7 @@ class QURLClient {
|
|
|
2302
417
|
: DEFAULT_TIMEOUT;
|
|
2303
418
|
const userAgent = options.userAgent ?? `qurl-typescript/${version_js_1.VERSION}`;
|
|
2304
419
|
if (/[\r\n]/.test(userAgent)) {
|
|
2305
|
-
throw clientValidationError("userAgent: must not contain CR/LF characters");
|
|
420
|
+
throw (0, client_errors_js_1.clientValidationError)("userAgent: must not contain CR/LF characters");
|
|
2306
421
|
}
|
|
2307
422
|
this.userAgent = userAgent;
|
|
2308
423
|
if (options.debug === true) {
|
|
@@ -2349,21 +464,21 @@ class QURLClient {
|
|
|
2349
464
|
branch: "non-array",
|
|
2350
465
|
qurls_type: typeof qurls,
|
|
2351
466
|
});
|
|
2352
|
-
return rest;
|
|
467
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(raw, rest);
|
|
2353
468
|
}
|
|
2354
469
|
// null is semantically "no tokens" — silent drop is fine, but surface
|
|
2355
470
|
// via debug so operators spot it if the API starts emitting null.
|
|
2356
471
|
if (qurls === null) {
|
|
2357
472
|
this.log("mapQurlsField: 'qurls' was null; dropping", { branch: "null" });
|
|
2358
|
-
return rest;
|
|
473
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(raw, rest);
|
|
2359
474
|
}
|
|
2360
475
|
if (qurls === undefined) {
|
|
2361
476
|
// Symmetric with null / non-array branches.
|
|
2362
477
|
this.log("mapQurlsField: 'qurls' was undefined; dropping", { branch: "undefined" });
|
|
2363
|
-
return rest;
|
|
478
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(raw, rest);
|
|
2364
479
|
}
|
|
2365
480
|
if (!rest.access_tokens) {
|
|
2366
|
-
return { ...rest, access_tokens: qurls };
|
|
481
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(raw, { ...rest, access_tokens: qurls });
|
|
2367
482
|
}
|
|
2368
483
|
// Server-side bug — drop `qurls` rather than merge. If the arrays
|
|
2369
484
|
// disagree, no consistent reconciliation exists; a silent merge
|
|
@@ -2373,7 +488,7 @@ class QURLClient {
|
|
|
2373
488
|
qurls_count: qurls.length,
|
|
2374
489
|
access_tokens_count: rest.access_tokens.length,
|
|
2375
490
|
});
|
|
2376
|
-
return rest;
|
|
491
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(raw, rest);
|
|
2377
492
|
}
|
|
2378
493
|
/**
|
|
2379
494
|
* Validate the response envelope from POST /v1/qurls/batch and return
|
|
@@ -2397,9 +512,9 @@ class QURLClient {
|
|
|
2397
512
|
* is malformed, additional shape errors don't help debug the upstream
|
|
2398
513
|
* issue.
|
|
2399
514
|
*/
|
|
2400
|
-
validateBatchCreateResponse(
|
|
2401
|
-
const result = envelope.data;
|
|
2402
|
-
const statusSuffix =
|
|
515
|
+
validateBatchCreateResponse(response, requestItemCount) {
|
|
516
|
+
const result = response.envelope.data;
|
|
517
|
+
const statusSuffix = ` (HTTP ${response.status})`;
|
|
2403
518
|
// Thread the server's request_id into the shape-guard error so
|
|
2404
519
|
// operators debugging "unexpected response" tickets have the
|
|
2405
520
|
// correlation handle on the error path, mirroring the success-
|
|
@@ -2409,7 +524,7 @@ class QURLClient {
|
|
|
2409
524
|
// .requestId property AND the message string so a stack trace
|
|
2410
525
|
// pasted into a support ticket carries the correlation handle
|
|
2411
526
|
// without a follow-up round-trip.
|
|
2412
|
-
const requestId = boundedErrorIdentifier(envelope.meta?.request_id);
|
|
527
|
+
const requestId = (0, client_errors_js_1.boundedErrorIdentifier)(response.envelope.meta?.request_id);
|
|
2413
528
|
const requestIdSuffix = requestId !== undefined ? ` [request_id=${requestId}]` : "";
|
|
2414
529
|
// `Number.isInteger` rejects NaN, Infinity, and floats. Combined
|
|
2415
530
|
// with `>= 0`, this rules out pathological proxy bodies (e.g.
|
|
@@ -2422,10 +537,10 @@ class QURLClient {
|
|
|
2422
537
|
!Number.isInteger(result.failed) ||
|
|
2423
538
|
result.failed < 0 ||
|
|
2424
539
|
!Array.isArray(result.results)) {
|
|
2425
|
-
throw unexpectedResponseError(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}${requestIdSuffix}`, { requestId });
|
|
540
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}${requestIdSuffix}`, { requestId });
|
|
2426
541
|
}
|
|
2427
542
|
if (result.succeeded + result.failed !== result.results.length) {
|
|
2428
|
-
throw unexpectedResponseError(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: ` +
|
|
543
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: ` +
|
|
2429
544
|
`counts/results length mismatch (succeeded=${result.succeeded}, ` +
|
|
2430
545
|
`failed=${result.failed}, results.length=${result.results.length})${requestIdSuffix}`, { requestId });
|
|
2431
546
|
}
|
|
@@ -2437,7 +552,7 @@ class QURLClient {
|
|
|
2437
552
|
// index range + dedup checks, this guarantees the seen-indices set is
|
|
2438
553
|
// exactly [0, requestItemCount).
|
|
2439
554
|
if (result.results.length !== requestItemCount) {
|
|
2440
|
-
throw unexpectedResponseError(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: ` +
|
|
555
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: ` +
|
|
2441
556
|
`results.length (${result.results.length}) does not match request item count (${requestItemCount})${requestIdSuffix}`, { requestId });
|
|
2442
557
|
}
|
|
2443
558
|
// Per-entry discriminated-union contract. Reason strings report
|
|
@@ -2448,16 +563,16 @@ class QURLClient {
|
|
|
2448
563
|
// attribution, so fail closed to match this guard's posture.
|
|
2449
564
|
const seenIndices = new Set();
|
|
2450
565
|
for (let i = 0; i < result.results.length; i++) {
|
|
2451
|
-
const reason = batchItemResultValidationReason(result.results[i]);
|
|
566
|
+
const reason = (0, client_resources_js_1.batchItemResultValidationReason)(result.results[i]);
|
|
2452
567
|
if (reason !== null) {
|
|
2453
|
-
throw unexpectedResponseError(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: results[${i}] ${reason}${requestIdSuffix}`, { requestId });
|
|
568
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: results[${i}] ${reason}${requestIdSuffix}`, { requestId });
|
|
2454
569
|
}
|
|
2455
570
|
const entry = result.results[i];
|
|
2456
571
|
if (entry.index >= requestItemCount) {
|
|
2457
|
-
throw unexpectedResponseError(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: results[${i}] index out of range (sent ${requestItemCount} items)${requestIdSuffix}`, { requestId });
|
|
572
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: results[${i}] index out of range (sent ${requestItemCount} items)${requestIdSuffix}`, { requestId });
|
|
2458
573
|
}
|
|
2459
574
|
if (seenIndices.has(entry.index)) {
|
|
2460
|
-
throw unexpectedResponseError(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: results[${i}] duplicate index${requestIdSuffix}`, { requestId });
|
|
575
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response shape from POST /v1/qurls/batch${statusSuffix}: results[${i}] duplicate index${requestIdSuffix}`, { requestId });
|
|
2461
576
|
}
|
|
2462
577
|
seenIndices.add(entry.index);
|
|
2463
578
|
}
|
|
@@ -2473,7 +588,7 @@ class QURLClient {
|
|
|
2473
588
|
pageCount += 1;
|
|
2474
589
|
const page = await loadPage({ ...input, cursor });
|
|
2475
590
|
for (const item of itemsFromPage(page)) {
|
|
2476
|
-
yield item;
|
|
591
|
+
yield (0, response_metadata_js_1.copyResponseMetadata)(page, item);
|
|
2477
592
|
}
|
|
2478
593
|
cursor = page.next_cursor;
|
|
2479
594
|
if (page.has_more === false) {
|
|
@@ -2483,11 +598,11 @@ class QURLClient {
|
|
|
2483
598
|
break;
|
|
2484
599
|
}
|
|
2485
600
|
if (seenCursors.has(cursor)) {
|
|
2486
|
-
throw unexpectedResponseError(`${methodName}: server returned repeated cursor after ${pageCount} auto-pagination pages`, { requestId: page.request_id });
|
|
601
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`${methodName}: server returned repeated cursor after ${pageCount} auto-pagination pages`, { requestId: page.request_id });
|
|
2487
602
|
}
|
|
2488
603
|
seenCursors.add(cursor);
|
|
2489
|
-
if (pageCount >= MAX_AUTO_PAGINATION_PAGES) {
|
|
2490
|
-
throw unexpectedResponseError(`${methodName}: exceeded ${MAX_AUTO_PAGINATION_PAGES} auto-pagination pages without termination`, { requestId: page.request_id });
|
|
604
|
+
if (pageCount >= client_limits_js_1.MAX_AUTO_PAGINATION_PAGES) {
|
|
605
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`${methodName}: exceeded ${client_limits_js_1.MAX_AUTO_PAGINATION_PAGES} auto-pagination pages without termination`, { requestId: page.request_id });
|
|
2491
606
|
}
|
|
2492
607
|
} while (cursor);
|
|
2493
608
|
}
|
|
@@ -2500,7 +615,9 @@ class QURLClient {
|
|
|
2500
615
|
* {@link ProtectedResource.createPortal}:
|
|
2501
616
|
*
|
|
2502
617
|
* ```ts
|
|
2503
|
-
* const resource = await client.protectUrl("https://internal.example.com/dashboard"
|
|
618
|
+
* const resource = await client.protectUrl("https://internal.example.com/dashboard", {
|
|
619
|
+
* name: "Team dashboard",
|
|
620
|
+
* });
|
|
2504
621
|
* const portal = await resource.createPortal({ validFor: "5m" });
|
|
2505
622
|
* share(portal.link);
|
|
2506
623
|
* ```
|
|
@@ -2511,13 +628,15 @@ class QURLClient {
|
|
|
2511
628
|
* {@link createResource}.
|
|
2512
629
|
*/
|
|
2513
630
|
async protectUrl(targetUrl, opts = {}, requestOptions) {
|
|
2514
|
-
requirePortalTargetUrl(targetUrl);
|
|
2515
|
-
requireObjectInput(opts, "protectUrl");
|
|
2516
|
-
requireNoUnknownFields(opts, PROTECT_URL_OPTION_KEYS, "protectUrl");
|
|
631
|
+
(0, client_validation_js_1.requirePortalTargetUrl)(targetUrl);
|
|
632
|
+
(0, client_validation_js_1.requireObjectInput)(opts, "protectUrl");
|
|
633
|
+
(0, client_validation_js_1.requireNoUnknownFields)(opts, client_input_fields_js_1.PROTECT_URL_OPTION_KEYS, "protectUrl");
|
|
2517
634
|
// camelCase options → wire fields; null is treated as omitted, via the
|
|
2518
635
|
// SDK-wide normalizePatchFields convention.
|
|
2519
|
-
const normalized = normalizePatchFields(opts, PROTECT_URL_OPTION_KEYS);
|
|
636
|
+
const normalized = (0, client_validation_js_1.normalizePatchFields)(opts, client_input_fields_js_1.PROTECT_URL_OPTION_KEYS);
|
|
2520
637
|
const body = { target_url: targetUrl };
|
|
638
|
+
if (normalized.name !== undefined)
|
|
639
|
+
body.name = normalized.name;
|
|
2521
640
|
if (normalized.private !== undefined)
|
|
2522
641
|
body.private = normalized.private;
|
|
2523
642
|
if (normalized.consumerAccess !== undefined) {
|
|
@@ -2532,20 +651,20 @@ class QURLClient {
|
|
|
2532
651
|
body.custom_domain = normalized.customDomain;
|
|
2533
652
|
if (normalized.alias !== undefined)
|
|
2534
653
|
body.alias = normalized.alias;
|
|
2535
|
-
validateResourceWriteFields(body, { requireUrlTarget: true });
|
|
654
|
+
(0, client_resources_js_1.validateResourceWriteFields)(body, { requireUrlTarget: true });
|
|
2536
655
|
const response = await this.rawRequest("POST", "/v1/resources", body, {
|
|
2537
656
|
requestOptions,
|
|
2538
657
|
});
|
|
2539
|
-
if (response.
|
|
2540
|
-
throw unexpectedResponseError("protectUrl: expected a resource response body");
|
|
2541
|
-
const resource = response.data;
|
|
2542
|
-
await verifyResourceIdentity(resource, "protectUrl");
|
|
658
|
+
if (response.status === 204)
|
|
659
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("protectUrl: expected a resource response body");
|
|
660
|
+
const resource = withEnvelopeMetadata(response.envelope.data, response.envelope.meta);
|
|
661
|
+
await (0, client_connectors_js_1.verifyResourceIdentity)(resource, "protectUrl");
|
|
2543
662
|
if (normalized.consumerAccess &&
|
|
2544
663
|
(resource.private !== true ||
|
|
2545
|
-
!sameConsumerPolicy(resource.consumer_access, normalized.consumerAccess, response.meta?.found_existing === true))) {
|
|
2546
|
-
throw unexpectedResponseError("protectUrl: private policy was not confirmed");
|
|
664
|
+
!(0, client_resources_js_1.sameConsumerPolicy)(resource.consumer_access, normalized.consumerAccess, response.envelope.meta?.found_existing === true))) {
|
|
665
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("protectUrl: private policy was not confirmed");
|
|
2547
666
|
}
|
|
2548
|
-
return new ProtectedResource(this, resource.crid,
|
|
667
|
+
return new client_portals_js_1.ProtectedResource(this, resource.crid,
|
|
2549
668
|
// Some resource types redact target_url in responses; fall back to
|
|
2550
669
|
// the caller-supplied URL so the handle stays useful.
|
|
2551
670
|
resource.target_url || targetUrl, resource);
|
|
@@ -2566,20 +685,31 @@ class QURLClient {
|
|
|
2566
685
|
* need the full resource details.
|
|
2567
686
|
*/
|
|
2568
687
|
resourceByCrid(id) {
|
|
2569
|
-
requireResourceCrid(id, "resourceByCrid");
|
|
2570
|
-
return new ProtectedResource(this, id);
|
|
688
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "resourceByCrid");
|
|
689
|
+
return new client_portals_js_1.ProtectedResource(this, id);
|
|
2571
690
|
}
|
|
2572
|
-
/**
|
|
2573
|
-
|
|
2574
|
-
|
|
691
|
+
/**
|
|
692
|
+
* Find or create the active qURL Connector resource for an immutable slug.
|
|
693
|
+
*
|
|
694
|
+
* `name` is the public name recipients see. The service requires it when the
|
|
695
|
+
* slug is new (`400 name_required` otherwise) and ignores it on reuse.
|
|
696
|
+
*
|
|
697
|
+
* @param slug - Immutable owner-scoped Connector slug.
|
|
698
|
+
* @param requestOptions - Per-call request options.
|
|
699
|
+
* @param consumerAccess - Private consumer policy for a new resource.
|
|
700
|
+
* @param name - Public resource name; required when the slug is new.
|
|
701
|
+
*/
|
|
702
|
+
async ensureConnectorResource(slug, requestOptions, consumerAccess, name) {
|
|
703
|
+
(0, client_connectors_js_1.requireConnectorSlug)(slug, "ensureConnectorResource");
|
|
704
|
+
(0, client_validation_js_1.requireResourceNameIfPresent)(name);
|
|
2575
705
|
if (consumerAccess !== undefined) {
|
|
2576
|
-
validateResourceWriteFields({ consumer_access: consumerAccess, private: true });
|
|
706
|
+
(0, client_resources_js_1.validateResourceWriteFields)({ consumer_access: consumerAccess, private: true });
|
|
2577
707
|
if (consumerAccess.version !== 1)
|
|
2578
|
-
throw clientValidationError("consumer_access: creation requires version one");
|
|
708
|
+
throw (0, client_errors_js_1.clientValidationError)("consumer_access: creation requires version one");
|
|
2579
709
|
}
|
|
2580
710
|
// Response validation imports the producer's P-256 resource key. Preflight
|
|
2581
711
|
// before POST so a missing runtime capability cannot orphan a committed row.
|
|
2582
|
-
requireConnectorSubtleCrypto("ensureConnectorResource");
|
|
712
|
+
(0, client_connectors_js_1.requireConnectorSubtleCrypto)("ensureConnectorResource");
|
|
2583
713
|
// Keep invalid options in the known pre-dispatch error arm. rawRequest
|
|
2584
714
|
// validates again when building the request, which protects future callers
|
|
2585
715
|
// that do not have this mutation-specific outcome contract.
|
|
@@ -2591,67 +721,67 @@ class QURLClient {
|
|
|
2591
721
|
type: "tunnel",
|
|
2592
722
|
slug,
|
|
2593
723
|
find_or_create: true,
|
|
724
|
+
...(name !== undefined ? { name } : {}),
|
|
2594
725
|
...(consumerAccess ? { private: true, consumer_access: consumerAccess } : {}),
|
|
2595
726
|
}, { requestOptions, retry: false, generateIdempotencyKey: false });
|
|
2596
727
|
// The find-or-create endpoint returns 201 for both newly-created
|
|
2597
728
|
// and found-existing rows; meta.found_existing distinguishes the arms.
|
|
2598
|
-
if (response.
|
|
2599
|
-
throw unexpectedResponseError(`ensureConnectorResource: expected HTTP 201, got ${response.
|
|
729
|
+
if (response.status !== 201) {
|
|
730
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`ensureConnectorResource: expected HTTP 201, got ${response.status ?? "unknown"}`);
|
|
2600
731
|
}
|
|
2601
732
|
if (consumerAccess &&
|
|
2602
|
-
(response.data.private !== true ||
|
|
2603
|
-
!sameConsumerPolicy(response.data.consumer_access, consumerAccess, response.meta?.found_existing === true))) {
|
|
2604
|
-
throw unexpectedResponseError("ensureConnectorResource: private policy was not confirmed");
|
|
733
|
+
(response.envelope.data.private !== true ||
|
|
734
|
+
!(0, client_resources_js_1.sameConsumerPolicy)(response.envelope.data.consumer_access, consumerAccess, response.envelope.meta?.found_existing === true))) {
|
|
735
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("ensureConnectorResource: private policy was not confirmed");
|
|
2605
736
|
}
|
|
2606
|
-
resource = await parseConnectorResource(this, response.data, "ensureConnectorResource", {
|
|
737
|
+
resource = await (0, client_connectors_js_1.parseConnectorResource)(this, response.envelope.data, "ensureConnectorResource", {
|
|
2607
738
|
slug,
|
|
2608
739
|
});
|
|
2609
740
|
}
|
|
2610
741
|
catch (error) {
|
|
2611
|
-
classifyConnectorMutationFailure("ensure", error);
|
|
742
|
+
(0, client_connectors_js_1.classifyConnectorMutationFailure)("ensure", error);
|
|
2612
743
|
}
|
|
2613
|
-
if (response.meta !== undefined &&
|
|
2614
|
-
response.meta !== null &&
|
|
2615
|
-
(typeof response.meta !== "object" || Array.isArray(response.meta))) {
|
|
2616
|
-
classifyConnectorMutationFailure("ensure", unexpectedResponseError("ensureConnectorResource: response has invalid meta"));
|
|
744
|
+
if (response.envelope.meta !== undefined &&
|
|
745
|
+
response.envelope.meta !== null &&
|
|
746
|
+
(typeof response.envelope.meta !== "object" || Array.isArray(response.envelope.meta))) {
|
|
747
|
+
(0, client_connectors_js_1.classifyConnectorMutationFailure)("ensure", (0, client_errors_js_1.unexpectedResponseError)("ensureConnectorResource: response has invalid meta"));
|
|
2617
748
|
}
|
|
2618
|
-
const foundExisting = response.meta
|
|
2619
|
-
?.found_existing;
|
|
749
|
+
const foundExisting = response.envelope.meta?.found_existing;
|
|
2620
750
|
if (foundExisting === undefined || foundExisting === null) {
|
|
2621
|
-
throw unexpectedResponseError("ensureConnectorResource: response is missing meta.found_existing; the validated resource exists, so reconcile by slug before retrying");
|
|
751
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("ensureConnectorResource: response is missing meta.found_existing; the validated resource exists, so reconcile by slug before retrying");
|
|
2622
752
|
}
|
|
2623
753
|
if (typeof foundExisting !== "boolean") {
|
|
2624
|
-
classifyConnectorMutationFailure("ensure", unexpectedResponseError("ensureConnectorResource: response has invalid meta.found_existing"));
|
|
754
|
+
(0, client_connectors_js_1.classifyConnectorMutationFailure)("ensure", (0, client_errors_js_1.unexpectedResponseError)("ensureConnectorResource: response has invalid meta.found_existing"));
|
|
2625
755
|
}
|
|
2626
|
-
return { resource, foundExisting };
|
|
756
|
+
return withEnvelopeMetadata({ resource: withEnvelopeMetadata(resource, response.envelope.meta), foundExisting }, response.envelope.meta);
|
|
2627
757
|
}
|
|
2628
758
|
/** Fetch a qURL Connector resource by its CRID. */
|
|
2629
759
|
async getConnectorResource(crid) {
|
|
2630
|
-
requireResourceCrid(crid, "getConnectorResource");
|
|
2631
|
-
requireConnectorSubtleCrypto("getConnectorResource");
|
|
2632
|
-
const { data,
|
|
2633
|
-
if (
|
|
2634
|
-
throw unexpectedResponseError(`getConnectorResource: expected HTTP 200, got ${
|
|
760
|
+
(0, client_paths_js_1.requireResourceCrid)(crid, "getConnectorResource");
|
|
761
|
+
(0, client_connectors_js_1.requireConnectorSubtleCrypto)("getConnectorResource");
|
|
762
|
+
const { envelope: { data, meta }, status: httpStatus, } = await this.rawRequest("GET", `/v1/resources/${encodeURIComponent(crid)}`, undefined, { retry: false });
|
|
763
|
+
if (httpStatus !== 200) {
|
|
764
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`getConnectorResource: expected HTTP 200, got ${httpStatus ?? "unknown"}`);
|
|
2635
765
|
}
|
|
2636
|
-
return parseConnectorResource(this, data?.resource, "getConnectorResource", {
|
|
766
|
+
return withEnvelopeMetadata(await (0, client_connectors_js_1.parseConnectorResource)(this, data?.resource, "getConnectorResource", {
|
|
2637
767
|
crid,
|
|
2638
768
|
revokedIsLifecycleError: true,
|
|
2639
|
-
});
|
|
769
|
+
}), meta);
|
|
2640
770
|
}
|
|
2641
771
|
/** Fetch the single active qURL Connector resource for an immutable slug. */
|
|
2642
772
|
async getConnectorResourceBySlug(slug) {
|
|
2643
|
-
requireConnectorSlug(slug, "getConnectorResourceBySlug");
|
|
773
|
+
(0, client_connectors_js_1.requireConnectorSlug)(slug, "getConnectorResourceBySlug");
|
|
2644
774
|
// Parsing validates the returned P-256 resource key; avoid dispatching a
|
|
2645
775
|
// read that this runtime cannot safely consume.
|
|
2646
|
-
requireConnectorSubtleCrypto("getConnectorResourceBySlug");
|
|
776
|
+
(0, client_connectors_js_1.requireConnectorSubtleCrypto)("getConnectorResourceBySlug");
|
|
2647
777
|
// The slug point lookup is intrinsically active-only. It also
|
|
2648
778
|
// rejects combining `slug` with `status`, so this query must stay slug-only.
|
|
2649
|
-
const { data,
|
|
2650
|
-
if (
|
|
2651
|
-
throw unexpectedResponseError(`getConnectorResourceBySlug: expected HTTP 200, got ${
|
|
779
|
+
const { envelope: { data, meta }, status: httpStatus, } = await this.rawRequest("GET", `/v1/resources?slug=${encodeURIComponent(slug)}`, undefined, { retry: false });
|
|
780
|
+
if (httpStatus !== 200) {
|
|
781
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`getConnectorResourceBySlug: expected HTTP 200, got ${httpStatus ?? "unknown"}`);
|
|
2652
782
|
}
|
|
2653
783
|
if (!Array.isArray(data)) {
|
|
2654
|
-
throw unexpectedResponseError("getConnectorResourceBySlug: response has missing or invalid data");
|
|
784
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("getConnectorResourceBySlug: response has missing or invalid data");
|
|
2655
785
|
}
|
|
2656
786
|
if (data.length === 0) {
|
|
2657
787
|
throw new errors_js_1.NotFoundError({
|
|
@@ -2669,7 +799,7 @@ class QURLClient {
|
|
|
2669
799
|
detail: `getConnectorResourceBySlug: expected one active resource, got ${data.length}`,
|
|
2670
800
|
});
|
|
2671
801
|
}
|
|
2672
|
-
return parseConnectorResource(this, data[0], "getConnectorResourceBySlug", { slug });
|
|
802
|
+
return withEnvelopeMetadata(await (0, client_connectors_js_1.parseConnectorResource)(this, data[0], "getConnectorResourceBySlug", { slug }), meta);
|
|
2673
803
|
}
|
|
2674
804
|
/**
|
|
2675
805
|
* Revoke a qURL Connector resource by CRID.
|
|
@@ -2678,12 +808,12 @@ class QURLClient {
|
|
|
2678
808
|
* outcome-unknown failure, reconcile by ID before issuing a deliberate retry.
|
|
2679
809
|
*/
|
|
2680
810
|
async deleteConnectorResource(crid) {
|
|
2681
|
-
requireResourceCrid(crid, "deleteConnectorResource");
|
|
811
|
+
(0, client_paths_js_1.requireResourceCrid)(crid, "deleteConnectorResource");
|
|
2682
812
|
try {
|
|
2683
813
|
await this.requestNoContent(`/v1/resources/${encodeURIComponent(crid)}`);
|
|
2684
814
|
}
|
|
2685
815
|
catch (error) {
|
|
2686
|
-
classifyConnectorMutationFailure("delete", error);
|
|
816
|
+
(0, client_connectors_js_1.classifyConnectorMutationFailure)("delete", error);
|
|
2687
817
|
}
|
|
2688
818
|
}
|
|
2689
819
|
/**
|
|
@@ -2708,19 +838,19 @@ class QURLClient {
|
|
|
2708
838
|
if (typeof resource === "string") {
|
|
2709
839
|
resourceId = resource;
|
|
2710
840
|
}
|
|
2711
|
-
else if (resource instanceof ProtectedResource) {
|
|
2712
|
-
if (!ProtectedResource.isBoundTo(resource, this)) {
|
|
2713
|
-
throw clientValidationError("createPortal: resource is bound to a different client");
|
|
841
|
+
else if (resource instanceof client_portals_js_1.ProtectedResource) {
|
|
842
|
+
if (!client_portals_js_1.ProtectedResource.isBoundTo(resource, this)) {
|
|
843
|
+
throw (0, client_errors_js_1.clientValidationError)("createPortal: resource is bound to a different client");
|
|
2714
844
|
}
|
|
2715
845
|
resourceId = resource.crid;
|
|
2716
846
|
}
|
|
2717
847
|
else {
|
|
2718
|
-
throw clientValidationError(`createPortal: resource must be a ProtectedResource handle or a CRID string (got ${describeShape(resource)})`);
|
|
848
|
+
throw (0, client_errors_js_1.clientValidationError)(`createPortal: resource must be a ProtectedResource handle or a CRID string (got ${(0, client_validation_js_1.describeShape)(resource)})`);
|
|
2719
849
|
}
|
|
2720
|
-
requireResourceCrid(resourceId, "createPortal");
|
|
2721
|
-
const body = buildCreatePortalBody(opts, "createPortal");
|
|
850
|
+
(0, client_paths_js_1.requireResourceCrid)(resourceId, "createPortal");
|
|
851
|
+
const body = (0, client_portals_js_1.buildCreatePortalBody)(opts, "createPortal");
|
|
2722
852
|
const data = await this.request("POST", `/v1/resources/${encodeURIComponent(resourceId)}/qurls`, body, requestOptions);
|
|
2723
|
-
return parsePortal(data, "createPortal", resourceId);
|
|
853
|
+
return (0, client_portals_js_1.parsePortal)(data, "createPortal", resourceId, body.target_path);
|
|
2724
854
|
}
|
|
2725
855
|
/**
|
|
2726
856
|
* Protect `targetUrl` and mint a portal in one API call.
|
|
@@ -2736,17 +866,17 @@ class QURLClient {
|
|
|
2736
866
|
* equivalent: {@link create}.
|
|
2737
867
|
*/
|
|
2738
868
|
async createPortalForUrl(targetUrl, opts = {}, requestOptions) {
|
|
2739
|
-
requirePortalTargetUrl(targetUrl);
|
|
869
|
+
(0, client_validation_js_1.requirePortalTargetUrl)(targetUrl);
|
|
2740
870
|
const body = {
|
|
2741
871
|
target_url: targetUrl,
|
|
2742
|
-
...buildCreatePortalBody(opts, "createPortalForUrl"),
|
|
872
|
+
...(0, client_portals_js_1.buildCreatePortalBody)(opts, "createPortalForUrl"),
|
|
2743
873
|
};
|
|
2744
874
|
const data = await this.request("POST", "/v1/qurls", body, requestOptions);
|
|
2745
|
-
const portal = await parsePortal(data, "createPortalForUrl");
|
|
2746
|
-
return {
|
|
875
|
+
const portal = await (0, client_portals_js_1.parsePortal)(data, "createPortalForUrl");
|
|
876
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(data, {
|
|
2747
877
|
portal,
|
|
2748
|
-
resource: new ProtectedResource(this, portal.crid, targetUrl),
|
|
2749
|
-
};
|
|
878
|
+
resource: (0, response_metadata_js_1.copyResponseMetadata)(data, new client_portals_js_1.ProtectedResource(this, portal.crid, targetUrl)),
|
|
879
|
+
});
|
|
2750
880
|
}
|
|
2751
881
|
/**
|
|
2752
882
|
* Open a qURL link programmatically and return the reachable resource.
|
|
@@ -2771,16 +901,16 @@ class QURLClient {
|
|
|
2771
901
|
* credentials.
|
|
2772
902
|
*/
|
|
2773
903
|
async enterPortal(qurlLink, requestOptions) {
|
|
2774
|
-
const token = extractAccessToken(qurlLink);
|
|
904
|
+
const token = (0, client_portals_js_1.extractAccessToken)(qurlLink);
|
|
2775
905
|
const resolved = await this.resolve(token, requestOptions);
|
|
2776
906
|
if (typeof resolved?.target_url !== "string" || resolved.target_url === "") {
|
|
2777
|
-
throw unexpectedResponseError("enterPortal: access was granted but the API returned no resource URL");
|
|
907
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("enterPortal: access was granted but the API returned no resource URL");
|
|
2778
908
|
}
|
|
2779
|
-
return {
|
|
909
|
+
return (0, response_metadata_js_1.copyResponseMetadata)(resolved, {
|
|
2780
910
|
resourceUrl: resolved.target_url,
|
|
2781
911
|
openSeconds: resolved.access_grant?.expires_in ?? 0,
|
|
2782
912
|
resourcePublicKey: resolved.resource_id,
|
|
2783
|
-
};
|
|
913
|
+
});
|
|
2784
914
|
}
|
|
2785
915
|
// --- Public API ---
|
|
2786
916
|
/**
|
|
@@ -2790,7 +920,7 @@ class QURLClient {
|
|
|
2790
920
|
* returns a {@link Portal} plus a reusable resource handle).
|
|
2791
921
|
*/
|
|
2792
922
|
async create(input, options) {
|
|
2793
|
-
validateCreateInput(input);
|
|
923
|
+
(0, client_resources_js_1.validateCreateInput)(input);
|
|
2794
924
|
return this.request("POST", "/v1/qurls", input, options);
|
|
2795
925
|
}
|
|
2796
926
|
/**
|
|
@@ -2804,6 +934,8 @@ class QURLClient {
|
|
|
2804
934
|
* populated `BatchCreateOutput` body in this case; whitelisted via
|
|
2805
935
|
* `passthroughStatuses` so the structured per-item errors aren't
|
|
2806
936
|
* swallowed by the generic error path.
|
|
937
|
+
* HTTP 400 is the documented all-item validation failure. HTTP 422 is
|
|
938
|
+
* not a batch passthrough status and follows normal error handling.
|
|
2807
939
|
*
|
|
2808
940
|
* Callers that only catch thrown errors will **silently miss partial
|
|
2809
941
|
* failures**. Always branch on `result.failed > 0` before treating the
|
|
@@ -2851,17 +983,17 @@ class QURLClient {
|
|
|
2851
983
|
// for non-conforming inputs. Without this guard, `batchCreate({} as
|
|
2852
984
|
// any)` would throw a raw TypeError on `.length`. Match the pattern.
|
|
2853
985
|
if (!input || !Array.isArray(input.items)) {
|
|
2854
|
-
throw clientValidationError("batchCreate: items must be an array");
|
|
986
|
+
throw (0, client_errors_js_1.clientValidationError)("batchCreate: items must be an array");
|
|
2855
987
|
}
|
|
2856
988
|
// Size checks are fail-fast (binary, no useful aggregation), per-item
|
|
2857
989
|
// validation below is collect-all (multiple bad items aggregate into
|
|
2858
990
|
// one throw). The asymmetry is deliberate: there's nothing to combine
|
|
2859
991
|
// for empty input or oversized batches — the size is wrong or it isn't.
|
|
2860
992
|
if (input.items.length === 0) {
|
|
2861
|
-
throw clientValidationError("batchCreate requires at least 1 item");
|
|
993
|
+
throw (0, client_errors_js_1.clientValidationError)("batchCreate requires at least 1 item");
|
|
2862
994
|
}
|
|
2863
|
-
if (input.items.length > MAX_BATCH_ITEMS) {
|
|
2864
|
-
throw clientValidationError(`batchCreate accepts at most ${MAX_BATCH_ITEMS} items, got ${input.items.length}`);
|
|
995
|
+
if (input.items.length > client_limits_js_1.MAX_BATCH_ITEMS) {
|
|
996
|
+
throw (0, client_errors_js_1.clientValidationError)(`batchCreate accepts at most ${client_limits_js_1.MAX_BATCH_ITEMS} items, got ${input.items.length}`);
|
|
2865
997
|
}
|
|
2866
998
|
// Collect ALL per-item validation errors in one pass (not fail-fast)
|
|
2867
999
|
// so callers see every problem without fix-re-run-repeat. Kept inline
|
|
@@ -2870,7 +1002,7 @@ class QURLClient {
|
|
|
2870
1002
|
const perItemErrors = [];
|
|
2871
1003
|
for (let i = 0; i < input.items.length; i++) {
|
|
2872
1004
|
try {
|
|
2873
|
-
validateCreateInput(input.items[i]);
|
|
1005
|
+
(0, client_resources_js_1.validateCreateInput)(input.items[i]);
|
|
2874
1006
|
}
|
|
2875
1007
|
catch (err) {
|
|
2876
1008
|
if (err instanceof errors_js_1.ValidationError) {
|
|
@@ -2887,18 +1019,18 @@ class QURLClient {
|
|
|
2887
1019
|
// Without this asymmetry, the same `; ` at both levels makes
|
|
2888
1020
|
// it ambiguous which separator is "next item" vs "next field
|
|
2889
1021
|
// within item". Mirrors the JSDoc example shape.
|
|
2890
|
-
throw clientValidationError(`batchCreate: ${perItemErrors.join(" | ")}`);
|
|
1022
|
+
throw (0, client_errors_js_1.clientValidationError)(`batchCreate: ${perItemErrors.join(" | ")}`);
|
|
2891
1023
|
}
|
|
2892
1024
|
// 400 carries per-item errors (see rawRequest JSDoc). Use rawRequest
|
|
2893
1025
|
// directly (not `this.request`) so we can read `meta.request_id`
|
|
2894
1026
|
// from the envelope and propagate it into the returned
|
|
2895
1027
|
// BatchCreateOutput — consumers filing support tickets on partial
|
|
2896
1028
|
// or total batch failures need the correlation ID.
|
|
2897
|
-
const
|
|
1029
|
+
const response = await this.rawRequest("POST", "/v1/qurls/batch", input, {
|
|
2898
1030
|
passthroughStatuses: BATCH_PASSTHROUGH_STATUSES,
|
|
2899
1031
|
requestOptions: options,
|
|
2900
1032
|
});
|
|
2901
|
-
const result = this.validateBatchCreateResponse(
|
|
1033
|
+
const result = this.validateBatchCreateResponse(response, input.items.length);
|
|
2902
1034
|
// Attach the server request_id from the envelope meta without
|
|
2903
1035
|
// mutating `result` (which is the parsed JSON body). Mirrors the
|
|
2904
1036
|
// non-mutating style used elsewhere (e.g. `mapQurlsField`). In the
|
|
@@ -2915,7 +1047,7 @@ class QURLClient {
|
|
|
2915
1047
|
// as the source, and `meta.request_id` is the canonical envelope
|
|
2916
1048
|
// field. The data-side variant would be a wire-format duplication
|
|
2917
1049
|
// we don't want to silently honor.
|
|
2918
|
-
const requestId = envelope.meta?.request_id;
|
|
1050
|
+
const requestId = response.envelope.meta?.request_id;
|
|
2919
1051
|
// Both-fields-present and data-only-present are both worth surfacing
|
|
2920
1052
|
// via debug — the former because meta wins silently, the latter
|
|
2921
1053
|
// because it suggests an envelope-shape transition. Data-side value
|
|
@@ -2937,7 +1069,7 @@ class QURLClient {
|
|
|
2937
1069
|
});
|
|
2938
1070
|
}
|
|
2939
1071
|
}
|
|
2940
|
-
return requestId !== undefined ? { ...result, request_id: requestId } : result;
|
|
1072
|
+
return withEnvelopeMetadata(requestId !== undefined ? { ...result, request_id: requestId } : result, response.envelope.meta);
|
|
2941
1073
|
}
|
|
2942
1074
|
/**
|
|
2943
1075
|
* Start one durable delegated qURL batch. The SDK sends this mutation once.
|
|
@@ -2945,12 +1077,12 @@ class QURLClient {
|
|
|
2945
1077
|
* semantic body.
|
|
2946
1078
|
*/
|
|
2947
1079
|
async createDelegatedQurlBatch(input, options) {
|
|
2948
|
-
validateCreateDelegatedQurlBatchInput(input);
|
|
2949
|
-
validateDelegatedBatchCreateOptions(options);
|
|
1080
|
+
(0, client_delegated_js_1.validateCreateDelegatedQurlBatchInput)(input);
|
|
1081
|
+
(0, client_delegated_js_1.validateDelegatedBatchCreateOptions)(options);
|
|
2950
1082
|
let dispatched = false;
|
|
2951
1083
|
let acceptedBatchId;
|
|
2952
1084
|
try {
|
|
2953
|
-
const
|
|
1085
|
+
const response = await this.rawRequest("POST", "/v1/delegated-qurl-batches", input, {
|
|
2954
1086
|
requestOptions: options,
|
|
2955
1087
|
retry: false,
|
|
2956
1088
|
generateIdempotencyKey: false,
|
|
@@ -2959,25 +1091,25 @@ class QURLClient {
|
|
|
2959
1091
|
dispatched = true;
|
|
2960
1092
|
},
|
|
2961
1093
|
});
|
|
2962
|
-
const status =
|
|
1094
|
+
const status = response.status ?? 0;
|
|
2963
1095
|
if (status !== 202) {
|
|
2964
|
-
throw delegatedResponseError(status, "create returned an unexpected success status");
|
|
1096
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "create returned an unexpected success status");
|
|
2965
1097
|
}
|
|
2966
|
-
const rawData = envelope.data;
|
|
1098
|
+
const rawData = response.envelope.data;
|
|
2967
1099
|
if (typeof rawData === "object" && rawData !== null && !Array.isArray(rawData)) {
|
|
2968
1100
|
const candidate = rawData.batch_id;
|
|
2969
|
-
if (typeof candidate === "string" && DELEGATED_BATCH_ID_PATTERN.test(candidate)) {
|
|
1101
|
+
if (typeof candidate === "string" && client_delegated_js_1.DELEGATED_BATCH_ID_PATTERN.test(candidate)) {
|
|
2970
1102
|
acceptedBatchId = candidate;
|
|
2971
1103
|
}
|
|
2972
1104
|
}
|
|
2973
|
-
const { data, requestId } = delegatedEnvelope(
|
|
2974
|
-
const base = delegatedBatchBase(data, status, requestId);
|
|
1105
|
+
const { data, requestId } = (0, client_delegated_js_1.delegatedEnvelope)(response, status);
|
|
1106
|
+
const base = (0, client_delegated_js_1.delegatedBatchBase)(data, status, requestId);
|
|
2975
1107
|
if (data.status !== "queued" || base.itemCount !== input.grants.length) {
|
|
2976
|
-
throw delegatedResponseError(status, "acceptance data is inconsistent", requestId);
|
|
1108
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "acceptance data is inconsistent", requestId);
|
|
2977
1109
|
}
|
|
2978
|
-
const responseHeaders = delegatedResponseHeaders(
|
|
2979
|
-
const location = validateDelegatedLocation(requiredDelegatedHeader(
|
|
2980
|
-
return {
|
|
1110
|
+
const responseHeaders = (0, client_delegated_js_1.delegatedResponseHeaders)(response, status, requestId);
|
|
1111
|
+
const location = (0, client_delegated_js_1.validateDelegatedLocation)((0, client_delegated_js_1.requiredDelegatedHeader)(response, "Location", status, requestId), this.baseUrl, base.batchId, status, requestId);
|
|
1112
|
+
return withEnvelopeMetadata({
|
|
2981
1113
|
http_status: 202,
|
|
2982
1114
|
batch_id: base.batchId,
|
|
2983
1115
|
status: "queued",
|
|
@@ -2987,12 +1119,12 @@ class QURLClient {
|
|
|
2987
1119
|
retry_after: responseHeaders.retryAfter,
|
|
2988
1120
|
location,
|
|
2989
1121
|
request_id: requestId,
|
|
2990
|
-
};
|
|
1122
|
+
}, response.envelope.meta);
|
|
2991
1123
|
}
|
|
2992
1124
|
catch (error) {
|
|
2993
1125
|
if (!dispatched)
|
|
2994
1126
|
throw error;
|
|
2995
|
-
classifyDelegatedBatchCreateFailure(error, acceptedBatchId);
|
|
1127
|
+
(0, client_delegated_js_1.classifyDelegatedBatchCreateFailure)(error, acceptedBatchId);
|
|
2996
1128
|
}
|
|
2997
1129
|
}
|
|
2998
1130
|
/**
|
|
@@ -3000,45 +1132,45 @@ class QURLClient {
|
|
|
3000
1132
|
* callers retain control of the poll count and total deadline.
|
|
3001
1133
|
*/
|
|
3002
1134
|
async getDelegatedQurlBatch(batchId, options) {
|
|
3003
|
-
if (typeof batchId !== "string" || !DELEGATED_BATCH_ID_PATTERN.test(batchId)) {
|
|
3004
|
-
throw clientValidationError("getDelegatedQurlBatch: batchId must match dqb_ plus 22 URL-safe characters");
|
|
1135
|
+
if (typeof batchId !== "string" || !client_delegated_js_1.DELEGATED_BATCH_ID_PATTERN.test(batchId)) {
|
|
1136
|
+
throw (0, client_errors_js_1.clientValidationError)("getDelegatedQurlBatch: batchId must match dqb_ plus 22 URL-safe characters");
|
|
3005
1137
|
}
|
|
3006
|
-
validateGetDelegatedQurlBatchOptions(options);
|
|
3007
|
-
const
|
|
1138
|
+
(0, client_delegated_js_1.validateGetDelegatedQurlBatchOptions)(options);
|
|
1139
|
+
const response = await this.rawRequest("GET", `/v1/delegated-qurl-batches/${encodeURIComponent(batchId)}`, undefined, {
|
|
3008
1140
|
passthroughStatuses: DELEGATED_BATCH_PASSTHROUGH_STATUSES,
|
|
3009
1141
|
allowEmptyPassthroughBody: true,
|
|
3010
1142
|
ifNoneMatch: options?.etag,
|
|
3011
1143
|
retry: false,
|
|
3012
1144
|
captureResponseHeaders: true,
|
|
3013
1145
|
});
|
|
3014
|
-
const status =
|
|
1146
|
+
const status = response.status ?? 0;
|
|
3015
1147
|
if (status === 304) {
|
|
3016
|
-
if (
|
|
3017
|
-
throw delegatedResponseError(status, "304 response is inconsistent");
|
|
1148
|
+
if (response.bodyEmpty !== true || options?.etag === undefined) {
|
|
1149
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "304 response is inconsistent");
|
|
3018
1150
|
}
|
|
3019
|
-
const responseHeaders = delegatedResponseHeaders(
|
|
1151
|
+
const responseHeaders = (0, client_delegated_js_1.delegatedResponseHeaders)(response, status);
|
|
3020
1152
|
if (responseHeaders.etag !== undefined && responseHeaders.etag !== options.etag) {
|
|
3021
|
-
throw delegatedResponseError(status, "304 response changed the ETag");
|
|
1153
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "304 response changed the ETag");
|
|
3022
1154
|
}
|
|
3023
|
-
return {
|
|
1155
|
+
return withEnvelopeMetadata({
|
|
3024
1156
|
http_status: 304,
|
|
3025
1157
|
etag: responseHeaders.etag ?? options.etag,
|
|
3026
1158
|
...(responseHeaders.retryAfter === undefined
|
|
3027
1159
|
? {}
|
|
3028
1160
|
: { retry_after: responseHeaders.retryAfter }),
|
|
3029
|
-
};
|
|
1161
|
+
}, response.envelope.meta);
|
|
3030
1162
|
}
|
|
3031
1163
|
if (status !== 200 && status !== 202) {
|
|
3032
|
-
throw delegatedResponseError(status, "read returned an unexpected success status");
|
|
1164
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "read returned an unexpected success status");
|
|
3033
1165
|
}
|
|
3034
|
-
const { data, requestId } = delegatedEnvelope(
|
|
3035
|
-
const base = delegatedBatchBase(data, status, requestId, batchId);
|
|
1166
|
+
const { data, requestId } = (0, client_delegated_js_1.delegatedEnvelope)(response, status);
|
|
1167
|
+
const base = (0, client_delegated_js_1.delegatedBatchBase)(data, status, requestId, batchId);
|
|
3036
1168
|
if (status === 202) {
|
|
3037
1169
|
if (data.status !== "queued" && data.status !== "running") {
|
|
3038
|
-
throw delegatedResponseError(status, "pending data has a terminal status", requestId);
|
|
1170
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "pending data has a terminal status", requestId);
|
|
3039
1171
|
}
|
|
3040
|
-
const responseHeaders = delegatedResponseHeaders(
|
|
3041
|
-
return {
|
|
1172
|
+
const responseHeaders = (0, client_delegated_js_1.delegatedResponseHeaders)(response, status, requestId);
|
|
1173
|
+
return withEnvelopeMetadata({
|
|
3042
1174
|
http_status: 202,
|
|
3043
1175
|
batch_id: base.batchId,
|
|
3044
1176
|
status: data.status,
|
|
@@ -3047,19 +1179,19 @@ class QURLClient {
|
|
|
3047
1179
|
etag: responseHeaders.etag,
|
|
3048
1180
|
retry_after: responseHeaders.retryAfter,
|
|
3049
1181
|
request_id: requestId,
|
|
3050
|
-
};
|
|
1182
|
+
}, response.envelope.meta);
|
|
3051
1183
|
}
|
|
3052
1184
|
if (data.status !== "succeeded" &&
|
|
3053
1185
|
data.status !== "partially_failed" &&
|
|
3054
1186
|
data.status !== "failed") {
|
|
3055
|
-
throw delegatedResponseError(status, "terminal data has a non-terminal status", requestId);
|
|
1187
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "terminal data has a non-terminal status", requestId);
|
|
3056
1188
|
}
|
|
3057
1189
|
let results;
|
|
3058
1190
|
try {
|
|
3059
|
-
if (!isUtcTimestamp(data.completed_at)) {
|
|
3060
|
-
throw delegatedResponseError(status, "terminal data has invalid completed_at", requestId);
|
|
1191
|
+
if (!(0, client_delegated_js_1.isUtcTimestamp)(data.completed_at)) {
|
|
1192
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "terminal data has invalid completed_at", requestId);
|
|
3061
1193
|
}
|
|
3062
|
-
results = validateDelegatedBatchResults(data.results, base.itemCount, data.status, status, requestId);
|
|
1194
|
+
results = (0, client_delegated_js_1.validateDelegatedBatchResults)(data.results, base.itemCount, data.status, status, requestId);
|
|
3063
1195
|
}
|
|
3064
1196
|
catch (error) {
|
|
3065
1197
|
const partialQurlIds = Array.isArray(data.results)
|
|
@@ -3068,7 +1200,7 @@ class QURLClient {
|
|
|
3068
1200
|
typeof item === "object" &&
|
|
3069
1201
|
item.status === "succeeded" &&
|
|
3070
1202
|
typeof item.qurl?.qurl_id === "string" &&
|
|
3071
|
-
DELEGATED_QURL_ID_PATTERN.test(item.qurl.qurl_id)
|
|
1203
|
+
client_delegated_js_1.DELEGATED_QURL_ID_PATTERN.test(item.qurl.qurl_id)
|
|
3072
1204
|
? [item.qurl.qurl_id]
|
|
3073
1205
|
: [])),
|
|
3074
1206
|
]
|
|
@@ -3083,17 +1215,17 @@ class QURLClient {
|
|
|
3083
1215
|
// This is the caller's only copy of the bearer links. Do not discard a
|
|
3084
1216
|
// valid terminal body because an intermediary removed a cache validator;
|
|
3085
1217
|
// qurl-service owns the required no-store response policy.
|
|
3086
|
-
const cacheControl =
|
|
3087
|
-
if (!hasDelegatedNoStore(cacheControl)) {
|
|
1218
|
+
const cacheControl = response.headers?.get("Cache-Control") ?? null;
|
|
1219
|
+
if (!(0, client_delegated_js_1.hasDelegatedNoStore)(cacheControl)) {
|
|
3088
1220
|
this.log("delegated batch terminal response is missing private, no-store", {
|
|
3089
1221
|
batch_id: base.batchId,
|
|
3090
1222
|
});
|
|
3091
1223
|
}
|
|
3092
|
-
const etag =
|
|
3093
|
-
const terminalEtag = etag !== undefined && etag.length <= MAX_DELEGATED_ETAG && STRONG_ETAG_PATTERN.test(etag)
|
|
1224
|
+
const etag = response.headers?.get("ETag") ?? undefined;
|
|
1225
|
+
const terminalEtag = etag !== undefined && etag.length <= client_limits_js_1.MAX_DELEGATED_ETAG && client_delegated_js_1.STRONG_ETAG_PATTERN.test(etag)
|
|
3094
1226
|
? etag
|
|
3095
1227
|
: undefined;
|
|
3096
|
-
return {
|
|
1228
|
+
return withEnvelopeMetadata({
|
|
3097
1229
|
http_status: 200,
|
|
3098
1230
|
batch_id: base.batchId,
|
|
3099
1231
|
status: data.status,
|
|
@@ -3103,28 +1235,28 @@ class QURLClient {
|
|
|
3103
1235
|
results,
|
|
3104
1236
|
...(terminalEtag === undefined ? {} : { etag: terminalEtag }),
|
|
3105
1237
|
request_id: requestId,
|
|
3106
|
-
};
|
|
1238
|
+
}, response.envelope.meta);
|
|
3107
1239
|
}
|
|
3108
1240
|
/** Read owner-visible metadata for one delegated qURL. */
|
|
3109
1241
|
async getDelegatedQurl(qurlId) {
|
|
3110
|
-
if (typeof qurlId !== "string" || !DELEGATED_QURL_ID_PATTERN.test(qurlId)) {
|
|
3111
|
-
throw clientValidationError("getDelegatedQurl: qurlId must match q_ plus 11 lowercase hexadecimal characters");
|
|
1242
|
+
if (typeof qurlId !== "string" || !client_delegated_js_1.DELEGATED_QURL_ID_PATTERN.test(qurlId)) {
|
|
1243
|
+
throw (0, client_errors_js_1.clientValidationError)("getDelegatedQurl: qurlId must match q_ plus 11 lowercase hexadecimal characters");
|
|
3112
1244
|
}
|
|
3113
|
-
const
|
|
3114
|
-
const status =
|
|
1245
|
+
const response = await this.rawRequest("GET", `/v1/delegated-qurls/${encodeURIComponent(qurlId)}`);
|
|
1246
|
+
const status = response.status ?? 0;
|
|
3115
1247
|
if (status !== 200) {
|
|
3116
|
-
throw delegatedResponseError(status, "read returned an unexpected success status");
|
|
1248
|
+
throw (0, client_delegated_js_1.delegatedResponseError)(status, "read returned an unexpected success status");
|
|
3117
1249
|
}
|
|
3118
|
-
const { data, requestId } = delegatedEnvelope(
|
|
3119
|
-
return validateDelegatedQurl(data, qurlId, status, requestId);
|
|
1250
|
+
const { data, requestId } = (0, client_delegated_js_1.delegatedEnvelope)(response, status);
|
|
1251
|
+
return withEnvelopeMetadata((0, client_delegated_js_1.validateDelegatedQurl)(data, qurlId, status, requestId), response.envelope.meta);
|
|
3120
1252
|
}
|
|
3121
1253
|
/**
|
|
3122
1254
|
* Revoke one delegated qURL. DELETE is never retried automatically. A 503
|
|
3123
1255
|
* `mutation_outcome_unknown` is safe to retry with the same qURL ID.
|
|
3124
1256
|
*/
|
|
3125
1257
|
async deleteDelegatedQurl(qurlId) {
|
|
3126
|
-
if (typeof qurlId !== "string" || !DELEGATED_QURL_ID_PATTERN.test(qurlId)) {
|
|
3127
|
-
throw clientValidationError("deleteDelegatedQurl: qurlId must match q_ plus 11 lowercase hexadecimal characters");
|
|
1258
|
+
if (typeof qurlId !== "string" || !client_delegated_js_1.DELEGATED_QURL_ID_PATTERN.test(qurlId)) {
|
|
1259
|
+
throw (0, client_errors_js_1.clientValidationError)("deleteDelegatedQurl: qurlId must match q_ plus 11 lowercase hexadecimal characters");
|
|
3128
1260
|
}
|
|
3129
1261
|
await this.requestNoContent(`/v1/delegated-qurls/${encodeURIComponent(qurlId)}`);
|
|
3130
1262
|
}
|
|
@@ -3136,7 +1268,7 @@ class QURLClient {
|
|
|
3136
1268
|
* resource automatically.
|
|
3137
1269
|
*/
|
|
3138
1270
|
async get(id) {
|
|
3139
|
-
validatePathId(id, "get", "id", RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
1271
|
+
(0, client_paths_js_1.validatePathId)(id, "get", "id", client_paths_js_1.RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
3140
1272
|
const raw = await this.request("GET", `/v1/qurls/${encodeURIComponent(id)}`);
|
|
3141
1273
|
return this.mapQurlsField(raw);
|
|
3142
1274
|
}
|
|
@@ -3153,8 +1285,8 @@ class QURLClient {
|
|
|
3153
1285
|
* validation style used for `max_sessions`, `tag count`, URL length.
|
|
3154
1286
|
*/
|
|
3155
1287
|
async list(input = {}) {
|
|
3156
|
-
const path = appendQuery("/v1/qurls", input, LIST_PARAM_KEYS, "list");
|
|
3157
|
-
const { data, meta } = await this.rawRequest("GET", path);
|
|
1288
|
+
const path = appendQuery("/v1/qurls", input, client_input_fields_js_1.LIST_PARAM_KEYS, "list");
|
|
1289
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", path);
|
|
3158
1290
|
return pageFromMeta({
|
|
3159
1291
|
// Defensive: map in case API includes nested tokens on list items in the future.
|
|
3160
1292
|
qurls: (data ?? []).map((raw) => this.mapQurlsField(raw)),
|
|
@@ -3172,7 +1304,7 @@ class QURLClient {
|
|
|
3172
1304
|
* iteration begins.
|
|
3173
1305
|
*/
|
|
3174
1306
|
listAll(input = {}) {
|
|
3175
|
-
validateListAllInput(input, LIST_PARAM_KEYS, "listAll");
|
|
1307
|
+
validateListAllInput(input, client_input_fields_js_1.LIST_PARAM_KEYS, "listAll");
|
|
3176
1308
|
return this.paginateAll("listAll", input, (pageInput) => this.list(pageInput), (page) => page.qurls);
|
|
3177
1309
|
}
|
|
3178
1310
|
/**
|
|
@@ -3184,7 +1316,7 @@ class QURLClient {
|
|
|
3184
1316
|
* @throws {ValidationError} If `id` is not a canonical CRID.
|
|
3185
1317
|
*/
|
|
3186
1318
|
async delete(id) {
|
|
3187
|
-
requireResourceCrid(id, "delete");
|
|
1319
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "delete");
|
|
3188
1320
|
await this.requestNoContent(`/v1/qurls/${encodeURIComponent(id)}`);
|
|
3189
1321
|
}
|
|
3190
1322
|
/**
|
|
@@ -3203,14 +1335,14 @@ class QURLClient {
|
|
|
3203
1335
|
* enforced separately by the runtime check inside `update()`.
|
|
3204
1336
|
*/
|
|
3205
1337
|
async extend(id, input, options) {
|
|
3206
|
-
validatePathId(id, "extend", "id", RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
3207
|
-
requireObjectInput(input, "extend");
|
|
1338
|
+
(0, client_paths_js_1.validatePathId)(id, "extend", "id", client_paths_js_1.RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
1339
|
+
(0, client_validation_js_1.requireObjectInput)(input, "extend");
|
|
3208
1340
|
const { extend_by, expires_at } = input;
|
|
3209
1341
|
if (extend_by === undefined && expires_at === undefined) {
|
|
3210
|
-
throw clientValidationError("extend: exactly one of `extend_by` or `expires_at` is required");
|
|
1342
|
+
throw (0, client_errors_js_1.clientValidationError)("extend: exactly one of `extend_by` or `expires_at` is required");
|
|
3211
1343
|
}
|
|
3212
1344
|
if (extend_by !== undefined && expires_at !== undefined) {
|
|
3213
|
-
throw clientValidationError("extend: `extend_by` and `expires_at` are mutually exclusive — provide exactly one");
|
|
1345
|
+
throw (0, client_errors_js_1.clientValidationError)("extend: `extend_by` and `expires_at` are mutually exclusive — provide exactly one");
|
|
3214
1346
|
}
|
|
3215
1347
|
return this.update(id, { extend_by, expires_at }, options);
|
|
3216
1348
|
}
|
|
@@ -3222,25 +1354,25 @@ class QURLClient {
|
|
|
3222
1354
|
* resource automatically.
|
|
3223
1355
|
*/
|
|
3224
1356
|
async update(id, input, options) {
|
|
3225
|
-
validatePathId(id, "update", "id", RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
3226
|
-
requireObjectInput(input, "update");
|
|
3227
|
-
requireNoUnknownFields(input, UPDATE_FIELD_KEYS, "update");
|
|
1357
|
+
(0, client_paths_js_1.validatePathId)(id, "update", "id", client_paths_js_1.RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
1358
|
+
(0, client_validation_js_1.requireObjectInput)(input, "update");
|
|
1359
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.UPDATE_FIELD_KEYS, "update");
|
|
3228
1360
|
// Normalize null → undefined so untyped-JS callers passing
|
|
3229
1361
|
// `{ tags: null }` don't leak null into the wire body or crash
|
|
3230
1362
|
// downstream validators. Shallow copy keeps caller input untouched.
|
|
3231
|
-
const normalized = normalizePatchFields(input, UPDATE_FIELD_KEYS);
|
|
1363
|
+
const normalized = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.UPDATE_FIELD_KEYS);
|
|
3232
1364
|
// Timing fields have no clear-semantic (unlike `description: ""`).
|
|
3233
|
-
requireNonEmptyIfPresent(normalized.extend_by, "extend_by");
|
|
3234
|
-
requireNonEmptyIfPresent(normalized.expires_at, "expires_at");
|
|
1365
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(normalized.extend_by, "extend_by");
|
|
1366
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(normalized.expires_at, "expires_at");
|
|
3235
1367
|
if (normalized.extend_by !== undefined && normalized.expires_at !== undefined) {
|
|
3236
|
-
throw clientValidationError("update: `extend_by` and `expires_at` are mutually exclusive — provide at most one");
|
|
1368
|
+
throw (0, client_errors_js_1.clientValidationError)("update: `extend_by` and `expires_at` are mutually exclusive — provide at most one");
|
|
3237
1369
|
}
|
|
3238
|
-
requireAtLeastOneField(normalized, UPDATE_FIELD_KEYS, "update");
|
|
1370
|
+
(0, client_validation_js_1.requireAtLeastOneField)(normalized, client_input_fields_js_1.UPDATE_FIELD_KEYS, "update");
|
|
3239
1371
|
// Per-field validators run after the empty-input guard so the
|
|
3240
1372
|
// cheap binary check short-circuits first; order is correctness-neutral
|
|
3241
1373
|
// (`requireMaxLength(undefined, …)` is a no-op).
|
|
3242
|
-
requireMaxLength(normalized.description, "description", MAX_DESCRIPTION);
|
|
3243
|
-
requireValidTags(normalized.tags);
|
|
1374
|
+
(0, client_validation_js_1.requireMaxLength)(normalized.description, "description", client_limits_js_1.MAX_DESCRIPTION);
|
|
1375
|
+
(0, client_validation_js_1.requireValidTags)(normalized.tags);
|
|
3244
1376
|
const raw = await this.request("PATCH", `/v1/qurls/${encodeURIComponent(id)}`, normalized, options);
|
|
3245
1377
|
return this.mapQurlsField(raw);
|
|
3246
1378
|
}
|
|
@@ -3258,7 +1390,7 @@ class QURLClient {
|
|
|
3258
1390
|
* server applies its 24h default expiration.
|
|
3259
1391
|
*/
|
|
3260
1392
|
async mintLink(id, input, options) {
|
|
3261
|
-
validatePathId(id, "mintLink", "id", RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
1393
|
+
(0, client_paths_js_1.validatePathId)(id, "mintLink", "id", client_paths_js_1.RESOURCE_OR_QURL_ID_PATH_OPTIONS);
|
|
3262
1394
|
// Normalize null → omitted so untyped-JS callers passing
|
|
3263
1395
|
// `{ expires_in: null, expires_at: "..." }` don't leak null into
|
|
3264
1396
|
// the wire body via JSON.stringify and don't bypass the XOR check
|
|
@@ -3266,15 +1398,15 @@ class QURLClient {
|
|
|
3266
1398
|
// pattern used by update(); keeps the two write surfaces symmetric.
|
|
3267
1399
|
let normalized = input;
|
|
3268
1400
|
if (input !== undefined) {
|
|
3269
|
-
requireObjectInput(input, "mintLink");
|
|
3270
|
-
requireNoUnknownFields(input, MINT_FIELD_KEYS, "mintLink");
|
|
1401
|
+
(0, client_validation_js_1.requireObjectInput)(input, "mintLink");
|
|
1402
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.MINT_FIELD_KEYS, "mintLink");
|
|
3271
1403
|
// Iterate the explicit allowlist (matching update() / list())
|
|
3272
1404
|
// so unknown keys from untyped-JS callers can't leak through
|
|
3273
1405
|
// to the wire body. The compile-time `assertExhaustive` check
|
|
3274
1406
|
// on MINT_FIELD_KEYS keeps this in sync with MintInput.
|
|
3275
1407
|
const stripped = {};
|
|
3276
1408
|
let hasAnyField = false;
|
|
3277
|
-
for (const key of MINT_FIELD_KEYS) {
|
|
1409
|
+
for (const key of client_input_fields_js_1.MINT_FIELD_KEYS) {
|
|
3278
1410
|
const value = input[key];
|
|
3279
1411
|
if (value !== null && value !== undefined) {
|
|
3280
1412
|
stripped[key] = value;
|
|
@@ -3287,16 +1419,16 @@ class QURLClient {
|
|
|
3287
1419
|
normalized = hasAnyField ? stripped : undefined;
|
|
3288
1420
|
}
|
|
3289
1421
|
if (normalized !== undefined) {
|
|
3290
|
-
requireNonEmptyIfPresent(normalized.expires_in, "expires_in");
|
|
3291
|
-
requireNonEmptyIfPresent(normalized.expires_at, "expires_at");
|
|
1422
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(normalized.expires_in, "expires_in");
|
|
1423
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(normalized.expires_at, "expires_at");
|
|
3292
1424
|
if (normalized.expires_in !== undefined && normalized.expires_at !== undefined) {
|
|
3293
|
-
throw clientValidationError("mintLink: `expires_in` and `expires_at` are mutually exclusive — provide at most one");
|
|
1425
|
+
throw (0, client_errors_js_1.clientValidationError)("mintLink: `expires_in` and `expires_at` are mutually exclusive — provide at most one");
|
|
3294
1426
|
}
|
|
3295
|
-
requireMaxLength(normalized.label, "label", MAX_LABEL);
|
|
3296
|
-
requireNonEmptyIfPresent(normalized.label, "label");
|
|
3297
|
-
requireNonEmptyIfPresent(normalized.session_duration, "session_duration");
|
|
3298
|
-
requireMaxSessionsInRange(normalized.max_sessions);
|
|
3299
|
-
requireValidAccessPolicy(normalized.access_policy);
|
|
1427
|
+
(0, client_validation_js_1.requireMaxLength)(normalized.label, "label", client_limits_js_1.MAX_LABEL);
|
|
1428
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(normalized.label, "label");
|
|
1429
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(normalized.session_duration, "session_duration");
|
|
1430
|
+
(0, client_validation_js_1.requireMaxSessionsInRange)(normalized.max_sessions);
|
|
1431
|
+
(0, client_validation_js_1.requireValidAccessPolicy)(normalized.access_policy);
|
|
3300
1432
|
// Duration *grammar* and access_policy contents are server-authoritative
|
|
3301
1433
|
// (same rationale as validateCreateInput). Empty-string rejection happens
|
|
3302
1434
|
// above; bound/unit checks happen server-side.
|
|
@@ -3319,29 +1451,47 @@ class QURLClient {
|
|
|
3319
1451
|
let body;
|
|
3320
1452
|
if (typeof input === "string") {
|
|
3321
1453
|
if (input.length === 0) {
|
|
3322
|
-
throw clientValidationError("resolve: access_token must be a non-empty string");
|
|
1454
|
+
throw (0, client_errors_js_1.clientValidationError)("resolve: access_token must be a non-empty string");
|
|
3323
1455
|
}
|
|
3324
1456
|
body = { access_token: input };
|
|
3325
1457
|
}
|
|
3326
1458
|
else if (typeof input === "object" && input !== null) {
|
|
3327
1459
|
if (typeof input.access_token !== "string" || input.access_token.length === 0) {
|
|
3328
|
-
throw clientValidationError("resolve: access_token must be a non-empty string");
|
|
1460
|
+
throw (0, client_errors_js_1.clientValidationError)("resolve: access_token must be a non-empty string");
|
|
3329
1461
|
}
|
|
3330
1462
|
// Allowlist-rebuild — symmetric with list()/update()/mintLink().
|
|
3331
1463
|
body = { access_token: input.access_token };
|
|
3332
1464
|
}
|
|
3333
1465
|
else {
|
|
3334
|
-
throw clientValidationError(`resolve: input must be a string or { access_token } object (got ${input === null ? "null" : typeof input})`);
|
|
1466
|
+
throw (0, client_errors_js_1.clientValidationError)(`resolve: input must be a string or { access_token } object (got ${input === null ? "null" : typeof input})`);
|
|
3335
1467
|
}
|
|
3336
1468
|
return this.request("POST", "/v1/resolve", body, options);
|
|
3337
1469
|
}
|
|
1470
|
+
/** Get the caller's account and credential identity. */
|
|
1471
|
+
async getIdentity() {
|
|
1472
|
+
const identity = await this.request("GET", "/v1/me");
|
|
1473
|
+
if (!identity ||
|
|
1474
|
+
typeof identity !== "object" ||
|
|
1475
|
+
Array.isArray(identity) ||
|
|
1476
|
+
typeof identity.owner_id !== "string" ||
|
|
1477
|
+
!identity.owner_id ||
|
|
1478
|
+
typeof identity.auth_type !== "string" ||
|
|
1479
|
+
!identity.auth_type ||
|
|
1480
|
+
(identity.api_key !== undefined &&
|
|
1481
|
+
(!identity.api_key ||
|
|
1482
|
+
typeof identity.api_key !== "object" ||
|
|
1483
|
+
Array.isArray(identity.api_key)))) {
|
|
1484
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("getIdentity: invalid caller identity response");
|
|
1485
|
+
}
|
|
1486
|
+
return identity;
|
|
1487
|
+
}
|
|
3338
1488
|
/** Get quota and usage information. */
|
|
3339
1489
|
async getQuota() {
|
|
3340
1490
|
return this.request("GET", "/v1/quota");
|
|
3341
1491
|
}
|
|
3342
1492
|
/** List resources from the `/v1/resources` API. */
|
|
3343
1493
|
async listResources(input = {}) {
|
|
3344
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery("/v1/resources", input, RESOURCE_LIST_PARAM_KEYS, "listResources"));
|
|
1494
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery("/v1/resources", input, client_input_fields_js_1.RESOURCE_LIST_PARAM_KEYS, "listResources"));
|
|
3345
1495
|
return pageFromMeta({ resources: data ?? [] }, meta);
|
|
3346
1496
|
}
|
|
3347
1497
|
/**
|
|
@@ -3351,7 +1501,7 @@ class QURLClient {
|
|
|
3351
1501
|
* iteration begins.
|
|
3352
1502
|
*/
|
|
3353
1503
|
listAllResources(input = {}) {
|
|
3354
|
-
validateListAllInput(input, RESOURCE_LIST_PARAM_KEYS, "listAllResources");
|
|
1504
|
+
validateListAllInput(input, client_input_fields_js_1.RESOURCE_LIST_PARAM_KEYS, "listAllResources");
|
|
3355
1505
|
return this.paginateAll("listAllResources", input, (pageInput) => this.listResources(pageInput), (page) => page.resources);
|
|
3356
1506
|
}
|
|
3357
1507
|
/**
|
|
@@ -3361,10 +1511,10 @@ class QURLClient {
|
|
|
3361
1511
|
* a portal-minting handle).
|
|
3362
1512
|
*/
|
|
3363
1513
|
async createResource(input, options) {
|
|
3364
|
-
requireObjectInput(input, "createResource");
|
|
3365
|
-
requireNoUnknownFields(input, CREATE_RESOURCE_FIELD_KEYS, "createResource");
|
|
3366
|
-
const normalized = normalizePatchFields(input, CREATE_RESOURCE_FIELD_KEYS);
|
|
3367
|
-
validateResourceWriteFields(normalized, {
|
|
1514
|
+
(0, client_validation_js_1.requireObjectInput)(input, "createResource");
|
|
1515
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.CREATE_RESOURCE_FIELD_KEYS, "createResource");
|
|
1516
|
+
const normalized = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.CREATE_RESOURCE_FIELD_KEYS);
|
|
1517
|
+
(0, client_resources_js_1.validateResourceWriteFields)(normalized, {
|
|
3368
1518
|
requireUrlTarget: true,
|
|
3369
1519
|
validateFindOrCreate: true,
|
|
3370
1520
|
});
|
|
@@ -3372,57 +1522,82 @@ class QURLClient {
|
|
|
3372
1522
|
}
|
|
3373
1523
|
/** Get one resource plus its bounded qURL preview. */
|
|
3374
1524
|
async getResource(id) {
|
|
3375
|
-
requireResourceCrid(id, "getResource");
|
|
1525
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "getResource");
|
|
3376
1526
|
return this.request("GET", `/v1/resources/${encodeURIComponent(id)}`);
|
|
3377
1527
|
}
|
|
3378
1528
|
/** Update resource metadata. */
|
|
3379
1529
|
async updateResource(id, input, options) {
|
|
3380
|
-
requireResourceCrid(id, "updateResource");
|
|
3381
|
-
requireObjectInput(input, "updateResource");
|
|
3382
|
-
requireNoUnknownFields(input, UPDATE_RESOURCE_FIELD_KEYS, "updateResource");
|
|
3383
|
-
const validationInput = normalizePatchFields(input, UPDATE_RESOURCE_FIELD_KEYS, { preserveNullFields: ["alias"] });
|
|
3384
|
-
requireAtLeastOneField(validationInput, UPDATE_RESOURCE_FIELD_KEYS, "updateResource");
|
|
3385
|
-
validateResourceWriteFields(validationInput, {
|
|
1530
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "updateResource");
|
|
1531
|
+
(0, client_validation_js_1.requireObjectInput)(input, "updateResource");
|
|
1532
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.UPDATE_RESOURCE_FIELD_KEYS, "updateResource");
|
|
1533
|
+
const validationInput = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.UPDATE_RESOURCE_FIELD_KEYS, { preserveNullFields: ["alias"] });
|
|
1534
|
+
(0, client_validation_js_1.requireAtLeastOneField)(validationInput, client_input_fields_js_1.UPDATE_RESOURCE_FIELD_KEYS, "updateResource");
|
|
1535
|
+
(0, client_resources_js_1.validateResourceWriteFields)(validationInput, {
|
|
3386
1536
|
allowAliasClear: true,
|
|
3387
1537
|
allowCustomDomainClear: true,
|
|
3388
1538
|
validatePreserveHost: true,
|
|
3389
1539
|
});
|
|
3390
1540
|
return this.request("PATCH", `/v1/resources/${encodeURIComponent(id)}`, validationInput, options);
|
|
3391
1541
|
}
|
|
1542
|
+
/** Start Stripe Connect onboarding for this publisher. LayerV collects 3%. */
|
|
1543
|
+
async connectPublisherStripe() {
|
|
1544
|
+
const result = await this.request("POST", "/v1/payments/connect");
|
|
1545
|
+
let target;
|
|
1546
|
+
try {
|
|
1547
|
+
target = new URL(result.url);
|
|
1548
|
+
}
|
|
1549
|
+
catch {
|
|
1550
|
+
throw (0, client_errors_js_1.clientValidationError)("invalid Stripe connection URL");
|
|
1551
|
+
}
|
|
1552
|
+
if (target.protocol !== "https:" ||
|
|
1553
|
+
target.host !== "connect.stripe.com" ||
|
|
1554
|
+
target.username ||
|
|
1555
|
+
target.password)
|
|
1556
|
+
throw (0, client_errors_js_1.clientValidationError)("invalid Stripe connection URL");
|
|
1557
|
+
return result;
|
|
1558
|
+
}
|
|
1559
|
+
/** Read this publisher's connected account and the fixed application fee. */
|
|
1560
|
+
async publisherStripeAccount() {
|
|
1561
|
+
const result = await this.request("GET", "/v1/payments/account");
|
|
1562
|
+
if ((result.account_id !== "" && !/^acct_[A-Za-z0-9]{1,128}$/.test(result.account_id)) ||
|
|
1563
|
+
result.layerv_fee_percent !== 3)
|
|
1564
|
+
throw (0, client_errors_js_1.clientValidationError)("invalid Stripe account response");
|
|
1565
|
+
return result;
|
|
1566
|
+
}
|
|
3392
1567
|
/** Send a publisher-only invitation. Mail delivery does not prove enrollment. */
|
|
3393
1568
|
async inviteConsumer(id, email) {
|
|
3394
|
-
requireResourceCrid(id, "inviteConsumer");
|
|
3395
|
-
requireNonEmptyIfPresent(email, "email");
|
|
1569
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "inviteConsumer");
|
|
1570
|
+
(0, client_validation_js_1.requireNonEmptyIfPresent)(email, "email");
|
|
3396
1571
|
if (typeof email !== "string" || email.length > 320)
|
|
3397
|
-
throw clientValidationError("email: must be a bounded string");
|
|
1572
|
+
throw (0, client_errors_js_1.clientValidationError)("email: must be a bounded string");
|
|
3398
1573
|
return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/invitations`, { email });
|
|
3399
1574
|
}
|
|
3400
1575
|
async listConsumerInvitations(id, input = {}) {
|
|
3401
|
-
requireResourceCrid(id, "listConsumerInvitations");
|
|
3402
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/invitations`, input, ["cursor", "limit"], "listConsumerInvitations"));
|
|
1576
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "listConsumerInvitations");
|
|
1577
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/invitations`, input, ["cursor", "limit"], "listConsumerInvitations"));
|
|
3403
1578
|
return pageFromMeta({ invitations: data ?? [] }, meta);
|
|
3404
1579
|
}
|
|
3405
1580
|
async listConsumerGrants(id, input = {}) {
|
|
3406
|
-
requireResourceCrid(id, "listConsumerGrants");
|
|
3407
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/consumer-grants`, input, ["cursor", "limit"], "listConsumerGrants"));
|
|
1581
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "listConsumerGrants");
|
|
1582
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/consumer-grants`, input, ["cursor", "limit"], "listConsumerGrants"));
|
|
3408
1583
|
return pageFromMeta({ grants: data ?? [] }, meta);
|
|
3409
1584
|
}
|
|
3410
1585
|
async resendConsumerInvitation(id, invitationId, expectedVersion) {
|
|
3411
|
-
requireResourceCrid(id, "resendConsumerInvitation");
|
|
3412
|
-
requireConsumerDecision(invitationId, expectedVersion);
|
|
1586
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "resendConsumerInvitation");
|
|
1587
|
+
(0, client_validation_js_1.requireConsumerDecision)(invitationId, expectedVersion);
|
|
3413
1588
|
return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/invitations/${encodeURIComponent(invitationId)}/resend`, { expected_version: expectedVersion });
|
|
3414
1589
|
}
|
|
3415
1590
|
/** Remove the invitation and its grant. Inspect session_cleanup before reporting stream removal. */
|
|
3416
1591
|
async revokeConsumerInvitation(id, invitationId, expectedVersion) {
|
|
3417
|
-
requireResourceCrid(id, "revokeConsumerInvitation");
|
|
3418
|
-
requireConsumerDecision(invitationId, expectedVersion);
|
|
1592
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "revokeConsumerInvitation");
|
|
1593
|
+
(0, client_validation_js_1.requireConsumerDecision)(invitationId, expectedVersion);
|
|
3419
1594
|
return this.request("DELETE", `/v1/resources/${encodeURIComponent(id)}/invitations/${encodeURIComponent(invitationId)}?expected_version=${expectedVersion}`);
|
|
3420
1595
|
}
|
|
3421
1596
|
/** Review approval, key opt-in with grantId new, or trusted publisher entitlement. */
|
|
3422
1597
|
async approveConsumerGrant(id, grantId, input) {
|
|
3423
|
-
requireResourceCrid(id, "approveConsumerGrant");
|
|
3424
|
-
requireObjectInput(input, "approveConsumerGrant");
|
|
3425
|
-
requireNoUnknownFields(input, [
|
|
1598
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "approveConsumerGrant");
|
|
1599
|
+
(0, client_validation_js_1.requireObjectInput)(input, "approveConsumerGrant");
|
|
1600
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, [
|
|
3426
1601
|
"expected_version",
|
|
3427
1602
|
"credential_kind",
|
|
3428
1603
|
"credential_id",
|
|
@@ -3432,19 +1607,19 @@ class QURLClient {
|
|
|
3432
1607
|
], "approveConsumerGrant");
|
|
3433
1608
|
if (input.credential_kind !== undefined &&
|
|
3434
1609
|
!["passkey", "device"].includes(input.credential_kind)) {
|
|
3435
|
-
throw clientValidationError("approveConsumerGrant: credential_kind must be passkey or device");
|
|
1610
|
+
throw (0, client_errors_js_1.clientValidationError)("approveConsumerGrant: credential_kind must be passkey or device");
|
|
3436
1611
|
}
|
|
3437
|
-
requireConsumerDecision(grantId, input.expected_version, grantId === "new");
|
|
1612
|
+
(0, client_validation_js_1.requireConsumerDecision)(grantId, input.expected_version, grantId === "new");
|
|
3438
1613
|
return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/consumer-grants/${encodeURIComponent(grantId)}/approve`, input);
|
|
3439
1614
|
}
|
|
3440
1615
|
async denyConsumerGrant(id, grantId, expectedVersion) {
|
|
3441
|
-
requireResourceCrid(id, "denyConsumerGrant");
|
|
3442
|
-
requireConsumerDecision(grantId, expectedVersion);
|
|
1616
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "denyConsumerGrant");
|
|
1617
|
+
(0, client_validation_js_1.requireConsumerDecision)(grantId, expectedVersion);
|
|
3443
1618
|
return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/consumer-grants/${encodeURIComponent(grantId)}/deny`, { expected_version: expectedVersion });
|
|
3444
1619
|
}
|
|
3445
1620
|
async revokeConsumerGrant(id, grantId, expectedVersion) {
|
|
3446
|
-
requireResourceCrid(id, "revokeConsumerGrant");
|
|
3447
|
-
requireConsumerDecision(grantId, expectedVersion);
|
|
1621
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "revokeConsumerGrant");
|
|
1622
|
+
(0, client_validation_js_1.requireConsumerDecision)(grantId, expectedVersion);
|
|
3448
1623
|
return this.request("DELETE", `/v1/resources/${encodeURIComponent(id)}/consumer-grants/${encodeURIComponent(grantId)}?expected_version=${expectedVersion}`);
|
|
3449
1624
|
}
|
|
3450
1625
|
/**
|
|
@@ -3453,19 +1628,19 @@ class QURLClient {
|
|
|
3453
1628
|
*/
|
|
3454
1629
|
async deleteResourceByIdentifier(id) {
|
|
3455
1630
|
if (typeof id !== "string")
|
|
3456
|
-
throw clientValidationError("id: invalid resource identifier");
|
|
1631
|
+
throw (0, client_errors_js_1.clientValidationError)("id: invalid resource identifier");
|
|
3457
1632
|
if (/^r_[a-z0-9_-]{11}$/.test(id)) {
|
|
3458
1633
|
await this.requestNoContent(`/v1/qurls/${encodeURIComponent(id)}`);
|
|
3459
1634
|
return;
|
|
3460
1635
|
}
|
|
3461
1636
|
const publicKey = /^[A-Za-z0-9_-]{107,214}$/.test(id) && id.length % 4 !== 1;
|
|
3462
1637
|
if (!publicKey)
|
|
3463
|
-
requireResourceCrid(id, "deleteResourceByIdentifier");
|
|
1638
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "deleteResourceByIdentifier");
|
|
3464
1639
|
await this.requestNoContent(`/v1/resources/${encodeURIComponent(id)}`);
|
|
3465
1640
|
}
|
|
3466
1641
|
/** Revoke a resource and all of its qURLs. */
|
|
3467
1642
|
async deleteResource(id) {
|
|
3468
|
-
requireResourceCrid(id, "deleteResource");
|
|
1643
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "deleteResource");
|
|
3469
1644
|
await this.requestNoContent(`/v1/resources/${encodeURIComponent(id)}`);
|
|
3470
1645
|
}
|
|
3471
1646
|
/**
|
|
@@ -3474,15 +1649,15 @@ class QURLClient {
|
|
|
3474
1649
|
* Portal-flow equivalent: {@link createPortal} (same endpoint).
|
|
3475
1650
|
*/
|
|
3476
1651
|
async createQurlForResource(id, input, options) {
|
|
3477
|
-
requireResourceCrid(id, "createQurlForResource");
|
|
1652
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "createQurlForResource");
|
|
3478
1653
|
let normalized = input;
|
|
3479
1654
|
if (input !== undefined) {
|
|
3480
|
-
requireObjectInput(input, "createQurlForResource");
|
|
3481
|
-
requireNoUnknownFields(input, CREATE_QURL_FOR_RESOURCE_FIELD_KEYS, "createQurlForResource");
|
|
3482
|
-
const stripped = normalizePatchFields(input, CREATE_QURL_FOR_RESOURCE_FIELD_KEYS);
|
|
1655
|
+
(0, client_validation_js_1.requireObjectInput)(input, "createQurlForResource");
|
|
1656
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.CREATE_QURL_FOR_RESOURCE_FIELD_KEYS, "createQurlForResource");
|
|
1657
|
+
const stripped = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.CREATE_QURL_FOR_RESOURCE_FIELD_KEYS);
|
|
3483
1658
|
normalized = Object.keys(stripped).length > 0 ? stripped : undefined;
|
|
3484
1659
|
}
|
|
3485
|
-
validateQurlTokenOptions(normalized);
|
|
1660
|
+
(0, client_resources_js_1.validateQurlTokenOptions)(normalized);
|
|
3486
1661
|
return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/qurls`, normalized, options);
|
|
3487
1662
|
}
|
|
3488
1663
|
/**
|
|
@@ -3492,36 +1667,36 @@ class QURLClient {
|
|
|
3492
1667
|
* before delivery to verify that the response CRID derives from that key.
|
|
3493
1668
|
*/
|
|
3494
1669
|
async shareResource(id, options = {}, requestOptions) {
|
|
3495
|
-
requireResourceCrid(id, "shareResource");
|
|
3496
|
-
requireObjectInput(options, "shareResource");
|
|
3497
|
-
requireNoUnknownFields(options, SHARE_RESOURCE_OPTION_KEYS, "shareResource");
|
|
1670
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "shareResource");
|
|
1671
|
+
(0, client_validation_js_1.requireObjectInput)(options, "shareResource");
|
|
1672
|
+
(0, client_validation_js_1.requireNoUnknownFields)(options, client_input_fields_js_1.SHARE_RESOURCE_OPTION_KEYS, "shareResource");
|
|
3498
1673
|
const body = {};
|
|
3499
1674
|
if (options.ttlSeconds !== undefined) {
|
|
3500
1675
|
if (typeof options.ttlSeconds !== "number" ||
|
|
3501
1676
|
!Number.isSafeInteger(options.ttlSeconds) ||
|
|
3502
1677
|
options.ttlSeconds <= 0) {
|
|
3503
|
-
throw clientValidationError("shareResource: ttlSeconds must be a positive safe integer");
|
|
1678
|
+
throw (0, client_errors_js_1.clientValidationError)("shareResource: ttlSeconds must be a positive safe integer");
|
|
3504
1679
|
}
|
|
3505
1680
|
body.ttl_seconds = options.ttlSeconds;
|
|
3506
1681
|
}
|
|
3507
1682
|
const data = await this.request("POST", `/v1/resources/${encodeURIComponent(id)}/share`, body, requestOptions);
|
|
3508
1683
|
if (typeof data?.qurl !== "string" || data.qurl.trim() === "") {
|
|
3509
|
-
throw unexpectedResponseError("shareResource: response is missing qurl");
|
|
1684
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("shareResource: response is missing qurl");
|
|
3510
1685
|
}
|
|
3511
1686
|
if (data.crid !== id) {
|
|
3512
|
-
throw unexpectedResponseError("shareResource: response CRID does not match the requested CRID");
|
|
1687
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("shareResource: response CRID does not match the requested CRID");
|
|
3513
1688
|
}
|
|
3514
1689
|
const wire = data;
|
|
3515
1690
|
for (const field of ["qurl_id", "type", "expires_at"]) {
|
|
3516
1691
|
const value = wire[field];
|
|
3517
1692
|
if (value !== undefined && value !== null && typeof value !== "string") {
|
|
3518
|
-
throw unexpectedResponseError(`shareResource: response has invalid ${field}`);
|
|
1693
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`shareResource: response has invalid ${field}`);
|
|
3519
1694
|
}
|
|
3520
1695
|
}
|
|
3521
1696
|
for (const field of ["qurl_id", "type"]) {
|
|
3522
1697
|
const value = wire[field];
|
|
3523
1698
|
if (typeof value === "string" && (value.trim() === "" || value.trim() !== value)) {
|
|
3524
|
-
throw unexpectedResponseError(`shareResource: response has invalid ${field}`);
|
|
1699
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`shareResource: response has invalid ${field}`);
|
|
3525
1700
|
}
|
|
3526
1701
|
}
|
|
3527
1702
|
if (wire.expires_in_seconds !== undefined &&
|
|
@@ -3529,18 +1704,18 @@ class QURLClient {
|
|
|
3529
1704
|
(typeof wire.expires_in_seconds !== "number" ||
|
|
3530
1705
|
!Number.isSafeInteger(wire.expires_in_seconds) ||
|
|
3531
1706
|
wire.expires_in_seconds < 0)) {
|
|
3532
|
-
throw unexpectedResponseError("shareResource: response has invalid expires_in_seconds");
|
|
1707
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("shareResource: response has invalid expires_in_seconds");
|
|
3533
1708
|
}
|
|
3534
1709
|
if (wire.single_use !== undefined &&
|
|
3535
1710
|
wire.single_use !== null &&
|
|
3536
1711
|
typeof wire.single_use !== "boolean") {
|
|
3537
|
-
throw unexpectedResponseError("shareResource: response has invalid single_use");
|
|
1712
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("shareResource: response has invalid single_use");
|
|
3538
1713
|
}
|
|
3539
|
-
const expiresAt = parseApiDate(typeof wire.expires_at === "string" ? wire.expires_at : undefined);
|
|
1714
|
+
const expiresAt = (0, client_portals_js_1.parseApiDate)(typeof wire.expires_at === "string" ? wire.expires_at : undefined);
|
|
3540
1715
|
if (typeof wire.expires_at === "string" && expiresAt === undefined) {
|
|
3541
|
-
throw unexpectedResponseError("shareResource: response has invalid expires_at");
|
|
1716
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("shareResource: response has invalid expires_at");
|
|
3542
1717
|
}
|
|
3543
|
-
return new share_js_1.ShareLink({
|
|
1718
|
+
return new share_js_1.ShareLink((0, response_metadata_js_1.copyResponseMetadata)(data, {
|
|
3544
1719
|
// Repair transport-only edge whitespace on the one-time credential;
|
|
3545
1720
|
// ancillary identifiers fail closed instead of being normalized.
|
|
3546
1721
|
link: data.qurl.trim(),
|
|
@@ -3550,45 +1725,45 @@ class QURLClient {
|
|
|
3550
1725
|
expiresAt,
|
|
3551
1726
|
expiresInSeconds: typeof wire.expires_in_seconds === "number" ? wire.expires_in_seconds : undefined,
|
|
3552
1727
|
singleUse: typeof wire.single_use === "boolean" ? wire.single_use : undefined,
|
|
3553
|
-
});
|
|
1728
|
+
}));
|
|
3554
1729
|
}
|
|
3555
1730
|
/** Revoke a specific qURL token on a resource. */
|
|
3556
1731
|
async revokeResourceQurl(id, qurlId) {
|
|
3557
|
-
requireResourceCrid(id, "revokeResourceQurl");
|
|
3558
|
-
validatePathId(qurlId, "revokeResourceQurl", "qurl id", QURL_ID_PATH_OPTIONS);
|
|
1732
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "revokeResourceQurl");
|
|
1733
|
+
(0, client_paths_js_1.validatePathId)(qurlId, "revokeResourceQurl", "qurl id", client_paths_js_1.QURL_ID_PATH_OPTIONS);
|
|
3559
1734
|
await this.requestNoContent(`/v1/resources/${encodeURIComponent(id)}/qurls/${encodeURIComponent(qurlId)}`);
|
|
3560
1735
|
}
|
|
3561
1736
|
/** Update a specific qURL token on a resource. */
|
|
3562
1737
|
async updateResourceQurl(id, qurlId, input, options) {
|
|
3563
|
-
requireResourceCrid(id, "updateResourceQurl");
|
|
3564
|
-
validatePathId(qurlId, "updateResourceQurl", "qurl id", QURL_ID_PATH_OPTIONS);
|
|
3565
|
-
requireObjectInput(input, "updateResourceQurl");
|
|
3566
|
-
requireNoUnknownFields(input, UPDATE_RESOURCE_QURL_FIELD_KEYS, "updateResourceQurl");
|
|
3567
|
-
const normalized = normalizePatchFields(input, UPDATE_RESOURCE_QURL_FIELD_KEYS);
|
|
3568
|
-
requireAtLeastOneField(normalized, UPDATE_RESOURCE_QURL_FIELD_KEYS, "updateResourceQurl");
|
|
3569
|
-
validateResourceQurlUpdateInput(normalized);
|
|
1738
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "updateResourceQurl");
|
|
1739
|
+
(0, client_paths_js_1.validatePathId)(qurlId, "updateResourceQurl", "qurl id", client_paths_js_1.QURL_ID_PATH_OPTIONS);
|
|
1740
|
+
(0, client_validation_js_1.requireObjectInput)(input, "updateResourceQurl");
|
|
1741
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.UPDATE_RESOURCE_QURL_FIELD_KEYS, "updateResourceQurl");
|
|
1742
|
+
const normalized = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.UPDATE_RESOURCE_QURL_FIELD_KEYS);
|
|
1743
|
+
(0, client_validation_js_1.requireAtLeastOneField)(normalized, client_input_fields_js_1.UPDATE_RESOURCE_QURL_FIELD_KEYS, "updateResourceQurl");
|
|
1744
|
+
(0, client_resources_js_1.validateResourceQurlUpdateInput)(normalized);
|
|
3570
1745
|
return this.request("PATCH", `/v1/resources/${encodeURIComponent(id)}/qurls/${encodeURIComponent(qurlId)}`, normalized, options);
|
|
3571
1746
|
}
|
|
3572
1747
|
/** List every active session, following bounded service pages. */
|
|
3573
1748
|
async listResourceSessions(id) {
|
|
3574
|
-
requireResourceCrid(id, "listResourceSessions");
|
|
1749
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "listResourceSessions");
|
|
3575
1750
|
const sessions = [];
|
|
3576
1751
|
let request_id;
|
|
3577
1752
|
let page_size;
|
|
3578
1753
|
for await (const session of this.paginateAll("listResourceSessions", {}, async ({ cursor }) => {
|
|
3579
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/sessions`, { paged: "true", cursor }, ["paged", "cursor"], "listResourceSessions"));
|
|
1754
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/sessions`, { paged: "true", cursor }, ["paged", "cursor"], "listResourceSessions"));
|
|
3580
1755
|
if (meta?.has_more && !meta.next_cursor) {
|
|
3581
|
-
throw unexpectedResponseError("Session page has no continuation cursor", {
|
|
1756
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("Session page has no continuation cursor", {
|
|
3582
1757
|
requestId: meta.request_id,
|
|
3583
1758
|
});
|
|
3584
1759
|
}
|
|
3585
1760
|
request_id = meta?.request_id;
|
|
3586
1761
|
page_size = meta?.page_size;
|
|
3587
|
-
return { ...meta, sessions: data ?? [] };
|
|
1762
|
+
return withEnvelopeMetadata({ ...meta, sessions: data ?? [] }, meta);
|
|
3588
1763
|
}, (page) => page.sessions)) {
|
|
3589
1764
|
sessions.push(session);
|
|
3590
1765
|
}
|
|
3591
|
-
return { sessions, request_id, has_more: false, page_size };
|
|
1766
|
+
return withEnvelopeMetadata({ sessions, request_id, has_more: false, page_size }, { request_id });
|
|
3592
1767
|
}
|
|
3593
1768
|
/**
|
|
3594
1769
|
* End every session page. This is not atomic: a failed page throws after earlier
|
|
@@ -3596,46 +1771,43 @@ class QURLClient {
|
|
|
3596
1771
|
* A retry can close newly admitted sessions and counts only its own transitions.
|
|
3597
1772
|
*/
|
|
3598
1773
|
async terminateAllResourceSessions(id) {
|
|
3599
|
-
requireResourceCrid(id, "terminateAllResourceSessions");
|
|
1774
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "terminateAllResourceSessions");
|
|
3600
1775
|
const path = `/v1/resources/${encodeURIComponent(id)}/sessions`;
|
|
3601
1776
|
let terminated = 0;
|
|
3602
1777
|
let request_id;
|
|
3603
1778
|
for await (const count of this.paginateAll("terminateAllResourceSessions", {}, async ({ cursor }) => {
|
|
3604
|
-
const { data, meta,
|
|
3605
|
-
if (
|
|
3606
|
-
throw unexpectedResponseError(`Unexpected 204 No Content from DELETE ${path}; expected response body`, { status: 204, requestId: meta?.request_id });
|
|
1779
|
+
const { envelope: { data, meta }, status: httpStatus, } = await this.rawRequest("DELETE", appendQuery(path, { paged: "true", cursor }, ["paged", "cursor"], "terminateAllResourceSessions"));
|
|
1780
|
+
if (httpStatus === 204) {
|
|
1781
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected 204 No Content from DELETE ${path}; expected response body`, { status: 204, requestId: meta?.request_id });
|
|
3607
1782
|
}
|
|
3608
1783
|
if (meta?.has_more && !meta.next_cursor) {
|
|
3609
|
-
throw unexpectedResponseError("Session page has no continuation cursor", {
|
|
1784
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("Session page has no continuation cursor", {
|
|
3610
1785
|
requestId: meta.request_id,
|
|
3611
1786
|
});
|
|
3612
1787
|
}
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
});
|
|
3617
|
-
}
|
|
3618
|
-
else if (!Number.isSafeInteger(data.terminated) || data.terminated < 0) {
|
|
3619
|
-
throw unexpectedResponseError("terminateAllResourceSessions: invalid terminated count", {
|
|
3620
|
-
requestId: meta?.request_id,
|
|
3621
|
-
});
|
|
1788
|
+
const count = data?.terminated;
|
|
1789
|
+
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) {
|
|
1790
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("terminateAllResourceSessions: missing or invalid terminated count; inspect sessions before retrying", { requestId: meta?.request_id });
|
|
3622
1791
|
}
|
|
3623
1792
|
request_id = meta?.request_id;
|
|
3624
|
-
return { ...meta, counts: [
|
|
1793
|
+
return withEnvelopeMetadata({ ...meta, counts: [count] }, meta);
|
|
3625
1794
|
}, (page) => page.counts)) {
|
|
3626
1795
|
terminated += count;
|
|
1796
|
+
if (!Number.isSafeInteger(terminated)) {
|
|
1797
|
+
throw (0, client_errors_js_1.unexpectedResponseError)("terminateAllResourceSessions: total exceeds safe integer range; inspect sessions before retrying", { requestId: request_id });
|
|
1798
|
+
}
|
|
3627
1799
|
}
|
|
3628
|
-
return { terminated, request_id };
|
|
1800
|
+
return withEnvelopeMetadata({ terminated, request_id }, { request_id });
|
|
3629
1801
|
}
|
|
3630
1802
|
/** Terminate a specific resource session. */
|
|
3631
1803
|
async terminateResourceSession(id, sessionId) {
|
|
3632
|
-
requireResourceCrid(id, "terminateResourceSession");
|
|
3633
|
-
validatePathId(sessionId, "terminateResourceSession", "session id");
|
|
1804
|
+
(0, client_paths_js_1.requireResourceCrid)(id, "terminateResourceSession");
|
|
1805
|
+
(0, client_paths_js_1.validatePathId)(sessionId, "terminateResourceSession", "session id");
|
|
3634
1806
|
await this.requestNoContent(`/v1/resources/${encodeURIComponent(id)}/sessions/${encodeURIComponent(sessionId)}`);
|
|
3635
1807
|
}
|
|
3636
1808
|
/** List connector installations. */
|
|
3637
1809
|
async listConnectorInstallations(input = {}) {
|
|
3638
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery("/v1/connectors/installations", input, CONNECTOR_INSTALLATION_LIST_PARAM_KEYS, "listConnectorInstallations"));
|
|
1810
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery("/v1/connectors/installations", input, client_input_fields_js_1.CONNECTOR_INSTALLATION_LIST_PARAM_KEYS, "listConnectorInstallations"));
|
|
3639
1811
|
return pageFromMeta({ installations: data ?? [] }, meta);
|
|
3640
1812
|
}
|
|
3641
1813
|
/**
|
|
@@ -3645,7 +1817,7 @@ class QURLClient {
|
|
|
3645
1817
|
* iteration begins.
|
|
3646
1818
|
*/
|
|
3647
1819
|
listAllConnectorInstallations(input = {}) {
|
|
3648
|
-
validateListAllInput(input, CONNECTOR_INSTALLATION_LIST_PARAM_KEYS, "listAllConnectorInstallations");
|
|
1820
|
+
validateListAllInput(input, client_input_fields_js_1.CONNECTOR_INSTALLATION_LIST_PARAM_KEYS, "listAllConnectorInstallations");
|
|
3649
1821
|
return this.paginateAll("listAllConnectorInstallations", input, (pageInput) => this.listConnectorInstallations(pageInput), (page) => page.installations);
|
|
3650
1822
|
}
|
|
3651
1823
|
/** Get current-period usage. */
|
|
@@ -3662,27 +1834,27 @@ class QURLClient {
|
|
|
3662
1834
|
}
|
|
3663
1835
|
/** Update customer settings. */
|
|
3664
1836
|
async updateCustomer(input, options) {
|
|
3665
|
-
requireObjectInput(input, "updateCustomer");
|
|
3666
|
-
requireNoUnknownFields(input, UPDATE_CUSTOMER_FIELD_KEYS, "updateCustomer");
|
|
3667
|
-
const normalizedRecord = normalizePatchFields(input, UPDATE_CUSTOMER_FIELD_KEYS);
|
|
1837
|
+
(0, client_validation_js_1.requireObjectInput)(input, "updateCustomer");
|
|
1838
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.UPDATE_CUSTOMER_FIELD_KEYS, "updateCustomer");
|
|
1839
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.UPDATE_CUSTOMER_FIELD_KEYS);
|
|
3668
1840
|
const normalized = normalizedRecord;
|
|
3669
1841
|
const spendingCap = normalized.spending_cap_cents;
|
|
3670
1842
|
if (spendingCap === undefined) {
|
|
3671
|
-
throw clientValidationError("updateCustomer: spending_cap_cents is required");
|
|
1843
|
+
throw (0, client_errors_js_1.clientValidationError)("updateCustomer: spending_cap_cents is required");
|
|
3672
1844
|
}
|
|
3673
1845
|
if (!Number.isInteger(spendingCap) || spendingCap < 0) {
|
|
3674
|
-
const rendered = typeof spendingCap === "number" ? String(spendingCap) : describeShape(spendingCap);
|
|
3675
|
-
throw clientValidationError(`updateCustomer: spending_cap_cents must be a non-negative integer (got ${rendered})`);
|
|
1846
|
+
const rendered = typeof spendingCap === "number" ? String(spendingCap) : (0, client_validation_js_1.describeShape)(spendingCap);
|
|
1847
|
+
throw (0, client_errors_js_1.clientValidationError)(`updateCustomer: spending_cap_cents must be a non-negative integer (got ${rendered})`);
|
|
3676
1848
|
}
|
|
3677
1849
|
return this.request("PATCH", "/v1/customer", normalized, options);
|
|
3678
1850
|
}
|
|
3679
1851
|
/** Create a Stripe checkout session. */
|
|
3680
1852
|
async createBillingCheckout(input, options) {
|
|
3681
|
-
requireObjectInput(input, "createBillingCheckout");
|
|
3682
|
-
requireNoUnknownFields(input, CREATE_BILLING_CHECKOUT_FIELD_KEYS, "createBillingCheckout");
|
|
3683
|
-
const normalizedRecord = normalizePatchFields(input, CREATE_BILLING_CHECKOUT_FIELD_KEYS);
|
|
1853
|
+
(0, client_validation_js_1.requireObjectInput)(input, "createBillingCheckout");
|
|
1854
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.CREATE_BILLING_CHECKOUT_FIELD_KEYS, "createBillingCheckout");
|
|
1855
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.CREATE_BILLING_CHECKOUT_FIELD_KEYS);
|
|
3684
1856
|
const normalized = normalizedRecord;
|
|
3685
|
-
requireNonEmptyStringField(normalizedRecord, "plan", "createBillingCheckout");
|
|
1857
|
+
(0, client_validation_js_1.requireNonEmptyStringField)(normalizedRecord, "plan", "createBillingCheckout");
|
|
3686
1858
|
return this.request("POST", "/v1/billing/checkout", normalized, options);
|
|
3687
1859
|
}
|
|
3688
1860
|
/** Create a Stripe billing portal session. */
|
|
@@ -3693,7 +1865,7 @@ class QURLClient {
|
|
|
3693
1865
|
async listBillingInvoices(input = {}) {
|
|
3694
1866
|
// This endpoint nests the list in `data.invoices` unlike the other list
|
|
3695
1867
|
// endpoints, which return `data` as the array directly.
|
|
3696
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery("/v1/billing/invoices", input, BILLING_INVOICE_LIST_PARAM_KEYS, "listBillingInvoices"));
|
|
1868
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery("/v1/billing/invoices", input, client_input_fields_js_1.BILLING_INVOICE_LIST_PARAM_KEYS, "listBillingInvoices"));
|
|
3697
1869
|
return pageFromMeta({ invoices: data?.invoices ?? [] }, meta);
|
|
3698
1870
|
}
|
|
3699
1871
|
/**
|
|
@@ -3703,22 +1875,22 @@ class QURLClient {
|
|
|
3703
1875
|
* iteration begins.
|
|
3704
1876
|
*/
|
|
3705
1877
|
listAllBillingInvoices(input = {}) {
|
|
3706
|
-
validateListAllInput(input, BILLING_INVOICE_LIST_PARAM_KEYS, "listAllBillingInvoices");
|
|
1878
|
+
validateListAllInput(input, client_input_fields_js_1.BILLING_INVOICE_LIST_PARAM_KEYS, "listAllBillingInvoices");
|
|
3707
1879
|
return this.paginateAll("listAllBillingInvoices", input, (pageInput) => this.listBillingInvoices(pageInput), (page) => page.invoices);
|
|
3708
1880
|
}
|
|
3709
1881
|
/** Register a custom domain. */
|
|
3710
1882
|
async registerDomain(input, options) {
|
|
3711
|
-
requireObjectInput(input, "registerDomain");
|
|
3712
|
-
requireNoUnknownFields(input, REGISTER_DOMAIN_FIELD_KEYS, "registerDomain");
|
|
3713
|
-
const normalizedRecord = normalizePatchFields(input, REGISTER_DOMAIN_FIELD_KEYS);
|
|
1883
|
+
(0, client_validation_js_1.requireObjectInput)(input, "registerDomain");
|
|
1884
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.REGISTER_DOMAIN_FIELD_KEYS, "registerDomain");
|
|
1885
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.REGISTER_DOMAIN_FIELD_KEYS);
|
|
3714
1886
|
const normalized = normalizedRecord;
|
|
3715
|
-
requireNonEmptyStringField(normalizedRecord, "domain", "registerDomain");
|
|
3716
|
-
requireMaxLength(normalized.domain, "domain", MAX_CUSTOM_DOMAIN);
|
|
1887
|
+
(0, client_validation_js_1.requireNonEmptyStringField)(normalizedRecord, "domain", "registerDomain");
|
|
1888
|
+
(0, client_validation_js_1.requireMaxLength)(normalized.domain, "domain", client_limits_js_1.MAX_CUSTOM_DOMAIN);
|
|
3717
1889
|
return this.request("POST", "/v1/domains", normalized, options);
|
|
3718
1890
|
}
|
|
3719
1891
|
/** List custom domains. */
|
|
3720
1892
|
async listDomains(input = {}) {
|
|
3721
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery("/v1/domains", input, DOMAIN_LIST_PARAM_KEYS, "listDomains"));
|
|
1893
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery("/v1/domains", input, client_input_fields_js_1.DOMAIN_LIST_PARAM_KEYS, "listDomains"));
|
|
3722
1894
|
return pageFromMeta({ domains: data ?? [] }, meta);
|
|
3723
1895
|
}
|
|
3724
1896
|
/**
|
|
@@ -3728,32 +1900,32 @@ class QURLClient {
|
|
|
3728
1900
|
* iteration begins.
|
|
3729
1901
|
*/
|
|
3730
1902
|
listAllDomains(input = {}) {
|
|
3731
|
-
validateListAllInput(input, DOMAIN_LIST_PARAM_KEYS, "listAllDomains");
|
|
1903
|
+
validateListAllInput(input, client_input_fields_js_1.DOMAIN_LIST_PARAM_KEYS, "listAllDomains");
|
|
3732
1904
|
return this.paginateAll("listAllDomains", input, (pageInput) => this.listDomains(pageInput), (page) => page.domains);
|
|
3733
1905
|
}
|
|
3734
1906
|
/** Get custom domain status. */
|
|
3735
1907
|
async getDomain(domain) {
|
|
3736
|
-
validatePathId(domain, "getDomain", "domain");
|
|
1908
|
+
(0, client_paths_js_1.validatePathId)(domain, "getDomain", "domain");
|
|
3737
1909
|
return this.request("GET", `/v1/domains/${encodeURIComponent(domain)}`);
|
|
3738
1910
|
}
|
|
3739
1911
|
/** Remove a custom domain. */
|
|
3740
1912
|
async deleteDomain(domain) {
|
|
3741
|
-
validatePathId(domain, "deleteDomain", "domain");
|
|
1913
|
+
(0, client_paths_js_1.validatePathId)(domain, "deleteDomain", "domain");
|
|
3742
1914
|
await this.requestNoContent(`/v1/domains/${encodeURIComponent(domain)}`);
|
|
3743
1915
|
}
|
|
3744
1916
|
/** Trigger DNS verification for a custom domain. */
|
|
3745
1917
|
async verifyDomain(domain, options) {
|
|
3746
|
-
validatePathId(domain, "verifyDomain", "domain");
|
|
1918
|
+
(0, client_paths_js_1.validatePathId)(domain, "verifyDomain", "domain");
|
|
3747
1919
|
return this.request("POST", `/v1/domains/${encodeURIComponent(domain)}/verify`, undefined, options);
|
|
3748
1920
|
}
|
|
3749
1921
|
/** Regenerate a domain verification token. */
|
|
3750
1922
|
async regenerateDomainToken(domain, options) {
|
|
3751
|
-
validatePathId(domain, "regenerateDomainToken", "domain");
|
|
1923
|
+
(0, client_paths_js_1.validatePathId)(domain, "regenerateDomainToken", "domain");
|
|
3752
1924
|
return this.request("POST", `/v1/domains/${encodeURIComponent(domain)}/regenerate-token`, undefined, options);
|
|
3753
1925
|
}
|
|
3754
1926
|
/** List webhooks. */
|
|
3755
1927
|
async listWebhooks(input = {}) {
|
|
3756
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery("/v1/webhooks", input, WEBHOOK_LIST_PARAM_KEYS, "listWebhooks"));
|
|
1928
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery("/v1/webhooks", input, client_input_fields_js_1.WEBHOOK_LIST_PARAM_KEYS, "listWebhooks"));
|
|
3757
1929
|
return pageFromMeta({ webhooks: data ?? [] }, meta);
|
|
3758
1930
|
}
|
|
3759
1931
|
/**
|
|
@@ -3763,16 +1935,16 @@ class QURLClient {
|
|
|
3763
1935
|
* iteration begins.
|
|
3764
1936
|
*/
|
|
3765
1937
|
listAllWebhooks(input = {}) {
|
|
3766
|
-
validateListAllInput(input, WEBHOOK_LIST_PARAM_KEYS, "listAllWebhooks");
|
|
1938
|
+
validateListAllInput(input, client_input_fields_js_1.WEBHOOK_LIST_PARAM_KEYS, "listAllWebhooks");
|
|
3767
1939
|
return this.paginateAll("listAllWebhooks", input, (pageInput) => this.listWebhooks(pageInput), (page) => page.webhooks);
|
|
3768
1940
|
}
|
|
3769
1941
|
/** Create a webhook. */
|
|
3770
1942
|
async createWebhook(input, options) {
|
|
3771
|
-
requireObjectInput(input, "createWebhook");
|
|
3772
|
-
requireNoUnknownFields(input, CREATE_WEBHOOK_FIELD_KEYS, "createWebhook");
|
|
3773
|
-
const normalizedRecord = normalizePatchFields(input, CREATE_WEBHOOK_FIELD_KEYS);
|
|
1943
|
+
(0, client_validation_js_1.requireObjectInput)(input, "createWebhook");
|
|
1944
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.CREATE_WEBHOOK_FIELD_KEYS, "createWebhook");
|
|
1945
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.CREATE_WEBHOOK_FIELD_KEYS);
|
|
3774
1946
|
const normalized = normalizedRecord;
|
|
3775
|
-
validateWebhookWriteFields(normalizedRecord, "createWebhook", {
|
|
1947
|
+
(0, client_management_js_1.validateWebhookWriteFields)(normalizedRecord, "createWebhook", {
|
|
3776
1948
|
url: true,
|
|
3777
1949
|
events: true,
|
|
3778
1950
|
});
|
|
@@ -3784,33 +1956,33 @@ class QURLClient {
|
|
|
3784
1956
|
}
|
|
3785
1957
|
/** Get a webhook. */
|
|
3786
1958
|
async getWebhook(id) {
|
|
3787
|
-
validatePathId(id, "getWebhook");
|
|
1959
|
+
(0, client_paths_js_1.validatePathId)(id, "getWebhook");
|
|
3788
1960
|
return this.request("GET", `/v1/webhooks/${encodeURIComponent(id)}`);
|
|
3789
1961
|
}
|
|
3790
1962
|
/** Update a webhook. */
|
|
3791
1963
|
async updateWebhook(id, input, options) {
|
|
3792
|
-
validatePathId(id, "updateWebhook");
|
|
3793
|
-
requireObjectInput(input, "updateWebhook");
|
|
3794
|
-
requireNoUnknownFields(input, UPDATE_WEBHOOK_FIELD_KEYS, "updateWebhook");
|
|
3795
|
-
const normalized = normalizePatchFields(input, UPDATE_WEBHOOK_FIELD_KEYS);
|
|
3796
|
-
requireAtLeastOneField(normalized, UPDATE_WEBHOOK_FIELD_KEYS, "updateWebhook");
|
|
3797
|
-
validateWebhookWriteFields(normalized, "updateWebhook");
|
|
1964
|
+
(0, client_paths_js_1.validatePathId)(id, "updateWebhook");
|
|
1965
|
+
(0, client_validation_js_1.requireObjectInput)(input, "updateWebhook");
|
|
1966
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.UPDATE_WEBHOOK_FIELD_KEYS, "updateWebhook");
|
|
1967
|
+
const normalized = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.UPDATE_WEBHOOK_FIELD_KEYS);
|
|
1968
|
+
(0, client_validation_js_1.requireAtLeastOneField)(normalized, client_input_fields_js_1.UPDATE_WEBHOOK_FIELD_KEYS, "updateWebhook");
|
|
1969
|
+
(0, client_management_js_1.validateWebhookWriteFields)(normalized, "updateWebhook");
|
|
3798
1970
|
return this.request("PATCH", `/v1/webhooks/${encodeURIComponent(id)}`, normalized, options);
|
|
3799
1971
|
}
|
|
3800
1972
|
/** Delete a webhook. */
|
|
3801
1973
|
async deleteWebhook(id) {
|
|
3802
|
-
validatePathId(id, "deleteWebhook");
|
|
1974
|
+
(0, client_paths_js_1.validatePathId)(id, "deleteWebhook");
|
|
3803
1975
|
await this.requestNoContent(`/v1/webhooks/${encodeURIComponent(id)}`);
|
|
3804
1976
|
}
|
|
3805
1977
|
/** Regenerate a webhook signing secret. */
|
|
3806
1978
|
async regenerateWebhookSecret(id, options) {
|
|
3807
|
-
validatePathId(id, "regenerateWebhookSecret");
|
|
1979
|
+
(0, client_paths_js_1.validatePathId)(id, "regenerateWebhookSecret");
|
|
3808
1980
|
return this.request("POST", `/v1/webhooks/${encodeURIComponent(id)}/secret`, undefined, options);
|
|
3809
1981
|
}
|
|
3810
1982
|
/** List delivery attempts for a webhook. */
|
|
3811
1983
|
async listWebhookDeliveries(id, input = {}) {
|
|
3812
|
-
validatePathId(id, "listWebhookDeliveries");
|
|
3813
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/webhooks/${encodeURIComponent(id)}/deliveries`, input, WEBHOOK_DELIVERY_LIST_PARAM_KEYS, "listWebhookDeliveries"));
|
|
1984
|
+
(0, client_paths_js_1.validatePathId)(id, "listWebhookDeliveries");
|
|
1985
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery(`/v1/webhooks/${encodeURIComponent(id)}/deliveries`, input, client_input_fields_js_1.WEBHOOK_DELIVERY_LIST_PARAM_KEYS, "listWebhookDeliveries"));
|
|
3814
1986
|
return pageFromMeta({ deliveries: data ?? [] }, meta);
|
|
3815
1987
|
}
|
|
3816
1988
|
/**
|
|
@@ -3820,20 +1992,101 @@ class QURLClient {
|
|
|
3820
1992
|
* iteration begins.
|
|
3821
1993
|
*/
|
|
3822
1994
|
listAllWebhookDeliveries(id, input = {}) {
|
|
3823
|
-
validatePathId(id, "listAllWebhookDeliveries");
|
|
3824
|
-
validateListAllInput(input, WEBHOOK_DELIVERY_LIST_PARAM_KEYS, "listAllWebhookDeliveries");
|
|
1995
|
+
(0, client_paths_js_1.validatePathId)(id, "listAllWebhookDeliveries");
|
|
1996
|
+
validateListAllInput(input, client_input_fields_js_1.WEBHOOK_DELIVERY_LIST_PARAM_KEYS, "listAllWebhookDeliveries");
|
|
3825
1997
|
return this.paginateAll("listAllWebhookDeliveries", input, (pageInput) => this.listWebhookDeliveries(id, pageInput), (page) => page.deliveries);
|
|
3826
1998
|
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Bind an external identity using the provider's trusted OAuth JWT as the
|
|
2001
|
+
* client's bearer credential. Persist the request key before calling, then
|
|
2002
|
+
* securely store the returned one-time plaintext key without logging it.
|
|
2003
|
+
* Retry a lost response within 24 hours with the same key and body.
|
|
2004
|
+
*/
|
|
2005
|
+
async createExternalIdentityBinding(input, options) {
|
|
2006
|
+
const method = "createExternalIdentityBinding";
|
|
2007
|
+
(0, client_validation_js_1.requireObjectInput)(input, method);
|
|
2008
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, ["provider", "external_id", "display_name", "rotate_existing"], method);
|
|
2009
|
+
if (!["slack", "discord", "teams"].includes(input.provider)) {
|
|
2010
|
+
throw (0, client_errors_js_1.clientValidationError)(`${method}: provider must be slack, discord, or teams`);
|
|
2011
|
+
}
|
|
2012
|
+
if (typeof input.external_id !== "string" ||
|
|
2013
|
+
input.external_id.length > 256 ||
|
|
2014
|
+
!/^[\x21-\x22\x24-\x7e]+$/.test(input.external_id)) {
|
|
2015
|
+
throw (0, client_errors_js_1.clientValidationError)(`${method}: external_id must contain 1-256 visible ASCII characters without #`);
|
|
2016
|
+
}
|
|
2017
|
+
if (input.display_name !== undefined &&
|
|
2018
|
+
(typeof input.display_name !== "string" ||
|
|
2019
|
+
[...input.display_name].length > 100 ||
|
|
2020
|
+
/[\uD800-\uDFFF]/u.test(input.display_name))) {
|
|
2021
|
+
throw (0, client_errors_js_1.clientValidationError)(`${method}: display_name must contain at most 100 Unicode characters`);
|
|
2022
|
+
}
|
|
2023
|
+
(0, client_validation_js_1.requireBooleanIfPresent)(input.rotate_existing, `${method}.rotate_existing`);
|
|
2024
|
+
validateRequestOptions(options);
|
|
2025
|
+
if (!options?.idempotencyKey || options.idempotencyKey.length < 32) {
|
|
2026
|
+
throw (0, client_errors_js_1.clientValidationError)(`${method}: a persisted idempotencyKey of 32-256 characters is required`);
|
|
2027
|
+
}
|
|
2028
|
+
const body = {
|
|
2029
|
+
provider: input.provider,
|
|
2030
|
+
external_id: input.external_id,
|
|
2031
|
+
...(input.display_name !== undefined ? { display_name: input.display_name } : {}),
|
|
2032
|
+
...(input.rotate_existing !== undefined ? { rotate_existing: input.rotate_existing } : {}),
|
|
2033
|
+
};
|
|
2034
|
+
const raw = await this.rawRequest("POST", "/v1/external-identity-bindings", body, {
|
|
2035
|
+
requestOptions: options,
|
|
2036
|
+
generateIdempotencyKey: false,
|
|
2037
|
+
});
|
|
2038
|
+
// This operation returns a flat document, unlike the data/meta endpoints.
|
|
2039
|
+
const result = raw.envelope;
|
|
2040
|
+
const expectedScopes = body.provider === "discord"
|
|
2041
|
+
? ["qurl:read", "qurl:write"]
|
|
2042
|
+
: ["qurl:read", "qurl:write", "qurl:agent"];
|
|
2043
|
+
if (raw.status !== 201 ||
|
|
2044
|
+
typeof result.binding_id !== "string" ||
|
|
2045
|
+
!/^eib_[A-Za-z0-9]{11}$/.test(result.binding_id) ||
|
|
2046
|
+
result.provider !== body.provider ||
|
|
2047
|
+
result.external_id !== body.external_id ||
|
|
2048
|
+
!result.api_key ||
|
|
2049
|
+
typeof result.api_key !== "object" ||
|
|
2050
|
+
Array.isArray(result.api_key) ||
|
|
2051
|
+
typeof result.api_key.key_id !== "string" ||
|
|
2052
|
+
!/^key_[A-Za-z0-9]{12}$/.test(result.api_key.key_id) ||
|
|
2053
|
+
typeof result.api_key.plaintext !== "string" ||
|
|
2054
|
+
!/^lv_(?:live|test)_[A-Za-z0-9_-]{43}$/.test(result.api_key.plaintext) ||
|
|
2055
|
+
result.api_key.key_prefix !== result.api_key.plaintext.slice(0, 12) ||
|
|
2056
|
+
!Array.isArray(result.scopes) ||
|
|
2057
|
+
result.scopes.length !== expectedScopes.length ||
|
|
2058
|
+
!expectedScopes.every((scope) => result.scopes.includes(scope)) ||
|
|
2059
|
+
!(0, client_delegated_js_1.isUtcTimestamp)(result.created_at) ||
|
|
2060
|
+
(result.display_name !== undefined &&
|
|
2061
|
+
(typeof result.display_name !== "string" ||
|
|
2062
|
+
[...result.display_name].length > 100 ||
|
|
2063
|
+
/[\uD800-\uDFFF]/u.test(result.display_name)))) {
|
|
2064
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: invalid binding response; reconcile with the same request key before retrying`);
|
|
2065
|
+
}
|
|
2066
|
+
return withEnvelopeMetadata({
|
|
2067
|
+
binding_id: result.binding_id,
|
|
2068
|
+
provider: result.provider,
|
|
2069
|
+
external_id: result.external_id,
|
|
2070
|
+
...(result.display_name !== undefined ? { display_name: result.display_name } : {}),
|
|
2071
|
+
api_key: {
|
|
2072
|
+
key_id: result.api_key.key_id,
|
|
2073
|
+
key_prefix: result.api_key.key_prefix,
|
|
2074
|
+
plaintext: result.api_key.plaintext,
|
|
2075
|
+
},
|
|
2076
|
+
scopes: result.scopes,
|
|
2077
|
+
created_at: result.created_at,
|
|
2078
|
+
}, raw.envelope.meta);
|
|
2079
|
+
}
|
|
3827
2080
|
/** Create a new API key. */
|
|
3828
2081
|
async createApiKey(input, options) {
|
|
3829
|
-
requireObjectInput(input, "createApiKey");
|
|
3830
|
-
requireNoUnknownFields(input, CREATE_API_KEY_FIELD_KEYS, "createApiKey");
|
|
3831
|
-
const normalizedRecord = normalizePatchFields(input, CREATE_API_KEY_FIELD_KEYS);
|
|
2082
|
+
(0, client_validation_js_1.requireObjectInput)(input, "createApiKey");
|
|
2083
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.CREATE_API_KEY_FIELD_KEYS, "createApiKey");
|
|
2084
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.CREATE_API_KEY_FIELD_KEYS);
|
|
3832
2085
|
// `kind` is required by the server and has no default there. Filling it
|
|
3833
2086
|
// in keeps the common durable-key call site unchanged.
|
|
3834
2087
|
normalizedRecord.kind ??= "api_key";
|
|
3835
2088
|
if (normalizedRecord.kind !== "api_key" && normalizedRecord.kind !== "enrollment_token") {
|
|
3836
|
-
throw clientValidationError("createApiKey: kind must be 'api_key' or 'enrollment_token'");
|
|
2089
|
+
throw (0, client_errors_js_1.clientValidationError)("createApiKey: kind must be 'api_key' or 'enrollment_token'");
|
|
3837
2090
|
}
|
|
3838
2091
|
// `scopes` and `target`/`claims` are mutually exclusive by kind, and the
|
|
3839
2092
|
// server rejects either mismatch with 400 invalid_input. Fail both
|
|
@@ -3841,20 +2094,20 @@ class QURLClient {
|
|
|
3841
2094
|
const isEnrollmentToken = normalizedRecord.kind === "enrollment_token";
|
|
3842
2095
|
if (isEnrollmentToken) {
|
|
3843
2096
|
if (normalizedRecord.scopes !== undefined) {
|
|
3844
|
-
throw clientValidationError("createApiKey: scopes is not accepted for kind 'enrollment_token'; " +
|
|
2097
|
+
throw (0, client_errors_js_1.clientValidationError)("createApiKey: scopes is not accepted for kind 'enrollment_token'; " +
|
|
3845
2098
|
"the server assigns them from target");
|
|
3846
2099
|
}
|
|
3847
|
-
validateEnrollmentTokenFields(normalizedRecord, "createApiKey");
|
|
2100
|
+
(0, client_management_js_1.validateEnrollmentTokenFields)(normalizedRecord, "createApiKey");
|
|
3848
2101
|
}
|
|
3849
2102
|
else {
|
|
3850
2103
|
for (const field of ["target", "claims", "expires_in"]) {
|
|
3851
2104
|
if (normalizedRecord[field] !== undefined) {
|
|
3852
|
-
throw clientValidationError(`createApiKey: ${field} is only accepted for kind 'enrollment_token'`);
|
|
2105
|
+
throw (0, client_errors_js_1.clientValidationError)(`createApiKey: ${field} is only accepted for kind 'enrollment_token'`);
|
|
3853
2106
|
}
|
|
3854
2107
|
}
|
|
3855
2108
|
}
|
|
3856
2109
|
const normalized = normalizedRecord;
|
|
3857
|
-
validateApiKeyWriteFields(normalizedRecord, "createApiKey", {
|
|
2110
|
+
(0, client_management_js_1.validateApiKeyWriteFields)(normalizedRecord, "createApiKey", {
|
|
3858
2111
|
name: true,
|
|
3859
2112
|
scopes: !isEnrollmentToken,
|
|
3860
2113
|
});
|
|
@@ -3862,7 +2115,7 @@ class QURLClient {
|
|
|
3862
2115
|
}
|
|
3863
2116
|
/** List API keys. */
|
|
3864
2117
|
async listApiKeys(input = {}) {
|
|
3865
|
-
const { data, meta } = await this.rawRequest("GET", appendQuery("/v1/api-keys", input, API_KEY_LIST_PARAM_KEYS, "listApiKeys"));
|
|
2118
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", appendQuery("/v1/api-keys", input, client_input_fields_js_1.API_KEY_LIST_PARAM_KEYS, "listApiKeys"));
|
|
3866
2119
|
return pageFromMeta({ api_keys: data ?? [] }, meta);
|
|
3867
2120
|
}
|
|
3868
2121
|
/**
|
|
@@ -3872,40 +2125,40 @@ class QURLClient {
|
|
|
3872
2125
|
* iteration begins.
|
|
3873
2126
|
*/
|
|
3874
2127
|
listAllApiKeys(input = {}) {
|
|
3875
|
-
validateListAllInput(input, API_KEY_LIST_PARAM_KEYS, "listAllApiKeys");
|
|
2128
|
+
validateListAllInput(input, client_input_fields_js_1.API_KEY_LIST_PARAM_KEYS, "listAllApiKeys");
|
|
3876
2129
|
return this.paginateAll("listAllApiKeys", input, (pageInput) => this.listApiKeys(pageInput), (page) => page.api_keys);
|
|
3877
2130
|
}
|
|
3878
2131
|
/** Update an API key. */
|
|
3879
2132
|
async updateApiKey(keyId, input, options) {
|
|
3880
|
-
validatePathId(keyId, "updateApiKey");
|
|
3881
|
-
requireObjectInput(input, "updateApiKey");
|
|
3882
|
-
requireNoUnknownFields(input, UPDATE_API_KEY_FIELD_KEYS, "updateApiKey");
|
|
3883
|
-
const normalized = normalizePatchFields(input, UPDATE_API_KEY_FIELD_KEYS);
|
|
3884
|
-
requireAtLeastOneField(normalized, UPDATE_API_KEY_FIELD_KEYS, "updateApiKey");
|
|
3885
|
-
validateApiKeyWriteFields(normalized, "updateApiKey");
|
|
2133
|
+
(0, client_paths_js_1.validatePathId)(keyId, "updateApiKey");
|
|
2134
|
+
(0, client_validation_js_1.requireObjectInput)(input, "updateApiKey");
|
|
2135
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.UPDATE_API_KEY_FIELD_KEYS, "updateApiKey");
|
|
2136
|
+
const normalized = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.UPDATE_API_KEY_FIELD_KEYS);
|
|
2137
|
+
(0, client_validation_js_1.requireAtLeastOneField)(normalized, client_input_fields_js_1.UPDATE_API_KEY_FIELD_KEYS, "updateApiKey");
|
|
2138
|
+
(0, client_management_js_1.validateApiKeyWriteFields)(normalized, "updateApiKey");
|
|
3886
2139
|
return this.request("PATCH", `/v1/api-keys/${encodeURIComponent(keyId)}`, normalized, options);
|
|
3887
2140
|
}
|
|
3888
2141
|
/** Revoke an API key. */
|
|
3889
2142
|
async revokeApiKey(keyId) {
|
|
3890
|
-
validatePathId(keyId, "revokeApiKey");
|
|
2143
|
+
(0, client_paths_js_1.validatePathId)(keyId, "revokeApiKey");
|
|
3891
2144
|
await this.requestNoContent(`/v1/api-keys/${encodeURIComponent(keyId)}`);
|
|
3892
2145
|
}
|
|
3893
2146
|
/** Redeem an access code. */
|
|
3894
2147
|
async redeemAccessCode(input, options) {
|
|
3895
|
-
requireObjectInput(input, "redeemAccessCode");
|
|
3896
|
-
requireNoUnknownFields(input, REDEEM_ACCESS_CODE_FIELD_KEYS, "redeemAccessCode");
|
|
3897
|
-
const normalizedRecord = normalizePatchFields(input, REDEEM_ACCESS_CODE_FIELD_KEYS);
|
|
2148
|
+
(0, client_validation_js_1.requireObjectInput)(input, "redeemAccessCode");
|
|
2149
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.REDEEM_ACCESS_CODE_FIELD_KEYS, "redeemAccessCode");
|
|
2150
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.REDEEM_ACCESS_CODE_FIELD_KEYS);
|
|
3898
2151
|
const normalized = normalizedRecord;
|
|
3899
|
-
requireNonEmptyStringField(normalizedRecord, "code", "redeemAccessCode");
|
|
2152
|
+
(0, client_validation_js_1.requireNonEmptyStringField)(normalizedRecord, "code", "redeemAccessCode");
|
|
3900
2153
|
return this.request("POST", "/v1/access-codes/redeem", normalized, options);
|
|
3901
2154
|
}
|
|
3902
2155
|
/** Create an access code. */
|
|
3903
2156
|
async createAccessCode(input, options) {
|
|
3904
|
-
requireObjectInput(input, "createAccessCode");
|
|
3905
|
-
requireNoUnknownFields(input, CREATE_ACCESS_CODE_FIELD_KEYS, "createAccessCode");
|
|
3906
|
-
const normalizedRecord = normalizePatchFields(input, CREATE_ACCESS_CODE_FIELD_KEYS);
|
|
2157
|
+
(0, client_validation_js_1.requireObjectInput)(input, "createAccessCode");
|
|
2158
|
+
(0, client_validation_js_1.requireNoUnknownFields)(input, client_input_fields_js_1.CREATE_ACCESS_CODE_FIELD_KEYS, "createAccessCode");
|
|
2159
|
+
const normalizedRecord = (0, client_validation_js_1.normalizePatchFields)(input, client_input_fields_js_1.CREATE_ACCESS_CODE_FIELD_KEYS);
|
|
3907
2160
|
const normalized = normalizedRecord;
|
|
3908
|
-
requireResourceCrid(normalizedRecord.resource_id, "createAccessCode", "resource_id");
|
|
2161
|
+
(0, client_paths_js_1.requireResourceCrid)(normalizedRecord.resource_id, "createAccessCode", "resource_id");
|
|
3909
2162
|
return this.request("POST", "/v1/access-codes", normalized, options);
|
|
3910
2163
|
}
|
|
3911
2164
|
/**
|
|
@@ -3916,36 +2169,36 @@ class QURLClient {
|
|
|
3916
2169
|
* this page and emits a debug log rather than pretending it fetched all pages.
|
|
3917
2170
|
*/
|
|
3918
2171
|
async listAccessCodes() {
|
|
3919
|
-
const { data, meta } = await this.rawRequest("GET", "/v1/access-codes");
|
|
2172
|
+
const { envelope: { data, meta }, } = await this.rawRequest("GET", "/v1/access-codes");
|
|
3920
2173
|
if (meta?.has_more || meta?.next_cursor) {
|
|
3921
2174
|
this.log("listAccessCodes: pagination metadata surfaced on unpaginated endpoint", {
|
|
3922
2175
|
has_more: meta.has_more,
|
|
3923
2176
|
next_cursor: meta.next_cursor,
|
|
3924
2177
|
});
|
|
3925
2178
|
}
|
|
3926
|
-
return {
|
|
2179
|
+
return withEnvelopeMetadata({
|
|
3927
2180
|
access_codes: data ?? [],
|
|
3928
2181
|
request_id: meta?.request_id,
|
|
3929
2182
|
has_more: false,
|
|
3930
2183
|
page_size: meta?.page_size,
|
|
3931
|
-
};
|
|
2184
|
+
}, meta);
|
|
3932
2185
|
}
|
|
3933
2186
|
/** Revoke an access code. */
|
|
3934
2187
|
async revokeAccessCode(id) {
|
|
3935
|
-
validatePathId(id, "revokeAccessCode");
|
|
2188
|
+
(0, client_paths_js_1.validatePathId)(id, "revokeAccessCode");
|
|
3936
2189
|
await this.requestNoContent(`/v1/access-codes/${encodeURIComponent(id)}`);
|
|
3937
2190
|
}
|
|
3938
2191
|
// --- Internal HTTP plumbing ---
|
|
3939
2192
|
async request(method, path, body, requestOptions) {
|
|
3940
2193
|
// List methods call rawRequest directly so 204 can degrade to an empty page.
|
|
3941
2194
|
// Body-returning endpoints declare 200 JSON today, so this path fails closed.
|
|
3942
|
-
const { data,
|
|
2195
|
+
const { envelope: { data, meta }, status: httpStatus, } = await this.rawRequest(method, path, body, {
|
|
3943
2196
|
requestOptions,
|
|
3944
2197
|
});
|
|
3945
|
-
if (
|
|
3946
|
-
throw unexpectedResponseError(`Unexpected 204 No Content from ${method} ${path}; expected response body`);
|
|
2198
|
+
if (httpStatus === 204) {
|
|
2199
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected 204 No Content from ${method} ${path}; expected response body`);
|
|
3947
2200
|
}
|
|
3948
|
-
return data;
|
|
2201
|
+
return withEnvelopeMetadata(data, meta);
|
|
3949
2202
|
}
|
|
3950
2203
|
/**
|
|
3951
2204
|
* Require the service's exact 204 response with no response bytes exposed by
|
|
@@ -3956,10 +2209,10 @@ class QURLClient {
|
|
|
3956
2209
|
const response = await this.rawRequest("DELETE", path, undefined, {
|
|
3957
2210
|
allowEmptySuccessBody: true,
|
|
3958
2211
|
});
|
|
3959
|
-
if (response.
|
|
3960
|
-
const status = response.
|
|
3961
|
-
const bodyShape = response.
|
|
3962
|
-
throw unexpectedResponseError(`Unexpected response from DELETE; expected empty HTTP 204, received HTTP ${status} ${bodyShape}. ` +
|
|
2212
|
+
if (response.status !== 204 || response.bodyEmpty !== true) {
|
|
2213
|
+
const status = response.status ?? 0;
|
|
2214
|
+
const bodyShape = response.bodyEmpty ? "with an empty body" : "with response bytes";
|
|
2215
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Unexpected response from DELETE; expected empty HTTP 204, received HTTP ${status} ${bodyShape}. ` +
|
|
3963
2216
|
"The delete may already have been applied; reconcile resource state before retrying.", { status });
|
|
3964
2217
|
}
|
|
3965
2218
|
}
|
|
@@ -4027,7 +2280,8 @@ class QURLClient {
|
|
|
4027
2280
|
// request bound: roughly `timeout * (maxRetries + 1) +
|
|
4028
2281
|
// sum(retryDelay)`.
|
|
4029
2282
|
onDispatch?.();
|
|
4030
|
-
|
|
2283
|
+
// Native edge fetch requires the global receiver, not this client.
|
|
2284
|
+
response = await this.fetchFn.call(globalThis, url, {
|
|
4031
2285
|
method,
|
|
4032
2286
|
headers,
|
|
4033
2287
|
body: serializedBody,
|
|
@@ -4038,13 +2292,13 @@ class QURLClient {
|
|
|
4038
2292
|
catch (err) {
|
|
4039
2293
|
lastError = this.classifyFetchError(err, requestSignal);
|
|
4040
2294
|
this.log(`${method} ${url} ${lastError instanceof errors_js_1.TimeoutError ? "timed out" : "network error"}`, {
|
|
4041
|
-
error: boundedErrorSnippet(lastError.message),
|
|
2295
|
+
error: (0, client_errors_js_1.boundedErrorSnippet)(lastError.message),
|
|
4042
2296
|
});
|
|
4043
2297
|
// An injected fetch can abort or impose a deadline for its own reason.
|
|
4044
2298
|
// That is not this SDK's timeout. Replaying it can defeat caller-side
|
|
4045
2299
|
// cancellation or deadline policy.
|
|
4046
2300
|
if (retryFetchFailure &&
|
|
4047
|
-
!isIndependentAbort(err, requestSignal) &&
|
|
2301
|
+
!(0, client_errors_js_1.isIndependentAbort)(err, requestSignal) &&
|
|
4048
2302
|
attempt < this.maxRetries) {
|
|
4049
2303
|
continue;
|
|
4050
2304
|
}
|
|
@@ -4069,23 +2323,23 @@ class QURLClient {
|
|
|
4069
2323
|
: REDIRECT_RESPONSE_STATUSES.has(response.status)
|
|
4070
2324
|
? `HTTP ${response.status}`
|
|
4071
2325
|
: "opaque";
|
|
4072
|
-
throw unexpectedResponseError(`Refused ${redirectKind} redirect response for ${method}`, {
|
|
2326
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(`Refused ${redirectKind} redirect response for ${method}`, {
|
|
4073
2327
|
status: response.status,
|
|
4074
2328
|
});
|
|
4075
2329
|
}
|
|
4076
2330
|
let responseBody;
|
|
4077
2331
|
try {
|
|
4078
|
-
responseBody = await readBoundedResponseBody(response);
|
|
2332
|
+
responseBody = await readBoundedResponseBody(response, this.maxResponseBodyBytes);
|
|
4079
2333
|
}
|
|
4080
2334
|
catch (err) {
|
|
4081
2335
|
if (err instanceof ResponseBodyTooLargeError) {
|
|
4082
2336
|
this.log(`rejected oversized response body from ${response.status}`, {
|
|
4083
2337
|
status: response.status,
|
|
4084
2338
|
});
|
|
4085
|
-
const detail = `Response body exceeds ${
|
|
2339
|
+
const detail = `Response body exceeds ${this.maxResponseBodyBytes}-byte limit on HTTP ${response.status}; ` +
|
|
4086
2340
|
"request a smaller page when listing collections";
|
|
4087
2341
|
if (response.ok) {
|
|
4088
|
-
throw unexpectedResponseError(detail, { status: response.status });
|
|
2342
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(detail, { status: response.status });
|
|
4089
2343
|
}
|
|
4090
2344
|
const bodyError = (0, errors_js_1.createError)({
|
|
4091
2345
|
status: response.status,
|
|
@@ -4093,7 +2347,7 @@ class QURLClient {
|
|
|
4093
2347
|
// the real HTTP status class (429/5xx) just like other unreadable
|
|
4094
2348
|
// error bodies instead of reclassifying it as client validation.
|
|
4095
2349
|
code: errors_js_1.ERROR_CODE_UNKNOWN,
|
|
4096
|
-
title: boundedErrorSnippet(response.statusText) || `HTTP ${response.status}`,
|
|
2350
|
+
title: (0, client_errors_js_1.boundedErrorSnippet)(response.statusText) || `HTTP ${response.status}`,
|
|
4097
2351
|
detail,
|
|
4098
2352
|
retry_after: this.parseRetryAfter(response),
|
|
4099
2353
|
});
|
|
@@ -4115,12 +2369,12 @@ class QURLClient {
|
|
|
4115
2369
|
if (response.ok) {
|
|
4116
2370
|
// A successful status with an invalid wire body is a deterministic
|
|
4117
2371
|
// response-contract failure. Do not replay a GET or a mutation.
|
|
4118
|
-
throw unexpectedResponseError(detail, { status: response.status });
|
|
2372
|
+
throw (0, client_errors_js_1.unexpectedResponseError)(detail, { status: response.status });
|
|
4119
2373
|
}
|
|
4120
2374
|
const encodingError = (0, errors_js_1.createError)({
|
|
4121
2375
|
status: response.status,
|
|
4122
2376
|
code: errors_js_1.ERROR_CODE_UNKNOWN,
|
|
4123
|
-
title: boundedErrorSnippet(response.statusText) || `HTTP ${response.status}`,
|
|
2377
|
+
title: (0, client_errors_js_1.boundedErrorSnippet)(response.statusText) || `HTTP ${response.status}`,
|
|
4124
2378
|
detail,
|
|
4125
2379
|
retry_after: this.parseRetryAfter(response),
|
|
4126
2380
|
});
|
|
@@ -4144,7 +2398,7 @@ class QURLClient {
|
|
|
4144
2398
|
// path retries. Mutations are never replayed after a body transport
|
|
4145
2399
|
// failure because the operation may have applied.
|
|
4146
2400
|
if (method === "GET" &&
|
|
4147
|
-
!isIndependentAbort(err, requestSignal) &&
|
|
2401
|
+
!(0, client_errors_js_1.isIndependentAbort)(err, requestSignal) &&
|
|
4148
2402
|
(response.ok || retryable.has(response.status)) &&
|
|
4149
2403
|
attempt < this.maxRetries) {
|
|
4150
2404
|
lastError = transportReadError;
|
|
@@ -4161,11 +2415,11 @@ class QURLClient {
|
|
|
4161
2415
|
? err.message
|
|
4162
2416
|
: `Failed to read response body on HTTP ${response.status}`;
|
|
4163
2417
|
const readError = response.ok
|
|
4164
|
-
? unexpectedResponseError(detail, { status: response.status })
|
|
2418
|
+
? (0, client_errors_js_1.unexpectedResponseError)(detail, { status: response.status })
|
|
4165
2419
|
: (0, errors_js_1.createError)({
|
|
4166
2420
|
status: response.status,
|
|
4167
2421
|
code: errors_js_1.ERROR_CODE_UNKNOWN,
|
|
4168
|
-
title: boundedErrorSnippet(response.statusText) || `HTTP ${response.status}`,
|
|
2422
|
+
title: (0, client_errors_js_1.boundedErrorSnippet)(response.statusText) || `HTTP ${response.status}`,
|
|
4169
2423
|
detail,
|
|
4170
2424
|
retry_after: this.parseRetryAfter(response),
|
|
4171
2425
|
});
|
|
@@ -4173,7 +2427,7 @@ class QURLClient {
|
|
|
4173
2427
|
// example a BigInt returned by json()) and non-Error throws. Do not
|
|
4174
2428
|
// replay them: another attempt will see the same local contract bug.
|
|
4175
2429
|
throw err instanceof ResponseBodyMaterializationError
|
|
4176
|
-
? withErrorCause(readError, err)
|
|
2430
|
+
? (0, client_errors_js_1.withErrorCause)(readError, err)
|
|
4177
2431
|
: readError;
|
|
4178
2432
|
}
|
|
4179
2433
|
// `response.ok` is true for the entire 200-299 range, so partial-
|
|
@@ -4199,10 +2453,10 @@ class QURLClient {
|
|
|
4199
2453
|
});
|
|
4200
2454
|
}
|
|
4201
2455
|
return {
|
|
4202
|
-
data: undefined,
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
2456
|
+
envelope: { data: undefined },
|
|
2457
|
+
status: response.status,
|
|
2458
|
+
bodyEmpty: responseBody.length === 0,
|
|
2459
|
+
headers: captureResponseHeaders ? response.headers : undefined,
|
|
4206
2460
|
};
|
|
4207
2461
|
}
|
|
4208
2462
|
// Exact no-content helpers must inspect both axes of the success
|
|
@@ -4212,20 +2466,20 @@ class QURLClient {
|
|
|
4212
2466
|
(isPassthrough && allowEmptyPassthroughBody)) &&
|
|
4213
2467
|
responseBody.length === 0) {
|
|
4214
2468
|
return {
|
|
4215
|
-
data: undefined,
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
2469
|
+
envelope: { data: undefined },
|
|
2470
|
+
status: response.status,
|
|
2471
|
+
bodyEmpty: true,
|
|
2472
|
+
headers: captureResponseHeaders ? response.headers : undefined,
|
|
4219
2473
|
};
|
|
4220
2474
|
}
|
|
4221
2475
|
try {
|
|
4222
2476
|
const json = JSON.parse(responseBody);
|
|
4223
2477
|
return {
|
|
4224
|
-
...json,
|
|
4225
|
-
|
|
2478
|
+
envelope: { ...json },
|
|
2479
|
+
status: response.status,
|
|
4226
2480
|
// JSON.parse("") throws, so parsed JSON is necessarily non-empty.
|
|
4227
|
-
|
|
4228
|
-
|
|
2481
|
+
bodyEmpty: false,
|
|
2482
|
+
headers: captureResponseHeaders ? response.headers : undefined,
|
|
4229
2483
|
};
|
|
4230
2484
|
}
|
|
4231
2485
|
catch {
|
|
@@ -4236,12 +2490,12 @@ class QURLClient {
|
|
|
4236
2490
|
// by-class don't miss the contract failure.
|
|
4237
2491
|
this.log(`non-JSON body on ${isPassthrough ? "passthrough" : "success"} response ${response.status}`, {
|
|
4238
2492
|
status: response.status,
|
|
4239
|
-
content_type: boundedErrorSnippet(response.headers.get("content-type")),
|
|
2493
|
+
content_type: (0, client_errors_js_1.boundedErrorSnippet)(response.headers.get("content-type")),
|
|
4240
2494
|
});
|
|
4241
2495
|
throw (0, errors_js_1.createError)({
|
|
4242
2496
|
status: response.status,
|
|
4243
2497
|
code: errors_js_1.ERROR_CODE_UNEXPECTED_RESPONSE,
|
|
4244
|
-
title: boundedErrorSnippet(response.statusText) || `HTTP ${response.status}`,
|
|
2498
|
+
title: (0, client_errors_js_1.boundedErrorSnippet)(response.statusText) || `HTTP ${response.status}`,
|
|
4245
2499
|
detail: `Expected JSON response body on HTTP ${response.status} but received non-JSON content`,
|
|
4246
2500
|
});
|
|
4247
2501
|
}
|
|
@@ -4272,13 +2526,13 @@ class QURLClient {
|
|
|
4272
2526
|
// 3. err.title (RFC 7807 required field)
|
|
4273
2527
|
// 4. HTTP status (final safety net)
|
|
4274
2528
|
// This prevents `"Title (403): undefined"` when the API omits detail.
|
|
4275
|
-
const detail = boundedErrorSnippet(err.detail) ??
|
|
4276
|
-
boundedErrorSnippet(err.message) ??
|
|
4277
|
-
boundedErrorSnippet(err.title) ??
|
|
2529
|
+
const detail = (0, client_errors_js_1.boundedErrorSnippet)(err.detail) ??
|
|
2530
|
+
(0, client_errors_js_1.boundedErrorSnippet)(err.message) ??
|
|
2531
|
+
(0, client_errors_js_1.boundedErrorSnippet)(err.title) ??
|
|
4278
2532
|
`HTTP ${response.status}`;
|
|
4279
2533
|
// HTTP/2 omits reason-phrases — `statusText` may be "".
|
|
4280
|
-
const title = boundedErrorSnippet(err.title) ??
|
|
4281
|
-
boundedErrorSnippet(response.statusText) ??
|
|
2534
|
+
const title = (0, client_errors_js_1.boundedErrorSnippet)(err.title) ??
|
|
2535
|
+
(0, client_errors_js_1.boundedErrorSnippet)(response.statusText) ??
|
|
4282
2536
|
`HTTP ${response.status}`;
|
|
4283
2537
|
// The transport status is authoritative for error classification. A
|
|
4284
2538
|
// conflicting or malformed RFC 7807 body must not turn a real 5xx into
|
|
@@ -4286,13 +2540,14 @@ class QURLClient {
|
|
|
4286
2540
|
const status = response.status;
|
|
4287
2541
|
return {
|
|
4288
2542
|
status,
|
|
4289
|
-
code: boundedErrorIdentifier(err.code) ?? errors_js_1.ERROR_CODE_UNKNOWN,
|
|
2543
|
+
code: (0, client_errors_js_1.boundedErrorIdentifier)(err.code) ?? errors_js_1.ERROR_CODE_UNKNOWN,
|
|
4290
2544
|
title,
|
|
4291
2545
|
detail,
|
|
4292
|
-
type: boundedErrorIdentifier(err.type),
|
|
4293
|
-
instance: boundedErrorIdentifier(err.instance),
|
|
4294
|
-
invalid_fields: boundedInvalidFields(err.invalid_fields),
|
|
4295
|
-
request_id: boundedErrorIdentifier(json.meta?.request_id),
|
|
2546
|
+
type: (0, client_errors_js_1.boundedErrorIdentifier)(err.type),
|
|
2547
|
+
instance: (0, client_errors_js_1.boundedErrorIdentifier)(err.instance),
|
|
2548
|
+
invalid_fields: (0, client_errors_js_1.boundedInvalidFields)(err.invalid_fields),
|
|
2549
|
+
request_id: (0, client_errors_js_1.boundedErrorIdentifier)(json.meta?.request_id),
|
|
2550
|
+
tombstone: response.status === 410 ? (0, client_errors_js_1.boundedTombstone)(json.meta?.tombstone) : undefined,
|
|
4296
2551
|
retry_after: this.parseRetryAfter(response),
|
|
4297
2552
|
};
|
|
4298
2553
|
}
|
|
@@ -4302,7 +2557,7 @@ class QURLClient {
|
|
|
4302
2557
|
// status-only safety net below.
|
|
4303
2558
|
this.log(`unexpected error response shape from ${response.status}`, {
|
|
4304
2559
|
status: response.status,
|
|
4305
|
-
body_keys: typeof json === "object" && json !== null ? boundedObjectKeys(json) : undefined,
|
|
2560
|
+
body_keys: typeof json === "object" && json !== null ? (0, client_errors_js_1.boundedObjectKeys)(json) : undefined,
|
|
4306
2561
|
});
|
|
4307
2562
|
}
|
|
4308
2563
|
catch {
|
|
@@ -4310,10 +2565,10 @@ class QURLClient {
|
|
|
4310
2565
|
// malformed envelope, and fall through to the status-only safety net.
|
|
4311
2566
|
this.log(`non-JSON error response from ${response.status}`, {
|
|
4312
2567
|
status: response.status,
|
|
4313
|
-
content_type: boundedErrorSnippet(response.headers.get("content-type")),
|
|
2568
|
+
content_type: (0, client_errors_js_1.boundedErrorSnippet)(response.headers.get("content-type")),
|
|
4314
2569
|
});
|
|
4315
2570
|
}
|
|
4316
|
-
const statusText = boundedErrorSnippet(response.statusText) || `HTTP ${response.status}`;
|
|
2571
|
+
const statusText = (0, client_errors_js_1.boundedErrorSnippet)(response.statusText) || `HTTP ${response.status}`;
|
|
4317
2572
|
return {
|
|
4318
2573
|
status: response.status,
|
|
4319
2574
|
code: errors_js_1.ERROR_CODE_UNKNOWN,
|
|
@@ -4339,8 +2594,8 @@ class QURLClient {
|
|
|
4339
2594
|
const trimmed = header.trim();
|
|
4340
2595
|
if (/^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(trimmed)) {
|
|
4341
2596
|
const seconds = Math.ceil((Date.parse(trimmed) - Date.now()) / 1000);
|
|
4342
|
-
return Number.isSafeInteger(seconds)
|
|
4343
|
-
? seconds
|
|
2597
|
+
return Number.isSafeInteger(seconds)
|
|
2598
|
+
? Math.min(Math.max(0, seconds), client_limits_js_1.RETRY_AFTER_PARSE_LIMIT_S)
|
|
4344
2599
|
: undefined;
|
|
4345
2600
|
}
|
|
4346
2601
|
if (!/^\d+$/.test(trimmed)) {
|
|
@@ -4355,10 +2610,10 @@ class QURLClient {
|
|
|
4355
2610
|
// `*1000` ms-conversion in retryDelay before the hard cap clamps
|
|
4356
2611
|
// them — fail fast instead, with a debug breadcrumb. Cutoff matches
|
|
4357
2612
|
// the hard cap so a clean cap-bounded value still parses.
|
|
4358
|
-
if (value > RETRY_AFTER_PARSE_LIMIT_S) {
|
|
2613
|
+
if (value > client_limits_js_1.RETRY_AFTER_PARSE_LIMIT_S) {
|
|
4359
2614
|
this.log("Retry-After header exceeded parse limit, using exponential backoff", {
|
|
4360
2615
|
header_value: header.slice(0, 100),
|
|
4361
|
-
limit_seconds: RETRY_AFTER_PARSE_LIMIT_S,
|
|
2616
|
+
limit_seconds: client_limits_js_1.RETRY_AFTER_PARSE_LIMIT_S,
|
|
4362
2617
|
});
|
|
4363
2618
|
return undefined;
|
|
4364
2619
|
}
|
|
@@ -4380,24 +2635,24 @@ class QURLClient {
|
|
|
4380
2635
|
retryDelay(attempt, lastError) {
|
|
4381
2636
|
if (lastError instanceof errors_js_1.QURLError && lastError.retryAfter !== undefined) {
|
|
4382
2637
|
const requestedMs = lastError.retryAfter * 1000;
|
|
4383
|
-
if (requestedMs > RETRY_AFTER_HARD_CAP_MS) {
|
|
2638
|
+
if (requestedMs > client_limits_js_1.RETRY_AFTER_HARD_CAP_MS) {
|
|
4384
2639
|
this.log("Retry-After exceeded hard cap, truncating", {
|
|
4385
2640
|
requested_seconds: lastError.retryAfter,
|
|
4386
|
-
capped_ms: RETRY_AFTER_HARD_CAP_MS,
|
|
2641
|
+
capped_ms: client_limits_js_1.RETRY_AFTER_HARD_CAP_MS,
|
|
4387
2642
|
});
|
|
4388
|
-
return RETRY_AFTER_HARD_CAP_MS;
|
|
2643
|
+
return client_limits_js_1.RETRY_AFTER_HARD_CAP_MS;
|
|
4389
2644
|
}
|
|
4390
2645
|
return requestedMs;
|
|
4391
2646
|
}
|
|
4392
|
-
const base = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
2647
|
+
const base = client_limits_js_1.RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 1);
|
|
4393
2648
|
const jitter = Math.random() * base * 0.5;
|
|
4394
|
-
return Math.min(base + jitter, RETRY_MAX_DELAY_MS);
|
|
2649
|
+
return Math.min(base + jitter, client_limits_js_1.RETRY_MAX_DELAY_MS);
|
|
4395
2650
|
}
|
|
4396
2651
|
classifyFetchError(err, requestSignal) {
|
|
4397
2652
|
// AbortSignal.timeout is the only timeout source owned by this SDK. Keep
|
|
4398
2653
|
// this check tied to that signal instead of trusting an injected fetch's
|
|
4399
2654
|
// AbortError or TimeoutError name.
|
|
4400
|
-
if (isSdkTimeout(err, requestSignal)) {
|
|
2655
|
+
if ((0, client_errors_js_1.isSdkTimeout)(err, requestSignal)) {
|
|
4401
2656
|
return new errors_js_1.TimeoutError("Request timed out", { cause: err });
|
|
4402
2657
|
}
|
|
4403
2658
|
const cause = err instanceof Error ? err : undefined;
|
|
@@ -4409,8 +2664,8 @@ class QURLClient {
|
|
|
4409
2664
|
if (!(err instanceof Error))
|
|
4410
2665
|
return undefined;
|
|
4411
2666
|
if (!response.ok) {
|
|
4412
|
-
const statusText = boundedErrorSnippet(response.statusText) || `HTTP ${response.status}`;
|
|
4413
|
-
return withErrorCause((0, errors_js_1.createError)({
|
|
2667
|
+
const statusText = (0, client_errors_js_1.boundedErrorSnippet)(response.statusText) || `HTTP ${response.status}`;
|
|
2668
|
+
return (0, client_errors_js_1.withErrorCause)((0, errors_js_1.createError)({
|
|
4414
2669
|
status: response.status,
|
|
4415
2670
|
code: errors_js_1.ERROR_CODE_UNKNOWN,
|
|
4416
2671
|
title: statusText,
|
|
@@ -4418,100 +2673,18 @@ class QURLClient {
|
|
|
4418
2673
|
retry_after: this.parseRetryAfter(response),
|
|
4419
2674
|
}), err);
|
|
4420
2675
|
}
|
|
4421
|
-
if (isSdkTimeout(err, requestSignal)) {
|
|
2676
|
+
if ((0, client_errors_js_1.isSdkTimeout)(err, requestSignal)) {
|
|
4422
2677
|
return new errors_js_1.TimeoutError(`Request timed out while reading response body after HTTP ${response.status}`, { cause: err });
|
|
4423
2678
|
}
|
|
4424
2679
|
return new errors_js_1.NetworkError(`Network failure while reading response body after HTTP ${response.status}`, { cause: err });
|
|
4425
2680
|
}
|
|
4426
2681
|
}
|
|
4427
2682
|
exports.QURLClient = QURLClient;
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
// A true private field (not a module-scope WeakMap registry, which
|
|
4437
|
-
// CONTRIBUTING.md's dual-build rule bars) so the binding stays on the
|
|
4438
|
-
// instance and out of Object.keys/JSON.stringify output.
|
|
4439
|
-
#client;
|
|
4440
|
-
/** The public resource CRID. */
|
|
4441
|
-
crid;
|
|
4442
|
-
/** The private URL protected by this resource, when known. */
|
|
4443
|
-
targetUrl;
|
|
4444
|
-
/**
|
|
4445
|
-
* Full server-populated {@link Resource} metadata when the handle came
|
|
4446
|
-
* from an API response; `undefined` for {@link QURLClient.resourceByCrid}
|
|
4447
|
-
* handles and for the one-call {@link QURLClient.createPortalForUrl} path.
|
|
4448
|
-
*/
|
|
4449
|
-
details;
|
|
4450
|
-
constructor(client, id, targetUrl, details) {
|
|
4451
|
-
this.#client = client;
|
|
4452
|
-
this.crid = id;
|
|
4453
|
-
this.targetUrl = targetUrl;
|
|
4454
|
-
this.details = details;
|
|
4455
|
-
}
|
|
4456
|
-
/**
|
|
4457
|
-
* Whether `resource` was produced by `client`. A static because the
|
|
4458
|
-
* binding lives in a private field that only this class can read; used
|
|
4459
|
-
* by {@link QURLClient.createPortal} to refuse handles bound to a
|
|
4460
|
-
* different client.
|
|
4461
|
-
*/
|
|
4462
|
-
static isBoundTo(resource, client) {
|
|
4463
|
-
return resource.#client === client;
|
|
4464
|
-
}
|
|
4465
|
-
/**
|
|
4466
|
-
* Mint a short-lived qURL link (a portal) for this resource. See
|
|
4467
|
-
* {@link QURLClient.createPortal} for the option reference.
|
|
4468
|
-
*/
|
|
4469
|
-
async createPortal(opts = {}, requestOptions) {
|
|
4470
|
-
return this.#client.createPortal(this, opts, requestOptions);
|
|
4471
|
-
}
|
|
4472
|
-
}
|
|
4473
|
-
exports.ProtectedResource = ProtectedResource;
|
|
4474
|
-
const CONNECTOR_RESOURCE_CONSTRUCTOR_TOKEN = Symbol("validated ConnectorResource");
|
|
4475
|
-
/**
|
|
4476
|
-
* A validated qURL Connector resource bound to the client that loaded it.
|
|
4477
|
-
*
|
|
4478
|
-
* Resource identity, reverse-routing identity, and NHP admission identity are
|
|
4479
|
-
* distinct server-issued values. Callers must consume each field verbatim and
|
|
4480
|
-
* must never derive one from another.
|
|
4481
|
-
*/
|
|
4482
|
-
class ConnectorResource {
|
|
4483
|
-
#client;
|
|
4484
|
-
resourcePublicKey;
|
|
4485
|
-
/** Required public identifier, verified against the returned resource key. */
|
|
4486
|
-
crid;
|
|
4487
|
-
connectorRoutingId;
|
|
4488
|
-
knockResourceId;
|
|
4489
|
-
slug;
|
|
4490
|
-
alias;
|
|
4491
|
-
/** @internal Instances are returned by QURLClient after wire validation. */
|
|
4492
|
-
constructor(client, details, validationToken) {
|
|
4493
|
-
if (validationToken !== CONNECTOR_RESOURCE_CONSTRUCTOR_TOKEN) {
|
|
4494
|
-
throw clientValidationError("ConnectorResource cannot be constructed directly; load it through QURLClient");
|
|
4495
|
-
}
|
|
4496
|
-
this.#client = client;
|
|
4497
|
-
this.resourcePublicKey = details.resource_id;
|
|
4498
|
-
this.crid = details.crid;
|
|
4499
|
-
this.connectorRoutingId = details.connector_routing_id;
|
|
4500
|
-
this.knockResourceId = details.knock_resource_id;
|
|
4501
|
-
this.slug = details.slug;
|
|
4502
|
-
this.alias = details.alias ?? undefined;
|
|
4503
|
-
}
|
|
4504
|
-
/** Mint a short-lived portal for this Connector resource. */
|
|
4505
|
-
async createPortal(opts = {}, requestOptions) {
|
|
4506
|
-
return this.#client.createPortal(this.crid, opts, requestOptions);
|
|
4507
|
-
}
|
|
4508
|
-
}
|
|
4509
|
-
exports.ConnectorResource = ConnectorResource;
|
|
4510
|
-
function sameConsumerPolicy(actual, expected, foundExisting) {
|
|
4511
|
-
return (actual?.mode === expected.mode &&
|
|
4512
|
-
(actual?.version === expected.version ||
|
|
4513
|
-
(foundExisting &&
|
|
4514
|
-
Number.isSafeInteger(actual?.version) &&
|
|
4515
|
-
Number(actual?.version) > expected.version)) &&
|
|
4516
|
-
actual?.contract_id === expected.contract_id);
|
|
4517
|
-
}
|
|
2683
|
+
var client_management_js_2 = require("./client-management.js");
|
|
2684
|
+
Object.defineProperty(exports, "isApiKeyRequestScope", { enumerable: true, get: function () { return client_management_js_2.isApiKeyRequestScope; } });
|
|
2685
|
+
var client_portals_js_2 = require("./client-portals.js");
|
|
2686
|
+
Object.defineProperty(exports, "SIGNED_FRAGMENT_RE", { enumerable: true, get: function () { return client_portals_js_2.SIGNED_FRAGMENT_RE; } });
|
|
2687
|
+
var client_portals_js_3 = require("./client-portals.js");
|
|
2688
|
+
Object.defineProperty(exports, "ProtectedResource", { enumerable: true, get: function () { return client_portals_js_3.ProtectedResource; } });
|
|
2689
|
+
var client_connectors_js_2 = require("./client-connectors.js");
|
|
2690
|
+
Object.defineProperty(exports, "ConnectorResource", { enumerable: true, get: function () { return client_connectors_js_2.ConnectorResource; } });
|