@layervai/qurl 3.1.0 → 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.
Files changed (152) hide show
  1. package/README.md +259 -46
  2. package/dist/cjs/client-connectors.d.ts +44 -0
  3. package/dist/cjs/client-connectors.js +189 -0
  4. package/dist/cjs/client-delegated.d.ts +30 -0
  5. package/dist/cjs/client-delegated.js +312 -0
  6. package/dist/cjs/client-errors.d.ts +44 -0
  7. package/dist/cjs/client-errors.js +219 -0
  8. package/dist/cjs/client-http-types.d.ts +37 -0
  9. package/dist/cjs/client-http-types.js +2 -0
  10. package/dist/cjs/client-input-fields.d.ts +53 -0
  11. package/dist/cjs/client-input-fields.js +262 -0
  12. package/dist/cjs/client-limits.d.ts +36 -0
  13. package/dist/cjs/client-limits.js +61 -0
  14. package/dist/cjs/client-management.d.ts +12 -0
  15. package/dist/cjs/client-management.js +119 -0
  16. package/dist/cjs/client-paths.d.ts +21 -0
  17. package/dist/cjs/client-paths.js +143 -0
  18. package/dist/cjs/client-portals.d.ts +82 -0
  19. package/dist/cjs/client-portals.js +244 -0
  20. package/dist/cjs/client-resources.d.ts +29 -0
  21. package/dist/cjs/client-resources.js +331 -0
  22. package/dist/cjs/client-validation.d.ts +35 -0
  23. package/dist/cjs/client-validation.js +326 -0
  24. package/dist/cjs/client.d.ts +45 -75
  25. package/dist/cjs/client.js +621 -2448
  26. package/dist/cjs/errors.d.ts +9 -1
  27. package/dist/cjs/errors.js +10 -1
  28. package/dist/cjs/index.d.ts +4 -2
  29. package/dist/cjs/index.js +4 -1
  30. package/dist/cjs/response-metadata.d.ts +13 -0
  31. package/dist/cjs/response-metadata.js +38 -0
  32. package/dist/cjs/share.js +2 -0
  33. package/dist/cjs/target-path.d.ts +2 -0
  34. package/dist/cjs/target-path.js +34 -0
  35. package/dist/cjs/types.d.ts +83 -7
  36. package/dist/cjs/version.d.ts +1 -1
  37. package/dist/cjs/version.js +1 -1
  38. package/dist/esm/client-connectors.d.ts +44 -0
  39. package/dist/esm/client-connectors.js +180 -0
  40. package/dist/esm/client-delegated.d.ts +30 -0
  41. package/dist/esm/client-delegated.js +295 -0
  42. package/dist/esm/client-errors.d.ts +44 -0
  43. package/dist/esm/client-errors.js +206 -0
  44. package/dist/esm/client-http-types.d.ts +37 -0
  45. package/dist/esm/client-http-types.js +1 -0
  46. package/dist/esm/client-input-fields.d.ts +53 -0
  47. package/dist/esm/client-input-fields.js +258 -0
  48. package/dist/esm/client-limits.d.ts +36 -0
  49. package/dist/esm/client-limits.js +58 -0
  50. package/dist/esm/client-management.d.ts +12 -0
  51. package/dist/esm/client-management.js +113 -0
  52. package/dist/esm/client-paths.d.ts +21 -0
  53. package/dist/esm/client-paths.js +138 -0
  54. package/dist/esm/client-portals.d.ts +82 -0
  55. package/dist/esm/client-portals.js +236 -0
  56. package/dist/esm/client-resources.d.ts +29 -0
  57. package/dist/esm/client-resources.js +323 -0
  58. package/dist/esm/client-validation.d.ts +35 -0
  59. package/dist/esm/client-validation.js +302 -0
  60. package/dist/esm/client.d.ts +45 -76
  61. package/dist/esm/client.js +318 -2147
  62. package/dist/esm/crid.d.ts +0 -1
  63. package/dist/esm/crid.js +0 -1
  64. package/dist/esm/errors.d.ts +9 -2
  65. package/dist/esm/errors.js +9 -1
  66. package/dist/esm/index.d.ts +4 -3
  67. package/dist/esm/index.js +2 -2
  68. package/dist/esm/internal.d.ts +0 -1
  69. package/dist/esm/internal.js +0 -1
  70. package/dist/esm/node/agent-runtime.d.ts +0 -1
  71. package/dist/esm/node/agent-runtime.js +0 -1
  72. package/dist/esm/node/agent-state.d.ts +0 -1
  73. package/dist/esm/node/agent-state.js +0 -1
  74. package/dist/esm/node/agent-transport.d.ts +0 -1
  75. package/dist/esm/node/agent-transport.js +0 -1
  76. package/dist/esm/node/deployment.d.ts +0 -1
  77. package/dist/esm/node/deployment.js +0 -1
  78. package/dist/esm/node/file-agent-state.d.ts +0 -1
  79. package/dist/esm/node/file-agent-state.js +0 -1
  80. package/dist/esm/node/native-udp.d.ts +0 -1
  81. package/dist/esm/node/native-udp.js +0 -1
  82. package/dist/esm/node/nhp-wire.d.ts +0 -1
  83. package/dist/esm/node/nhp-wire.js +0 -1
  84. package/dist/esm/node/portal-opener.d.ts +0 -1
  85. package/dist/esm/node/portal-opener.js +0 -1
  86. package/dist/esm/node/provider.d.ts +0 -1
  87. package/dist/esm/node/provider.js +0 -1
  88. package/dist/esm/node/qv2.d.ts +0 -1
  89. package/dist/esm/node/qv2.js +0 -1
  90. package/dist/esm/node/relay.d.ts +0 -1
  91. package/dist/esm/node/relay.js +0 -1
  92. package/dist/esm/node/sealed-agent-state.d.ts +0 -1
  93. package/dist/esm/node/sealed-agent-state.js +0 -1
  94. package/dist/esm/node/strict-json.d.ts +0 -1
  95. package/dist/esm/node/strict-json.js +0 -1
  96. package/dist/esm/node.d.ts +0 -1
  97. package/dist/esm/node.js +0 -1
  98. package/dist/esm/response-metadata.d.ts +13 -0
  99. package/dist/esm/response-metadata.js +33 -0
  100. package/dist/esm/share.d.ts +0 -1
  101. package/dist/esm/share.js +2 -1
  102. package/dist/esm/target-path.d.ts +2 -0
  103. package/dist/esm/target-path.js +30 -0
  104. package/dist/esm/types.d.ts +83 -8
  105. package/dist/esm/types.js +0 -1
  106. package/dist/esm/version.d.ts +1 -2
  107. package/dist/esm/version.js +1 -2
  108. package/package.json +9 -7
  109. package/dist/esm/client.d.ts.map +0 -1
  110. package/dist/esm/client.js.map +0 -1
  111. package/dist/esm/crid.d.ts.map +0 -1
  112. package/dist/esm/crid.js.map +0 -1
  113. package/dist/esm/errors.d.ts.map +0 -1
  114. package/dist/esm/errors.js.map +0 -1
  115. package/dist/esm/index.d.ts.map +0 -1
  116. package/dist/esm/index.js.map +0 -1
  117. package/dist/esm/internal.d.ts.map +0 -1
  118. package/dist/esm/internal.js.map +0 -1
  119. package/dist/esm/node/agent-runtime.d.ts.map +0 -1
  120. package/dist/esm/node/agent-runtime.js.map +0 -1
  121. package/dist/esm/node/agent-state.d.ts.map +0 -1
  122. package/dist/esm/node/agent-state.js.map +0 -1
  123. package/dist/esm/node/agent-transport.d.ts.map +0 -1
  124. package/dist/esm/node/agent-transport.js.map +0 -1
  125. package/dist/esm/node/deployment.d.ts.map +0 -1
  126. package/dist/esm/node/deployment.js.map +0 -1
  127. package/dist/esm/node/file-agent-state.d.ts.map +0 -1
  128. package/dist/esm/node/file-agent-state.js.map +0 -1
  129. package/dist/esm/node/native-udp.d.ts.map +0 -1
  130. package/dist/esm/node/native-udp.js.map +0 -1
  131. package/dist/esm/node/nhp-wire.d.ts.map +0 -1
  132. package/dist/esm/node/nhp-wire.js.map +0 -1
  133. package/dist/esm/node/portal-opener.d.ts.map +0 -1
  134. package/dist/esm/node/portal-opener.js.map +0 -1
  135. package/dist/esm/node/provider.d.ts.map +0 -1
  136. package/dist/esm/node/provider.js.map +0 -1
  137. package/dist/esm/node/qv2.d.ts.map +0 -1
  138. package/dist/esm/node/qv2.js.map +0 -1
  139. package/dist/esm/node/relay.d.ts.map +0 -1
  140. package/dist/esm/node/relay.js.map +0 -1
  141. package/dist/esm/node/sealed-agent-state.d.ts.map +0 -1
  142. package/dist/esm/node/sealed-agent-state.js.map +0 -1
  143. package/dist/esm/node/strict-json.d.ts.map +0 -1
  144. package/dist/esm/node/strict-json.js.map +0 -1
  145. package/dist/esm/node.d.ts.map +0 -1
  146. package/dist/esm/node.js.map +0 -1
  147. package/dist/esm/share.d.ts.map +0 -1
  148. package/dist/esm/share.js.map +0 -1
  149. package/dist/esm/types.d.ts.map +0 -1
  150. package/dist/esm/types.js.map +0 -1
  151. package/dist/esm/version.d.ts.map +0 -1
  152. package/dist/esm/version.js.map +0 -1
@@ -0,0 +1,206 @@
1
+ import { ERROR_CODE_CLIENT_VALIDATION, ERROR_CODE_UNEXPECTED_RESPONSE, ValidationError, } from "./errors.js";
2
+ import { MAX_DEBUG_BODY_KEYS, MAX_DIAGNOSTIC_COLLECTION_BYTES, MAX_ERROR_SNIPPET_BYTES, MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS, MAX_INVALID_FIELD_ENTRIES, } from "./client-limits.js";
3
+ export const TEXT_ENCODER = new TextEncoder();
4
+ const ERROR_SNIPPET_ELLIPSIS = "...";
5
+ const ERROR_SNIPPET_ELLIPSIS_BYTES = 3;
6
+ // RFC 8259 JSON exchanged between systems must be valid UTF-8. A non-fatal
7
+ // decoder would replace malformed bytes with U+FFFD before JSON.parse and hide
8
+ // the producer's exact contract violation.
9
+ export const TEXT_DECODER = new TextDecoder("utf-8", { fatal: true });
10
+ /**
11
+ * Construct a {@link ValidationError} for a client-side pre-flight check.
12
+ * Uses `status: 0` (matching {@link NetworkError}/{@link TimeoutError}) and
13
+ * `code: "client_validation"` so catch-by-class still works and callers can
14
+ * tell the error originated inside the SDK rather than from the API.
15
+ */
16
+ export function clientValidationError(detail) {
17
+ return new ValidationError({
18
+ status: 0,
19
+ code: ERROR_CODE_CLIENT_VALIDATION,
20
+ title: "Invalid Argument",
21
+ detail,
22
+ });
23
+ }
24
+ /**
25
+ * Construct a {@link ValidationError} for the case where the API returned
26
+ * a response that parsed as JSON but didn't match the expected schema
27
+ * shape (e.g. 400 with a body that isn't a {@link BatchCreateOutput}).
28
+ *
29
+ * Distinct from {@link clientValidationError} so callers can `.code`-branch
30
+ * between "I passed bad input locally" (`"client_validation"`) and "the
31
+ * server returned a body I can't interpret" (`"unexpected_response"`).
32
+ * Uses `status: 0` by default because a logical shape mismatch is not tied to
33
+ * the HTTP classification. Callers that enforce an observed HTTP contract
34
+ * pass that status explicitly.
35
+ *
36
+ * Threads `request_id` through to {@link QURLError.requestId} when the
37
+ * server-side correlation ID is available — operators debugging
38
+ * "unexpected response" tickets need it on the *error* path too, not
39
+ * just on success/passthrough returns.
40
+ */
41
+ export function unexpectedResponseError(detail, options = {}) {
42
+ // Every detail passed here is an SDK-authored contract message whose dynamic
43
+ // inputs were validated by the caller-side shape guards. Do not pass raw
44
+ // server strings to this helper. Server diagnostics use boundedErrorSnippet.
45
+ const safeRequestId = boundedErrorIdentifier(options.requestId);
46
+ return new ValidationError({
47
+ status: options.status ?? 0,
48
+ code: ERROR_CODE_UNEXPECTED_RESPONSE,
49
+ title: "Unexpected Response",
50
+ detail,
51
+ request_id: safeRequestId,
52
+ });
53
+ }
54
+ export function withErrorCause(error, cause) {
55
+ // Match the native Error `cause` descriptor. In particular, do not leak a
56
+ // transport object through Object.keys(), object spread, or JSON logging.
57
+ Object.defineProperty(error, "cause", {
58
+ value: cause,
59
+ writable: true,
60
+ configurable: true,
61
+ });
62
+ return error;
63
+ }
64
+ export function isIndependentAbort(error, requestSignal) {
65
+ return (error instanceof Error &&
66
+ (error.name === "AbortError" || error.name === "TimeoutError") &&
67
+ !requestSignal.aborted);
68
+ }
69
+ export function isSdkTimeout(error, requestSignal) {
70
+ return (error instanceof Error &&
71
+ requestSignal.aborted &&
72
+ (error.name === "TimeoutError" || error.name === "AbortError"));
73
+ }
74
+ /**
75
+ * Normalize a server-provided problem title/detail into a single-line,
76
+ * UTF-8-safe snippet. Error messages must stay bounded even when a proxy or
77
+ * compromised upstream returns very large structured problem fields.
78
+ */
79
+ function normalizedErrorSnippet(value) {
80
+ if (typeof value !== "string")
81
+ return undefined;
82
+ // Bound source work before the Unicode regular expressions allocate
83
+ // normalized copies. The response body cap is much larger than one retained
84
+ // diagnostic, so processing the full server string would waste memory.
85
+ const normalized = value
86
+ .slice(0, MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS)
87
+ // Strip control characters plus bidi override/isolate controls that can
88
+ // visually reorder diagnostics. Preserve other formatting characters
89
+ // such as ZWJ/ZWNJ, which are required by legitimate scripts and emoji.
90
+ .replace(/[\p{Cc}\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/gu, " ")
91
+ .trim()
92
+ .replace(/\s+/g, " ");
93
+ return normalized === "" ? undefined : normalized;
94
+ }
95
+ export function boundedErrorSnippet(value) {
96
+ const sourceTruncated = typeof value === "string" && value.length > MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS;
97
+ const normalized = normalizedErrorSnippet(value);
98
+ if (normalized === undefined)
99
+ return undefined;
100
+ const contentLimit = sourceTruncated
101
+ ? MAX_ERROR_SNIPPET_BYTES - ERROR_SNIPPET_ELLIPSIS_BYTES
102
+ : MAX_ERROR_SNIPPET_BYTES;
103
+ // UTF-8 uses at most three bytes per UTF-16 code unit. Most diagnostics are
104
+ // short ASCII, so avoid an allocation only to prove they fit.
105
+ if (normalized.length * 3 <= contentLimit) {
106
+ return sourceTruncated ? `${normalized}${ERROR_SNIPPET_ELLIPSIS}` : normalized;
107
+ }
108
+ const encoded = TEXT_ENCODER.encode(normalized);
109
+ if (encoded.byteLength <= contentLimit) {
110
+ return sourceTruncated ? `${normalized}${ERROR_SNIPPET_ELLIPSIS}` : normalized;
111
+ }
112
+ let end = MAX_ERROR_SNIPPET_BYTES - ERROR_SNIPPET_ELLIPSIS_BYTES;
113
+ // Do not split a multi-byte UTF-8 sequence at the cap.
114
+ while (end > 0 && (encoded[end] & 0xc0) === 0x80)
115
+ end--;
116
+ return `${TEXT_DECODER.decode(encoded.subarray(0, end))}${ERROR_SNIPPET_ELLIPSIS}`;
117
+ }
118
+ /** Keep server identifiers useful as exact machine-readable discriminants. */
119
+ export function boundedErrorIdentifier(value) {
120
+ if (typeof value !== "string" || value.length > MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS) {
121
+ return undefined;
122
+ }
123
+ const normalized = normalizedErrorSnippet(value);
124
+ if (normalized === undefined ||
125
+ normalized !== value ||
126
+ TEXT_ENCODER.encode(value).byteLength > MAX_ERROR_SNIPPET_BYTES) {
127
+ return undefined;
128
+ }
129
+ return value;
130
+ }
131
+ /** Keep only the documented retained metadata, with bounded wire values. */
132
+ export function boundedTombstone(value) {
133
+ if (typeof value !== "object" || value === null || Array.isArray(value))
134
+ return undefined;
135
+ const raw = value;
136
+ const timestamp = boundedErrorIdentifier(raw.tombstoned_at);
137
+ if (timestamp === undefined ||
138
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(timestamp) ||
139
+ !Number.isFinite(Date.parse(timestamp)))
140
+ return undefined;
141
+ const out = { tombstoned_at: timestamp };
142
+ const count = raw.final_access_count;
143
+ // The API omits zero: absence means no snapshot or a redacted count.
144
+ if (typeof count === "number" && Number.isSafeInteger(count) && count >= 1) {
145
+ out.final_access_count = count;
146
+ }
147
+ return out;
148
+ }
149
+ /** Collect at most the first N enumerable own string keys without a full key-array copy. */
150
+ function firstEnumerableOwnKeys(value, limit) {
151
+ const keys = [];
152
+ for (const key in value) {
153
+ if (!Object.prototype.hasOwnProperty.call(value, key))
154
+ continue;
155
+ keys.push(key);
156
+ if (keys.length === limit)
157
+ break;
158
+ }
159
+ return keys;
160
+ }
161
+ /** Bound the structured per-field diagnostics exposed on ValidationError. */
162
+ export function boundedInvalidFields(value) {
163
+ if (typeof value !== "object" || value === null || Array.isArray(value))
164
+ return undefined;
165
+ const entries = [];
166
+ const retainedKeys = new Set();
167
+ let retainedBytes = 0;
168
+ // Bound inspected entries as well as retained entries. A hostile envelope
169
+ // whose values all have the wrong type must not make diagnostics scan every
170
+ // key in the response body.
171
+ for (const rawKey of firstEnumerableOwnKeys(value, MAX_INVALID_FIELD_ENTRIES)) {
172
+ const rawValue = value[rawKey];
173
+ const key = boundedErrorSnippet(rawKey);
174
+ const fieldDetail = boundedErrorSnippet(rawValue);
175
+ if (key !== undefined && fieldDetail !== undefined && !retainedKeys.has(key)) {
176
+ const entryBytes = TEXT_ENCODER.encode(key).byteLength + TEXT_ENCODER.encode(fieldDetail).byteLength;
177
+ // A later, smaller entry can still fit even when this one cannot.
178
+ if (retainedBytes + entryBytes > MAX_DIAGNOSTIC_COLLECTION_BYTES)
179
+ continue;
180
+ retainedKeys.add(key);
181
+ entries.push([key, fieldDetail]);
182
+ retainedBytes += entryBytes;
183
+ }
184
+ }
185
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
186
+ }
187
+ /** Bound server-controlled object keys before forwarding them to a debug sink. */
188
+ export function boundedObjectKeys(value) {
189
+ // Stop before normalization so a hostile envelope cannot force work over
190
+ // every enumerable own key. Some early keys may normalize away; bounded
191
+ // diagnostics are more important here than filling every available slot.
192
+ const retained = [];
193
+ let retainedBytes = 0;
194
+ for (const rawKey of firstEnumerableOwnKeys(value, MAX_DEBUG_BODY_KEYS)) {
195
+ const key = boundedErrorSnippet(rawKey);
196
+ if (key === undefined)
197
+ continue;
198
+ const keyBytes = TEXT_ENCODER.encode(key).byteLength;
199
+ // A later, smaller key can still fit even when this one cannot.
200
+ if (retainedBytes + keyBytes > MAX_DIAGNOSTIC_COLLECTION_BYTES)
201
+ continue;
202
+ retained.push(key);
203
+ retainedBytes += keyBytes;
204
+ }
205
+ return retained;
206
+ }
@@ -0,0 +1,37 @@
1
+ export interface ApiResponse<T> {
2
+ data: T;
3
+ meta?: {
4
+ found_existing?: unknown;
5
+ request_id?: string;
6
+ page_size?: number;
7
+ has_more?: boolean;
8
+ next_cursor?: string;
9
+ };
10
+ }
11
+ export interface HttpResponse<T> {
12
+ envelope: ApiResponse<T>;
13
+ status: number;
14
+ bodyEmpty: boolean;
15
+ headers?: Headers;
16
+ }
17
+ export interface ApiErrorEnvelope {
18
+ error?: {
19
+ type?: string;
20
+ title?: string;
21
+ status?: number;
22
+ detail?: string;
23
+ code?: string;
24
+ instance?: string;
25
+ invalid_fields?: Record<string, string>;
26
+ /**
27
+ * Legacy field from pre-RFC-7807 error shapes. Supported for backward
28
+ * compatibility when the API has been configured to return the older
29
+ * `{ error: { code, message } }` envelope.
30
+ */
31
+ message?: string;
32
+ };
33
+ meta?: {
34
+ request_id?: string;
35
+ tombstone?: unknown;
36
+ };
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ /** Allowlist of known query-param keys for the `list()` endpoint. */
2
+ export declare const LIST_PARAM_KEYS: readonly ["limit", "cursor", "status", "q", "sort", "created_after", "created_before", "expires_before", "expires_after"];
3
+ export declare const REQUEST_OPTION_KEYS: readonly ["idempotencyKey"];
4
+ export declare const DELEGATED_BATCH_INPUT_KEYS: readonly ["mint_capability", "grants"];
5
+ export declare const DELEGATED_BATCH_GRANT_KEYS: readonly ["expires_in", "label", "one_time_use", "max_sessions", "session_duration", "access_policy"];
6
+ export declare const DELEGATED_BATCH_CREATE_OPTION_KEYS: readonly ["idempotencyKey"];
7
+ export declare const DELEGATED_BATCH_GET_OPTION_KEYS: readonly ["etag"];
8
+ export declare function assertExhaustive<T extends true>(_: T): void;
9
+ export declare const CREATE_FIELD_KEYS: readonly ["name", "private", "type", "target_url", "expires_in", "one_time_use", "max_sessions", "session_duration", "label", "access_policy", "custom_domain"];
10
+ /**
11
+ * Fields accepted by `update()`. The empty-input pre-flight check in
12
+ * {@link QURLClient.update} iterates this const — so adding a new field to
13
+ * {@link UpdateInput} without listing it here will fail the
14
+ * paired `assertExhaustive` witness at compile time rather than silently
15
+ * sneaking through an empty-input request.
16
+ */
17
+ export declare const UPDATE_FIELD_KEYS: readonly ["extend_by", "expires_at", "description", "tags"];
18
+ /**
19
+ * Fields accepted by `mintLink()`. Iterated by the null-stripping
20
+ * normalization loop in {@link QURLClient.mintLink} so unknown keys
21
+ * from untyped-JS callers don't leak through to the wire body.
22
+ * Symmetric with `LIST_PARAM_KEYS` / `UPDATE_FIELD_KEYS`.
23
+ */
24
+ export declare const MINT_FIELD_KEYS: readonly ["expires_in", "expires_at", "label", "one_time_use", "max_sessions", "session_duration", "access_policy"];
25
+ export declare const RESOURCE_LIST_PARAM_KEYS: readonly ["cursor", "limit", "alias", "slug", "status", "type"];
26
+ export declare const CREATE_RESOURCE_FIELD_KEYS: readonly ["name", "private", "consumer_access", "type", "target_url", "description", "tags", "custom_domain", "alias", "slug", "find_or_create"];
27
+ export declare const UPDATE_RESOURCE_FIELD_KEYS: readonly ["allowed_device_keys_add", "allowed_device_keys_remove", "consumer_access", "expected_consumer_access_version", "description", "tags", "custom_domain", "preserve_host", "alias"];
28
+ export declare const CREATE_QURL_FOR_RESOURCE_FIELD_KEYS: readonly ["expires_in", "one_time_use", "max_sessions", "session_duration", "label", "access_policy", "target_path"];
29
+ export declare const SHARE_RESOURCE_OPTION_KEYS: readonly ["ttlSeconds"];
30
+ /**
31
+ * Options accepted by the portal-verb `protectUrl` / `createPortal`
32
+ * surface. These are camelCase caller-facing options (the portal verbs are
33
+ * the idiomatic layer); the build helpers map them onto the same wire
34
+ * fields the REST-shaped methods send.
35
+ */
36
+ export declare const PROTECT_URL_OPTION_KEYS: readonly ["name", "private", "consumerAccess", "description", "tags", "customDomain", "alias"];
37
+ export declare const CREATE_PORTAL_OPTION_KEYS: readonly ["name", "validFor", "label", "oneTimeUse", "maxSessions", "sessionDuration", "targetPath"];
38
+ export declare const UPDATE_RESOURCE_QURL_FIELD_KEYS: readonly ["extend_by", "expires_at", "label", "access_policy", "max_sessions", "session_duration"];
39
+ export declare const UPDATE_CUSTOMER_FIELD_KEYS: readonly ["spending_cap_cents"];
40
+ export declare const CREATE_BILLING_CHECKOUT_FIELD_KEYS: readonly ["plan"];
41
+ export declare const REGISTER_DOMAIN_FIELD_KEYS: readonly ["domain"];
42
+ export declare const CREATE_WEBHOOK_FIELD_KEYS: readonly ["url", "events", "description"];
43
+ export declare const UPDATE_WEBHOOK_FIELD_KEYS: readonly ["url", "events", "description", "status"];
44
+ export declare const CREATE_API_KEY_FIELD_KEYS: readonly ["kind", "name", "scopes", "target", "claims", "expires_in"];
45
+ export declare const UPDATE_API_KEY_FIELD_KEYS: readonly ["name", "scopes"];
46
+ export declare const REDEEM_ACCESS_CODE_FIELD_KEYS: readonly ["code", "honeypot", "elapsed_ms"];
47
+ export declare const CREATE_ACCESS_CODE_FIELD_KEYS: readonly ["resource_id", "name", "max_uses", "expires_at"];
48
+ export declare const CONNECTOR_INSTALLATION_LIST_PARAM_KEYS: readonly ["cursor", "limit"];
49
+ export declare const BILLING_INVOICE_LIST_PARAM_KEYS: readonly ["limit", "cursor"];
50
+ export declare const DOMAIN_LIST_PARAM_KEYS: readonly ["limit", "cursor"];
51
+ export declare const WEBHOOK_LIST_PARAM_KEYS: readonly ["limit", "cursor"];
52
+ export declare const WEBHOOK_DELIVERY_LIST_PARAM_KEYS: readonly ["limit", "cursor"];
53
+ export declare const API_KEY_LIST_PARAM_KEYS: readonly ["limit", "cursor", "status"];
@@ -0,0 +1,258 @@
1
+ /** Allowlist of known query-param keys for the `list()` endpoint. */
2
+ export const LIST_PARAM_KEYS = [
3
+ "limit",
4
+ "cursor",
5
+ "status",
6
+ "q",
7
+ "sort",
8
+ "created_after",
9
+ "created_before",
10
+ "expires_before",
11
+ "expires_after",
12
+ ];
13
+ export const REQUEST_OPTION_KEYS = [
14
+ "idempotencyKey",
15
+ ];
16
+ export const DELEGATED_BATCH_INPUT_KEYS = [
17
+ "mint_capability",
18
+ "grants",
19
+ ];
20
+ export const DELEGATED_BATCH_GRANT_KEYS = [
21
+ "expires_in",
22
+ "label",
23
+ "one_time_use",
24
+ "max_sessions",
25
+ "session_duration",
26
+ "access_policy",
27
+ ];
28
+ export const DELEGATED_BATCH_CREATE_OPTION_KEYS = [
29
+ "idempotencyKey",
30
+ ];
31
+ export const DELEGATED_BATCH_GET_OPTION_KEYS = [
32
+ "etag",
33
+ ];
34
+ // Compile-time witness: `Exclude<keyof X, (typeof KEYS)[number]>` is
35
+ // `never` iff KEYS lists every key of X. Paired with the
36
+ // `satisfies readonly (keyof X)[]` clause on the array (which catches
37
+ // the other direction), this gives bidirectional drift detection on
38
+ // the *_KEYS allowlists at zero runtime cost.
39
+ export function assertExhaustive(_) {
40
+ /* type-only check */
41
+ }
42
+ assertExhaustive(true);
43
+ assertExhaustive(true);
44
+ assertExhaustive(true);
45
+ assertExhaustive(true);
46
+ assertExhaustive(true);
47
+ assertExhaustive(true);
48
+ export const CREATE_FIELD_KEYS = [
49
+ "name",
50
+ "private",
51
+ "type",
52
+ "target_url",
53
+ "expires_in",
54
+ "one_time_use",
55
+ "max_sessions",
56
+ "session_duration",
57
+ "label",
58
+ "access_policy",
59
+ "custom_domain",
60
+ ];
61
+ assertExhaustive(true);
62
+ /**
63
+ * Fields accepted by `update()`. The empty-input pre-flight check in
64
+ * {@link QURLClient.update} iterates this const — so adding a new field to
65
+ * {@link UpdateInput} without listing it here will fail the
66
+ * paired `assertExhaustive` witness at compile time rather than silently
67
+ * sneaking through an empty-input request.
68
+ */
69
+ export const UPDATE_FIELD_KEYS = [
70
+ "extend_by",
71
+ "expires_at",
72
+ "description",
73
+ "tags",
74
+ ];
75
+ assertExhaustive(true);
76
+ /**
77
+ * Fields accepted by `mintLink()`. Iterated by the null-stripping
78
+ * normalization loop in {@link QURLClient.mintLink} so unknown keys
79
+ * from untyped-JS callers don't leak through to the wire body.
80
+ * Symmetric with `LIST_PARAM_KEYS` / `UPDATE_FIELD_KEYS`.
81
+ */
82
+ export const MINT_FIELD_KEYS = [
83
+ "expires_in",
84
+ "expires_at",
85
+ "label",
86
+ "one_time_use",
87
+ "max_sessions",
88
+ "session_duration",
89
+ "access_policy",
90
+ ];
91
+ assertExhaustive(true);
92
+ export const RESOURCE_LIST_PARAM_KEYS = [
93
+ "cursor",
94
+ "limit",
95
+ "alias",
96
+ "slug",
97
+ "status",
98
+ "type",
99
+ ];
100
+ assertExhaustive(true);
101
+ export const CREATE_RESOURCE_FIELD_KEYS = [
102
+ "name",
103
+ "private",
104
+ "consumer_access",
105
+ "type",
106
+ "target_url",
107
+ "description",
108
+ "tags",
109
+ "custom_domain",
110
+ "alias",
111
+ "slug",
112
+ "find_or_create",
113
+ ];
114
+ assertExhaustive(true);
115
+ export const UPDATE_RESOURCE_FIELD_KEYS = [
116
+ "allowed_device_keys_add",
117
+ "allowed_device_keys_remove",
118
+ "consumer_access",
119
+ "expected_consumer_access_version",
120
+ "description",
121
+ "tags",
122
+ "custom_domain",
123
+ "preserve_host",
124
+ "alias",
125
+ ];
126
+ assertExhaustive(true);
127
+ export const CREATE_QURL_FOR_RESOURCE_FIELD_KEYS = [
128
+ "expires_in",
129
+ "one_time_use",
130
+ "max_sessions",
131
+ "session_duration",
132
+ "label",
133
+ "access_policy",
134
+ "target_path",
135
+ ];
136
+ export const SHARE_RESOURCE_OPTION_KEYS = [
137
+ "ttlSeconds",
138
+ ];
139
+ assertExhaustive(true);
140
+ assertExhaustive(true);
141
+ /**
142
+ * Options accepted by the portal-verb `protectUrl` / `createPortal`
143
+ * surface. These are camelCase caller-facing options (the portal verbs are
144
+ * the idiomatic layer); the build helpers map them onto the same wire
145
+ * fields the REST-shaped methods send.
146
+ */
147
+ export const PROTECT_URL_OPTION_KEYS = [
148
+ "name",
149
+ "private",
150
+ "consumerAccess",
151
+ "description",
152
+ "tags",
153
+ "customDomain",
154
+ "alias",
155
+ ];
156
+ assertExhaustive(true);
157
+ export const CREATE_PORTAL_OPTION_KEYS = [
158
+ "name",
159
+ "validFor",
160
+ "label",
161
+ "oneTimeUse",
162
+ "maxSessions",
163
+ "sessionDuration",
164
+ "targetPath",
165
+ ];
166
+ assertExhaustive(true);
167
+ export const UPDATE_RESOURCE_QURL_FIELD_KEYS = [
168
+ "extend_by",
169
+ "expires_at",
170
+ "label",
171
+ "access_policy",
172
+ "max_sessions",
173
+ "session_duration",
174
+ ];
175
+ assertExhaustive(true);
176
+ export const UPDATE_CUSTOMER_FIELD_KEYS = [
177
+ "spending_cap_cents",
178
+ ];
179
+ assertExhaustive(true);
180
+ export const CREATE_BILLING_CHECKOUT_FIELD_KEYS = [
181
+ "plan",
182
+ ];
183
+ assertExhaustive(true);
184
+ export const REGISTER_DOMAIN_FIELD_KEYS = [
185
+ "domain",
186
+ ];
187
+ assertExhaustive(true);
188
+ export const CREATE_WEBHOOK_FIELD_KEYS = [
189
+ "url",
190
+ "events",
191
+ "description",
192
+ ];
193
+ assertExhaustive(true);
194
+ export const UPDATE_WEBHOOK_FIELD_KEYS = [
195
+ "url",
196
+ "events",
197
+ "description",
198
+ "status",
199
+ ];
200
+ assertExhaustive(true);
201
+ export const CREATE_API_KEY_FIELD_KEYS = [
202
+ "kind",
203
+ "name",
204
+ "scopes",
205
+ "target",
206
+ "claims",
207
+ "expires_in",
208
+ ];
209
+ assertExhaustive(true);
210
+ export const UPDATE_API_KEY_FIELD_KEYS = [
211
+ "name",
212
+ "scopes",
213
+ ];
214
+ assertExhaustive(true);
215
+ export const REDEEM_ACCESS_CODE_FIELD_KEYS = [
216
+ "code",
217
+ "honeypot",
218
+ "elapsed_ms",
219
+ ];
220
+ assertExhaustive(true);
221
+ export const CREATE_ACCESS_CODE_FIELD_KEYS = [
222
+ "resource_id",
223
+ "name",
224
+ "max_uses",
225
+ "expires_at",
226
+ ];
227
+ assertExhaustive(true);
228
+ export const CONNECTOR_INSTALLATION_LIST_PARAM_KEYS = [
229
+ "cursor",
230
+ "limit",
231
+ ];
232
+ assertExhaustive(true);
233
+ export const BILLING_INVOICE_LIST_PARAM_KEYS = [
234
+ "limit",
235
+ "cursor",
236
+ ];
237
+ assertExhaustive(true);
238
+ export const DOMAIN_LIST_PARAM_KEYS = [
239
+ "limit",
240
+ "cursor",
241
+ ];
242
+ assertExhaustive(true);
243
+ export const WEBHOOK_LIST_PARAM_KEYS = [
244
+ "limit",
245
+ "cursor",
246
+ ];
247
+ assertExhaustive(true);
248
+ export const WEBHOOK_DELIVERY_LIST_PARAM_KEYS = [
249
+ "limit",
250
+ "cursor",
251
+ ];
252
+ assertExhaustive(true);
253
+ export const API_KEY_LIST_PARAM_KEYS = [
254
+ "limit",
255
+ "cursor",
256
+ "status",
257
+ ];
258
+ assertExhaustive(true);
@@ -0,0 +1,36 @@
1
+ export declare const MAX_RESPONSE_BODY_BYTES: number;
2
+ export declare const MAX_CONFIGURABLE_RESPONSE_BODY_BYTES: number;
3
+ export declare const MAX_ERROR_SNIPPET_BYTES = 512;
4
+ export declare const MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS = 512;
5
+ export declare const MAX_INVALID_FIELD_ENTRIES = 100;
6
+ export declare const MAX_DEBUG_BODY_KEYS = 100;
7
+ export declare const MAX_DIAGNOSTIC_COLLECTION_BYTES: number;
8
+ export declare const RETRY_BASE_DELAY_MS = 500;
9
+ export declare const RETRY_MAX_DELAY_MS = 30000;
10
+ export declare const RETRY_AFTER_HARD_CAP_MS: number;
11
+ export declare const RETRY_AFTER_PARSE_LIMIT_S: number;
12
+ export declare const MAX_IDEMPOTENCY_KEY = 256;
13
+ export declare const MAX_TARGET_URL = 2048;
14
+ export declare const MAX_LABEL = 500;
15
+ export declare const MAX_BATCH_ITEMS = 100;
16
+ export declare const MAX_DESCRIPTION = 500;
17
+ /** Code points, matching qurl-service ValidatePreviewName. */
18
+ export declare const MAX_RESOURCE_NAME = 100;
19
+ export declare const MAX_CUSTOM_DOMAIN = 253;
20
+ export declare const MAX_MAX_SESSIONS = 1000;
21
+ export declare const MAX_API_KEY_NAME = 100;
22
+ export declare const MAX_ENROLLMENT_TOKEN_TTL_SECONDS = 86400n;
23
+ export declare const MAX_ALIAS = 64;
24
+ export declare const MAX_SLUG = 64;
25
+ export declare const MAX_TARGET_PATH = 2048;
26
+ export declare const MAX_TAGS = 10;
27
+ export declare const MAX_TAG_LENGTH = 50;
28
+ export declare const MAX_AUTO_PAGINATION_PAGES = 10000;
29
+ export declare const MAX_DELEGATED_MINT_CAPABILITY = 8192;
30
+ export declare const MAX_DELEGATED_ETAG = 96;
31
+ export declare const MAX_DELEGATED_BATCH_ITEMS = 100;
32
+ export declare const MAX_PATH_ID_DECODE_PASSES = 3;
33
+ export declare const MAX_PATH_ID_LENGTH = 4096;
34
+ export declare const MAX_ALLOWED_DEVICE_KEYS = 256;
35
+ export declare const MAX_LIST_LIMIT = 100;
36
+ export declare const MAX_CONSUMER_CONTRACT_ID_BYTES = 128;
@@ -0,0 +1,58 @@
1
+ // Joint Go/TypeScript policy: docs/api-response-limits.md.
2
+ // Keep the 1 MiB default and bounded opt-in ceiling aligned across both SDKs.
3
+ // Decoded streaming bytes remain authoritative when length hints are unusable.
4
+ export const MAX_RESPONSE_BODY_BYTES = 1 << 20;
5
+ export const MAX_CONFIGURABLE_RESPONSE_BODY_BYTES = 16 << 20;
6
+ export const MAX_ERROR_SNIPPET_BYTES = 512;
7
+ // UTF-8 uses at least one byte per UTF-16 code unit. Reading more source units
8
+ // cannot add a byte that fits in a 512-byte retained diagnostic.
9
+ export const MAX_ERROR_SNIPPET_SOURCE_CODE_UNITS = MAX_ERROR_SNIPPET_BYTES;
10
+ export const MAX_INVALID_FIELD_ENTRIES = 100;
11
+ export const MAX_DEBUG_BODY_KEYS = 100;
12
+ // Bound each retained diagnostic collection as well as each string. Without
13
+ // this aggregate cap, 100 valid 512-byte keys and values could turn a small
14
+ // server error into a large caller-visible object. This is a diagnostic budget,
15
+ // not part of the API wire contract.
16
+ export const MAX_DIAGNOSTIC_COLLECTION_BYTES = 8 << 10;
17
+ export const RETRY_BASE_DELAY_MS = 500;
18
+ // Bounds local exponential backoff (NOT server-asserted Retry-After —
19
+ // see `RETRY_AFTER_HARD_CAP_MS` for that).
20
+ export const RETRY_MAX_DELAY_MS = 30_000;
21
+ // Hard cap on server-asserted Retry-After. Guards against `Retry-After`
22
+ // values >2^31-1 ms overflowing Node's setTimeout (silently truncated to
23
+ // 1ms — turns a "wait a month" directive into a hot retry loop).
24
+ export const RETRY_AFTER_HARD_CAP_MS = 60 * 60 * 1000;
25
+ export const RETRY_AFTER_PARSE_LIMIT_S = RETRY_AFTER_HARD_CAP_MS / 1000;
26
+ export const MAX_IDEMPOTENCY_KEY = 256;
27
+ // ---- Spec-derived validation helpers ------------------------------------
28
+ // These mirror constraints documented on each request schema in
29
+ // `openapi.yaml` so obvious mistakes fail fast instead of round-tripping
30
+ // to the API and coming back as a generic 400.
31
+ // CI checks every MAX_* constant against contract/validation-bounds.json; the
32
+ // weekly published-spec check detects upstream changes. Local safety budgets
33
+ // are classified separately and must not be raised to bypass a service cap.
34
+ export const MAX_TARGET_URL = 2048;
35
+ export const MAX_LABEL = 500;
36
+ export const MAX_BATCH_ITEMS = 100;
37
+ export const MAX_DESCRIPTION = 500;
38
+ /** Code points, matching qurl-service ValidatePreviewName. */
39
+ export const MAX_RESOURCE_NAME = 100;
40
+ export const MAX_CUSTOM_DOMAIN = 253;
41
+ export const MAX_MAX_SESSIONS = 1000;
42
+ export const MAX_API_KEY_NAME = 100;
43
+ export const MAX_ENROLLMENT_TOKEN_TTL_SECONDS = 86400n;
44
+ export const MAX_ALIAS = 64;
45
+ export const MAX_SLUG = 64;
46
+ export const MAX_TARGET_PATH = 2048;
47
+ export const MAX_TAGS = 10;
48
+ export const MAX_TAG_LENGTH = 50;
49
+ export const MAX_AUTO_PAGINATION_PAGES = 10_000;
50
+ export const MAX_DELEGATED_MINT_CAPABILITY = 8192;
51
+ export const MAX_DELEGATED_ETAG = 96;
52
+ export const MAX_DELEGATED_BATCH_ITEMS = 100;
53
+ export const MAX_PATH_ID_DECODE_PASSES = 3;
54
+ export const MAX_PATH_ID_LENGTH = 4096;
55
+ export const MAX_ALLOWED_DEVICE_KEYS = 256;
56
+ export const MAX_LIST_LIMIT = 100;
57
+ // Local UTF-8 budget; OpenAPI maxLength counts characters, not bytes.
58
+ export const MAX_CONSUMER_CONTRACT_ID_BYTES = 128;
@@ -0,0 +1,12 @@
1
+ import type { ApiKeyRequestScope } from "./types.js";
2
+ /** Narrow a response scope before reusing it in a create/update request. */
3
+ export declare function isApiKeyRequestScope(scope: string): scope is ApiKeyRequestScope;
4
+ export declare function validateWebhookWriteFields(input: Record<string, unknown>, method: string, requiredFields?: {
5
+ url?: boolean;
6
+ events?: boolean;
7
+ }): void;
8
+ export declare function validateApiKeyWriteFields(input: Record<string, unknown>, method: string, requiredFields?: {
9
+ name?: boolean;
10
+ scopes?: boolean;
11
+ }): void;
12
+ export declare function validateEnrollmentTokenFields(input: Record<string, unknown>, method: string): void;