@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,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConnectorResource = exports.CONNECTOR_SLUG_PATTERN = void 0;
4
+ exports.requireConnectorSlug = requireConnectorSlug;
5
+ exports.classifyConnectorMutationFailure = classifyConnectorMutationFailure;
6
+ exports.requireConnectorSubtleCrypto = requireConnectorSubtleCrypto;
7
+ exports.verifyResourceIdentity = verifyResourceIdentity;
8
+ exports.parseConnectorResource = parseConnectorResource;
9
+ const response_metadata_js_1 = require("./response-metadata.js");
10
+ const errors_js_1 = require("./errors.js");
11
+ const crid_js_1 = require("./crid.js");
12
+ const client_errors_js_1 = require("./client-errors.js");
13
+ const client_validation_js_1 = require("./client-validation.js");
14
+ // Keep these identity contracts aligned with qurl-go and the public API.
15
+ exports.CONNECTOR_SLUG_PATTERN = /^[a-z][a-z0-9-]{1,62}[a-z0-9]$/;
16
+ // Current service schemas intentionally share this grammar, but keep the
17
+ // mutable alias contract separate from the immutable lookup identity.
18
+ const CONNECTOR_ALIAS_PATTERN = /^[a-z][a-z0-9-]{1,62}[a-z0-9]$/;
19
+ const CONNECTOR_RESOURCE_ID_PATTERN = /^[A-Za-z0-9_-]{122}$/;
20
+ const CONNECTOR_RESOURCE_ID_DER_PREFIX = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE";
21
+ const CONNECTOR_ROUTING_ID_PATTERN = /^c-[a-z2-7]{51}[aq]$/;
22
+ function requireConnectorSlug(slug, method) {
23
+ if (typeof slug !== "string" || !exports.CONNECTOR_SLUG_PATTERN.test(slug)) {
24
+ throw (0, client_errors_js_1.clientValidationError)(`${method}: slug must be 3-64 lowercase alphanumeric or hyphen characters, start with a letter, and end alphanumeric`);
25
+ }
26
+ }
27
+ function classifyConnectorMutationFailure(operation, error) {
28
+ if (!(error instanceof errors_js_1.QURLError)) {
29
+ throw new errors_js_1.ConnectorResourceOutcomeUnknownError(new errors_js_1.RuntimeError("Unexpected failure after Connector mutation dispatch", { cause: error }));
30
+ }
31
+ // Most authoritative 4xx responses prove that the mutation was rejected.
32
+ // bootstrap_key_consumed is the exception: the service returns it from the
33
+ // consume phase after the resource may have been bound or found.
34
+ // Callers perform argument/runtime preflight outside their mutation try
35
+ // blocks; every other surfaced failure therefore follows dispatch or a
36
+ // nominal success whose resource contract could not be consumed, so callers
37
+ // must reconcile before retrying. Ensure's exact 201 + valid resource but
38
+ // missing found_existing is handled after this classifier: the row proves
39
+ // the selected resource, while only its required metadata is unavailable.
40
+ if (operation === "ensure" && error.status === 409 && error.code === "bootstrap_key_consumed") {
41
+ throw new errors_js_1.ConnectorResourceOutcomeUnknownError(error);
42
+ }
43
+ if (error.status >= 400 && error.status < 500) {
44
+ throw error;
45
+ }
46
+ throw new errors_js_1.ConnectorResourceOutcomeUnknownError(error);
47
+ }
48
+ function decodeCanonicalBase64Url(value) {
49
+ if (!CONNECTOR_RESOURCE_ID_PATTERN.test(value) ||
50
+ !value.startsWith(CONNECTOR_RESOURCE_ID_DER_PREFIX)) {
51
+ return undefined;
52
+ }
53
+ try {
54
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "==";
55
+ const binary = atob(padded);
56
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
57
+ const canonical = btoa(String.fromCharCode(...bytes))
58
+ .replace(/\+/g, "-")
59
+ .replace(/\//g, "_")
60
+ .replace(/=+$/, "");
61
+ return canonical === value ? bytes : undefined;
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ }
67
+ function requireConnectorSubtleCrypto(method) {
68
+ if (!globalThis.crypto?.subtle) {
69
+ throw new errors_js_1.RuntimeError(`${method}: requires the Web Crypto SubtleCrypto API`);
70
+ }
71
+ }
72
+ async function importsAsConnectorPublicKey(der) {
73
+ try {
74
+ const keyData = new ArrayBuffer(der.byteLength);
75
+ new Uint8Array(keyData).set(der);
76
+ await globalThis.crypto.subtle.importKey("spki", keyData, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ async function verifyResourceIdentity(resource, method, expectedCrid) {
84
+ const errorOptions = { requestId: (0, response_metadata_js_1.getResponseMetadata)(resource)?.requestId };
85
+ if (!resource || typeof resource.resource_id !== "string") {
86
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has missing or invalid resource_id`, errorOptions);
87
+ }
88
+ const resourceKey = decodeCanonicalBase64Url(resource.resource_id);
89
+ requireConnectorSubtleCrypto(method);
90
+ if (!resourceKey || !(await importsAsConnectorPublicKey(resourceKey))) {
91
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has missing or invalid resource_id`, errorOptions);
92
+ }
93
+ if (expectedCrid !== undefined && resource.crid !== expectedCrid) {
94
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response crid does not match the request`, errorOptions);
95
+ }
96
+ if (!(await (0, crid_js_1.cridKeyMatches)(resource.crid, resourceKey))) {
97
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has missing, invalid, or public-key-mismatched crid`, errorOptions);
98
+ }
99
+ }
100
+ async function parseConnectorResource(client, value, method, expectation = {}) {
101
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
102
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response is missing connector resource data`);
103
+ }
104
+ const resource = value;
105
+ await verifyResourceIdentity(resource, method, expectation.crid);
106
+ if (typeof resource.connector_routing_id !== "string" ||
107
+ !CONNECTOR_ROUTING_ID_PATTERN.test(resource.connector_routing_id)) {
108
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has missing or invalid connector_routing_id`);
109
+ }
110
+ if (typeof resource.knock_resource_id !== "string" ||
111
+ resource.knock_resource_id.trim() === "" ||
112
+ resource.knock_resource_id.trim() !== resource.knock_resource_id ||
113
+ (0, client_validation_js_1.containsControlCharacter)(resource.knock_resource_id)) {
114
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has missing or invalid knock_resource_id`);
115
+ }
116
+ if (resource.resource_id === resource.knock_resource_id ||
117
+ resource.connector_routing_id === resource.knock_resource_id) {
118
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response cross-wires resource identity, routing, and admission values`);
119
+ }
120
+ if (resource.type !== "tunnel") {
121
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response resource type is not tunnel`);
122
+ }
123
+ if (typeof resource.slug !== "string" || !exports.CONNECTOR_SLUG_PATTERN.test(resource.slug)) {
124
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has missing or invalid slug`);
125
+ }
126
+ if (expectation.slug !== undefined && resource.slug !== expectation.slug) {
127
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response slug does not match the request`);
128
+ }
129
+ if (resource.alias !== undefined &&
130
+ resource.alias !== null &&
131
+ (typeof resource.alias !== "string" || !CONNECTOR_ALIAS_PATTERN.test(resource.alias))) {
132
+ // The public API intentionally gives mutable aliases and immutable slugs the
133
+ // same canonical wire grammar (domain aliasPattern/slugPattern). Keeping
134
+ // response validation aligned avoids accepting a row the service itself
135
+ // could not create or update.
136
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has invalid alias`);
137
+ }
138
+ if (resource.status === "revoked") {
139
+ if (expectation.revokedIsLifecycleError !== true) {
140
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: active-only operation returned a revoked connector resource`);
141
+ }
142
+ throw new errors_js_1.QURLError({
143
+ status: 0,
144
+ code: errors_js_1.ERROR_CODE_CONNECTOR_RESOURCE_REVOKED,
145
+ title: "Connector Resource Revoked",
146
+ detail: `${method}: qURL Connector resource is revoked`,
147
+ });
148
+ }
149
+ if (resource.status !== "active") {
150
+ throw (0, client_errors_js_1.unexpectedResponseError)(`${method}: response has invalid resource status`);
151
+ }
152
+ return new ConnectorResource(client, resource, CONNECTOR_RESOURCE_CONSTRUCTOR_TOKEN);
153
+ }
154
+ const CONNECTOR_RESOURCE_CONSTRUCTOR_TOKEN = Symbol("validated ConnectorResource");
155
+ /**
156
+ * A validated qURL Connector resource bound to the client that loaded it.
157
+ *
158
+ * Resource identity, reverse-routing identity, and NHP admission identity are
159
+ * distinct server-issued values. Callers must consume each field verbatim and
160
+ * must never derive one from another.
161
+ */
162
+ class ConnectorResource {
163
+ #client;
164
+ resourcePublicKey;
165
+ /** Required public identifier, verified against the returned resource key. */
166
+ crid;
167
+ connectorRoutingId;
168
+ knockResourceId;
169
+ slug;
170
+ alias;
171
+ /** @internal Instances are returned by QURLClient after wire validation. */
172
+ constructor(client, details, validationToken) {
173
+ if (validationToken !== CONNECTOR_RESOURCE_CONSTRUCTOR_TOKEN) {
174
+ throw (0, client_errors_js_1.clientValidationError)("ConnectorResource cannot be constructed directly; load it through QURLClient");
175
+ }
176
+ this.#client = client;
177
+ this.resourcePublicKey = details.resource_id;
178
+ this.crid = details.crid;
179
+ this.connectorRoutingId = details.connector_routing_id;
180
+ this.knockResourceId = details.knock_resource_id;
181
+ this.slug = details.slug;
182
+ this.alias = details.alias ?? undefined;
183
+ }
184
+ /** Mint a short-lived portal for this Connector resource. */
185
+ async createPortal(opts = {}, requestOptions) {
186
+ return this.#client.createPortal(this.crid, opts, requestOptions);
187
+ }
188
+ }
189
+ exports.ConnectorResource = ConnectorResource;
@@ -0,0 +1,30 @@
1
+ import type { CreateDelegatedQurlBatchInput, CreateDelegatedQurlBatchOptions, DelegatedQurl, DelegatedQurlBatchItemResult, GetDelegatedQurlBatchOptions } from "./types.js";
2
+ import { ValidationError } from "./errors.js";
3
+ import type { HttpResponse } from "./client-http-types.js";
4
+ export declare const DELEGATED_BATCH_ID_PATTERN: RegExp;
5
+ export declare const DELEGATED_QURL_ID_PATTERN: RegExp;
6
+ export declare const STRONG_ETAG_PATTERN: RegExp;
7
+ export declare function validateCreateDelegatedQurlBatchInput(input: unknown): asserts input is CreateDelegatedQurlBatchInput;
8
+ export declare function validateDelegatedBatchCreateOptions(options: unknown): asserts options is CreateDelegatedQurlBatchOptions;
9
+ export declare function validateGetDelegatedQurlBatchOptions(options: unknown): asserts options is GetDelegatedQurlBatchOptions | undefined;
10
+ export declare function delegatedResponseError(status: number, detail: string, requestId?: string): ValidationError;
11
+ export declare function delegatedEnvelope(response: HttpResponse<unknown>, status: number): {
12
+ data: Record<string, unknown>;
13
+ requestId: string;
14
+ };
15
+ export declare function requiredDelegatedHeader(response: HttpResponse<unknown>, name: string, status: number, requestId?: string): string;
16
+ export declare function hasDelegatedNoStore(value: string | null): boolean;
17
+ export declare function delegatedResponseHeaders(response: HttpResponse<unknown>, status: number, requestId?: string): {
18
+ etag?: string;
19
+ retryAfter?: number;
20
+ };
21
+ export declare function isUtcTimestamp(value: unknown): value is string;
22
+ export declare function delegatedBatchBase(data: Record<string, unknown>, status: number, requestId: string, expectedBatchId?: string): {
23
+ batchId: string;
24
+ itemCount: number;
25
+ submittedAt: string;
26
+ };
27
+ export declare function validateDelegatedLocation(value: string, baseUrl: string, batchId: string, status: number, requestId: string): string;
28
+ export declare function validateDelegatedQurl(value: unknown, expectedQurlId: string, status: number, requestId: string): DelegatedQurl;
29
+ export declare function validateDelegatedBatchResults(value: unknown, itemCount: number, batchStatus: "succeeded" | "partially_failed" | "failed", status: number, requestId: string): DelegatedQurlBatchItemResult[];
30
+ export declare function classifyDelegatedBatchCreateFailure(error: unknown, batchId?: string): never;
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STRONG_ETAG_PATTERN = exports.DELEGATED_QURL_ID_PATTERN = exports.DELEGATED_BATCH_ID_PATTERN = void 0;
4
+ exports.validateCreateDelegatedQurlBatchInput = validateCreateDelegatedQurlBatchInput;
5
+ exports.validateDelegatedBatchCreateOptions = validateDelegatedBatchCreateOptions;
6
+ exports.validateGetDelegatedQurlBatchOptions = validateGetDelegatedQurlBatchOptions;
7
+ exports.delegatedResponseError = delegatedResponseError;
8
+ exports.delegatedEnvelope = delegatedEnvelope;
9
+ exports.requiredDelegatedHeader = requiredDelegatedHeader;
10
+ exports.hasDelegatedNoStore = hasDelegatedNoStore;
11
+ exports.delegatedResponseHeaders = delegatedResponseHeaders;
12
+ exports.isUtcTimestamp = isUtcTimestamp;
13
+ exports.delegatedBatchBase = delegatedBatchBase;
14
+ exports.validateDelegatedLocation = validateDelegatedLocation;
15
+ exports.validateDelegatedQurl = validateDelegatedQurl;
16
+ exports.validateDelegatedBatchResults = validateDelegatedBatchResults;
17
+ exports.classifyDelegatedBatchCreateFailure = classifyDelegatedBatchCreateFailure;
18
+ const errors_js_1 = require("./errors.js");
19
+ const client_errors_js_1 = require("./client-errors.js");
20
+ const client_validation_js_1 = require("./client-validation.js");
21
+ const client_input_fields_js_1 = require("./client-input-fields.js");
22
+ const client_limits_js_1 = require("./client-limits.js");
23
+ const MIN_DELEGATED_BATCH_IDEMPOTENCY_KEY = 32;
24
+ exports.DELEGATED_BATCH_ID_PATTERN = /^dqb_[A-Za-z0-9_-]{22}$/;
25
+ exports.DELEGATED_QURL_ID_PATTERN = /^q_[0-9a-f]{11}$/;
26
+ exports.STRONG_ETAG_PATTERN = /^"[\x21\x23-\x7e]+"$/;
27
+ const UTC_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
28
+ function validateCreateDelegatedQurlBatchInput(input) {
29
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
30
+ throw (0, client_errors_js_1.clientValidationError)(`createDelegatedQurlBatch: input must be an object (got ${(0, client_validation_js_1.describeShape)(input)})`);
31
+ }
32
+ const typed = input;
33
+ (0, client_validation_js_1.requireNoUnknownFields)(typed, client_input_fields_js_1.DELEGATED_BATCH_INPUT_KEYS, "createDelegatedQurlBatch");
34
+ if (typeof typed.mint_capability !== "string" ||
35
+ typed.mint_capability.length < 1 ||
36
+ typed.mint_capability.length > client_limits_js_1.MAX_DELEGATED_MINT_CAPABILITY) {
37
+ throw (0, client_errors_js_1.clientValidationError)(`createDelegatedQurlBatch: mint_capability must be a string of 1-${client_limits_js_1.MAX_DELEGATED_MINT_CAPABILITY} characters`);
38
+ }
39
+ if (!Array.isArray(typed.grants) ||
40
+ typed.grants.length < 1 ||
41
+ typed.grants.length > client_limits_js_1.MAX_DELEGATED_BATCH_ITEMS) {
42
+ throw (0, client_errors_js_1.clientValidationError)(`createDelegatedQurlBatch: grants must contain 1-${client_limits_js_1.MAX_DELEGATED_BATCH_ITEMS} items`);
43
+ }
44
+ for (const [index, grant] of typed.grants.entries()) {
45
+ const field = `createDelegatedQurlBatch: grants[${index}]`;
46
+ if (typeof grant !== "object" || grant === null || Array.isArray(grant)) {
47
+ throw (0, client_errors_js_1.clientValidationError)(`${field}: must be an object (got ${(0, client_validation_js_1.describeShape)(grant)})`);
48
+ }
49
+ (0, client_validation_js_1.requireNoUnknownFields)(grant, client_input_fields_js_1.DELEGATED_BATCH_GRANT_KEYS, field);
50
+ (0, client_validation_js_1.requireMaxUtf8Bytes)(grant.label, `${field}.label`, client_limits_js_1.MAX_LABEL);
51
+ (0, client_validation_js_1.requireBooleanIfPresent)(grant.one_time_use, `${field}.one_time_use`);
52
+ (0, client_validation_js_1.requireMaxSessionsInRange)(grant.max_sessions, `${field}.max_sessions`);
53
+ for (const durationField of ["expires_in", "session_duration"]) {
54
+ const value = grant[durationField];
55
+ if (value !== undefined && (typeof value !== "string" || value.length === 0)) {
56
+ throw (0, client_errors_js_1.clientValidationError)(`${field}.${durationField}: must be a non-empty string`);
57
+ }
58
+ }
59
+ if (grant.access_policy !== undefined) {
60
+ (0, client_validation_js_1.requireValidAccessPolicy)(grant.access_policy);
61
+ }
62
+ }
63
+ }
64
+ function validateDelegatedBatchCreateOptions(options) {
65
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
66
+ throw (0, client_errors_js_1.clientValidationError)("createDelegatedQurlBatch: options are required");
67
+ }
68
+ (0, client_validation_js_1.requireNoUnknownFields)(options, client_input_fields_js_1.DELEGATED_BATCH_CREATE_OPTION_KEYS, "createDelegatedQurlBatch options");
69
+ const key = options.idempotencyKey;
70
+ if (typeof key !== "string" ||
71
+ key.length < MIN_DELEGATED_BATCH_IDEMPOTENCY_KEY ||
72
+ key.length > client_limits_js_1.MAX_IDEMPOTENCY_KEY ||
73
+ !/^[!-~]+$/.test(key)) {
74
+ throw (0, client_errors_js_1.clientValidationError)(`createDelegatedQurlBatch: idempotencyKey must contain ${MIN_DELEGATED_BATCH_IDEMPOTENCY_KEY}-${client_limits_js_1.MAX_IDEMPOTENCY_KEY} visible ASCII characters`);
75
+ }
76
+ }
77
+ function validateGetDelegatedQurlBatchOptions(options) {
78
+ if (options === undefined)
79
+ return;
80
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
81
+ throw (0, client_errors_js_1.clientValidationError)(`getDelegatedQurlBatch: options must be an object (got ${(0, client_validation_js_1.describeShape)(options)})`);
82
+ }
83
+ (0, client_validation_js_1.requireNoUnknownFields)(options, client_input_fields_js_1.DELEGATED_BATCH_GET_OPTION_KEYS, "getDelegatedQurlBatch options");
84
+ const etag = options.etag;
85
+ if (etag !== undefined &&
86
+ (typeof etag !== "string" ||
87
+ etag.length > client_limits_js_1.MAX_DELEGATED_ETAG ||
88
+ !exports.STRONG_ETAG_PATTERN.test(etag))) {
89
+ throw (0, client_errors_js_1.clientValidationError)("getDelegatedQurlBatch: etag must be a valid strong ETag");
90
+ }
91
+ }
92
+ function delegatedResponseError(status, detail, requestId) {
93
+ return (0, client_errors_js_1.unexpectedResponseError)(`Delegated qURL batch ${detail}`, { status, requestId });
94
+ }
95
+ function delegatedResponseObject(value, status, label, requestId) {
96
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
97
+ throw delegatedResponseError(status, `${label} is not an object`, requestId);
98
+ }
99
+ return value;
100
+ }
101
+ function delegatedEnvelope(response, status) {
102
+ if (typeof response.envelope.meta !== "object" ||
103
+ response.envelope.meta === null ||
104
+ Array.isArray(response.envelope.meta)) {
105
+ throw delegatedResponseError(status, "response meta is not an object");
106
+ }
107
+ const meta = response.envelope.meta;
108
+ if (typeof meta.request_id !== "string") {
109
+ throw delegatedResponseError(status, "response meta has invalid request_id");
110
+ }
111
+ const data = delegatedResponseObject(response.envelope.data, status, "response data", meta.request_id);
112
+ return { data, requestId: meta.request_id };
113
+ }
114
+ function requiredDelegatedHeader(response, name, status, requestId) {
115
+ const headers = response.headers;
116
+ if (!headers || typeof headers.get !== "function") {
117
+ throw delegatedResponseError(status, "response headers are unavailable", requestId);
118
+ }
119
+ const value = headers.get(name);
120
+ if (value === null) {
121
+ throw delegatedResponseError(status, `response is missing ${name}`, requestId);
122
+ }
123
+ return value;
124
+ }
125
+ function hasDelegatedNoStore(value) {
126
+ if (value === null)
127
+ return false;
128
+ const directives = new Set(value.split(",").map((directive) => directive.trim().split("=", 1)[0].toLowerCase()));
129
+ return directives.has("private") && directives.has("no-store");
130
+ }
131
+ function delegatedResponseHeaders(response, status, requestId) {
132
+ const cacheControl = response.headers?.get("Cache-Control") ?? null;
133
+ if (cacheControl !== null) {
134
+ if (!hasDelegatedNoStore(cacheControl)) {
135
+ throw delegatedResponseError(status, "response has invalid Cache-Control", requestId);
136
+ }
137
+ }
138
+ else if (status !== 304) {
139
+ requiredDelegatedHeader(response, "Cache-Control", status, requestId);
140
+ }
141
+ const etag = response.headers?.get("ETag") ?? undefined;
142
+ if (etag !== undefined && (etag.length > client_limits_js_1.MAX_DELEGATED_ETAG || !exports.STRONG_ETAG_PATTERN.test(etag))) {
143
+ throw delegatedResponseError(status, "response has invalid strong ETag", requestId);
144
+ }
145
+ const rawRetryAfter = response.headers?.get("Retry-After") ?? "";
146
+ const trimmed = rawRetryAfter.trim();
147
+ const seconds = /^\d+$/.test(trimmed)
148
+ ? Number(trimmed)
149
+ : /^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(trimmed)
150
+ ? Math.ceil((Date.parse(trimmed) - Date.now()) / 1000)
151
+ : NaN;
152
+ const retryAfter = Number.isSafeInteger(seconds) && seconds > 0 && seconds <= client_limits_js_1.RETRY_AFTER_PARSE_LIMIT_S
153
+ ? seconds
154
+ : undefined;
155
+ return { etag, retryAfter };
156
+ }
157
+ function isUtcTimestamp(value) {
158
+ return (typeof value === "string" &&
159
+ UTC_TIMESTAMP_PATTERN.test(value) &&
160
+ Number.isFinite(Date.parse(value)));
161
+ }
162
+ function delegatedBatchBase(data, status, requestId, expectedBatchId) {
163
+ if (typeof data.batch_id !== "string" ||
164
+ !exports.DELEGATED_BATCH_ID_PATTERN.test(data.batch_id) ||
165
+ (expectedBatchId !== undefined && data.batch_id !== expectedBatchId)) {
166
+ throw delegatedResponseError(status, "response has invalid batch_id", requestId);
167
+ }
168
+ if (!Number.isInteger(data.item_count) ||
169
+ Number(data.item_count) < 1 ||
170
+ Number(data.item_count) > client_limits_js_1.MAX_DELEGATED_BATCH_ITEMS) {
171
+ throw delegatedResponseError(status, "response has invalid item_count", requestId);
172
+ }
173
+ if (!isUtcTimestamp(data.submitted_at)) {
174
+ throw delegatedResponseError(status, "response has invalid submitted_at", requestId);
175
+ }
176
+ return {
177
+ batchId: data.batch_id,
178
+ itemCount: Number(data.item_count),
179
+ submittedAt: data.submitted_at,
180
+ };
181
+ }
182
+ function validateDelegatedLocation(value, baseUrl, batchId, status, requestId) {
183
+ let location;
184
+ try {
185
+ const base = new URL(`${baseUrl}/`);
186
+ const relative = value.startsWith("/v1/delegated-qurl-batches/")
187
+ ? `${base.pathname.replace(/\/$/, "")}${value}`
188
+ : value;
189
+ location = new URL(relative, base);
190
+ }
191
+ catch {
192
+ throw delegatedResponseError(status, "response has invalid Location", requestId);
193
+ }
194
+ const expected = new URL(`${baseUrl}/v1/delegated-qurl-batches/${batchId}`);
195
+ if (location.username !== "" ||
196
+ location.password !== "" ||
197
+ location.search !== "" ||
198
+ location.hash !== "" ||
199
+ location.href !== expected.href) {
200
+ throw delegatedResponseError(status, "response has invalid Location", requestId);
201
+ }
202
+ return location.href;
203
+ }
204
+ function validateDelegatedQurlLink(value) {
205
+ if (typeof value !== "string")
206
+ return false;
207
+ try {
208
+ const link = new URL(value);
209
+ return (link.protocol === "https:" &&
210
+ link.username === "" &&
211
+ link.password === "" &&
212
+ link.hash.length > 1);
213
+ }
214
+ catch {
215
+ return false;
216
+ }
217
+ }
218
+ function validateDelegatedQurl(value, expectedQurlId, status, requestId) {
219
+ const data = delegatedResponseObject(value, status, "response data", requestId);
220
+ if (data.qurl_id !== expectedQurlId) {
221
+ throw delegatedResponseError(status, "response has invalid qurl_id", requestId);
222
+ }
223
+ if (data.status !== "active" &&
224
+ data.status !== "consumed" &&
225
+ data.status !== "expired" &&
226
+ data.status !== "revoked") {
227
+ throw delegatedResponseError(status, "response has invalid status", requestId);
228
+ }
229
+ if (!isUtcTimestamp(data.expires_at) ||
230
+ typeof data.one_time_use !== "boolean" ||
231
+ !Number.isInteger(data.max_sessions) ||
232
+ Number(data.max_sessions) < 0 ||
233
+ !Number.isInteger(data.session_duration) ||
234
+ Number(data.session_duration) <= 0 ||
235
+ (data.label !== undefined && typeof data.label !== "string") ||
236
+ (data.access_policy !== undefined &&
237
+ (typeof data.access_policy !== "object" ||
238
+ data.access_policy === null ||
239
+ Array.isArray(data.access_policy)))) {
240
+ throw delegatedResponseError(status, "response has invalid qURL metadata", requestId);
241
+ }
242
+ return { ...data, request_id: requestId };
243
+ }
244
+ function validateDelegatedBatchResults(value, itemCount, batchStatus, status, requestId) {
245
+ if (!Array.isArray(value) || value.length !== itemCount) {
246
+ throw delegatedResponseError(status, "terminal response has invalid results", requestId);
247
+ }
248
+ let succeeded = 0;
249
+ const qurlIds = new Set();
250
+ const links = new Set();
251
+ const results = value.map((raw, index) => {
252
+ const item = delegatedResponseObject(raw, status, `result ${index}`, requestId);
253
+ if (item.index !== index) {
254
+ throw delegatedResponseError(status, `result ${index} has invalid index`, requestId);
255
+ }
256
+ if (item.status === "succeeded") {
257
+ if (item.error !== undefined && item.error !== null) {
258
+ throw delegatedResponseError(status, `result ${index} has conflicting fields`, requestId);
259
+ }
260
+ const qurl = delegatedResponseObject(item.qurl, status, `result ${index} qurl`, requestId);
261
+ if (typeof qurl.qurl_id !== "string" ||
262
+ !exports.DELEGATED_QURL_ID_PATTERN.test(qurl.qurl_id) ||
263
+ !validateDelegatedQurlLink(qurl.qurl_link) ||
264
+ !isUtcTimestamp(qurl.expires_at) ||
265
+ qurlIds.has(qurl.qurl_id) ||
266
+ links.has(qurl.qurl_link)) {
267
+ throw delegatedResponseError(status, `result ${index} has invalid qurl`, requestId);
268
+ }
269
+ qurlIds.add(qurl.qurl_id);
270
+ links.add(qurl.qurl_link);
271
+ succeeded++;
272
+ return {
273
+ index,
274
+ status: "succeeded",
275
+ qurl: {
276
+ qurl_id: qurl.qurl_id,
277
+ qurl_link: qurl.qurl_link,
278
+ expires_at: qurl.expires_at,
279
+ },
280
+ };
281
+ }
282
+ if (item.status !== "failed" || (item.qurl !== undefined && item.qurl !== null)) {
283
+ throw delegatedResponseError(status, `result ${index} has invalid status`, requestId);
284
+ }
285
+ const error = delegatedResponseObject(item.error, status, `result ${index} error`, requestId);
286
+ if (typeof error.code !== "string" ||
287
+ error.code.length === 0 ||
288
+ typeof error.message !== "string") {
289
+ throw delegatedResponseError(status, `result ${index} has invalid error`, requestId);
290
+ }
291
+ return {
292
+ index,
293
+ status: "failed",
294
+ error: { code: error.code, message: error.message },
295
+ };
296
+ });
297
+ if ((batchStatus === "succeeded" && succeeded !== itemCount) ||
298
+ (batchStatus === "failed" && succeeded !== 0) ||
299
+ (batchStatus === "partially_failed" && (succeeded === 0 || succeeded === itemCount))) {
300
+ throw delegatedResponseError(status, "terminal status does not match results", requestId);
301
+ }
302
+ return results;
303
+ }
304
+ function classifyDelegatedBatchCreateFailure(error, batchId) {
305
+ if (!(error instanceof errors_js_1.QURLError)) {
306
+ throw new errors_js_1.DelegatedBatchOutcomeUnknownError(new errors_js_1.RuntimeError("Unexpected failure after delegated batch dispatch", { cause: error }), batchId);
307
+ }
308
+ if ((error.status >= 400 && error.status < 500 && error.status !== 408) || error.status === 501) {
309
+ throw error;
310
+ }
311
+ throw new errors_js_1.DelegatedBatchOutcomeUnknownError(error, batchId);
312
+ }
@@ -0,0 +1,44 @@
1
+ import { ValidationError } from "./errors.js";
2
+ import type { TombstoneInfo } from "./types.js";
3
+ export declare const TEXT_ENCODER: import("node:util").TextEncoder;
4
+ export declare const TEXT_DECODER: import("node:util").TextDecoder;
5
+ /**
6
+ * Construct a {@link ValidationError} for a client-side pre-flight check.
7
+ * Uses `status: 0` (matching {@link NetworkError}/{@link TimeoutError}) and
8
+ * `code: "client_validation"` so catch-by-class still works and callers can
9
+ * tell the error originated inside the SDK rather than from the API.
10
+ */
11
+ export declare function clientValidationError(detail: string): ValidationError;
12
+ /**
13
+ * Construct a {@link ValidationError} for the case where the API returned
14
+ * a response that parsed as JSON but didn't match the expected schema
15
+ * shape (e.g. 400 with a body that isn't a {@link BatchCreateOutput}).
16
+ *
17
+ * Distinct from {@link clientValidationError} so callers can `.code`-branch
18
+ * between "I passed bad input locally" (`"client_validation"`) and "the
19
+ * server returned a body I can't interpret" (`"unexpected_response"`).
20
+ * Uses `status: 0` by default because a logical shape mismatch is not tied to
21
+ * the HTTP classification. Callers that enforce an observed HTTP contract
22
+ * pass that status explicitly.
23
+ *
24
+ * Threads `request_id` through to {@link QURLError.requestId} when the
25
+ * server-side correlation ID is available — operators debugging
26
+ * "unexpected response" tickets need it on the *error* path too, not
27
+ * just on success/passthrough returns.
28
+ */
29
+ export declare function unexpectedResponseError(detail: string, options?: {
30
+ status?: number;
31
+ requestId?: string;
32
+ }): ValidationError;
33
+ export declare function withErrorCause<T extends Error>(error: T, cause: unknown): T;
34
+ export declare function isIndependentAbort(error: unknown, requestSignal: AbortSignal): boolean;
35
+ export declare function isSdkTimeout(error: unknown, requestSignal: AbortSignal): boolean;
36
+ export declare function boundedErrorSnippet(value: unknown): string | undefined;
37
+ /** Keep server identifiers useful as exact machine-readable discriminants. */
38
+ export declare function boundedErrorIdentifier(value: unknown): string | undefined;
39
+ /** Keep only the documented retained metadata, with bounded wire values. */
40
+ export declare function boundedTombstone(value: unknown): TombstoneInfo | undefined;
41
+ /** Bound the structured per-field diagnostics exposed on ValidationError. */
42
+ export declare function boundedInvalidFields(value: unknown): Record<string, string> | undefined;
43
+ /** Bound server-controlled object keys before forwarding them to a debug sink. */
44
+ export declare function boundedObjectKeys(value: object): string[];