@layervai/qurl 2.0.0 → 3.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 CHANGED
@@ -590,7 +590,9 @@ console.log(`Access granted to ${access.target_url} for ${access.access_grant?.e
590
590
  | `createApiKey(input)` / `listApiKeys(input?)` / `listAllApiKeys(input?)` / `updateApiKey(id, input)` / `revokeApiKey(id)` | API key management |
591
591
  | `createAccessCode(input)` / `listAccessCodes()` / `redeemAccessCode(input)` / `revokeAccessCode(id)` | Access code management |
592
592
 
593
- `listResourceSessions(id)` and `listAccessCodes()` reflect currently unpaginated service endpoints. Their outputs always return `has_more: false`; if the service starts surfacing cursor metadata, the SDK emits a debug log rather than exposing an unactionable next-page signal.
593
+ `listResourceSessions(id)` opts in to bounded pages with `paged=true` and collects every service page, including empty intermediate pages, and returns `has_more: false`. Its `request_id` and `page_size` describe the last page. `terminateAllResourceSessions(id)` follows every page and sums closed sessions. Termination is not atomic: an error can occur after some sessions close. List sessions to reconcile before retrying; a retry can also close newly admitted sessions and counts only its own changes. A successful call returns the last page’s `request_id`.
594
+
595
+ `listAccessCodes()` remains unpaginated. It returns `has_more: false` and logs unexpected cursor metadata.
594
596
 
595
597
  `listAll*()` methods validate ids and query params when called, before the async iterator is consumed. Wrap the `listAll*()` call itself in `try/catch` when passing dynamic input.
596
598
 
@@ -968,3 +970,30 @@ peer: registration, restart, relocation, exact retirement, and credential recove
968
970
  This checks protocol interoperability; the peer is not a sandbox authority.
969
971
  Shared conformance vectors cover assignment, registration,
970
972
  OTP, and completion packet construction and reply decryption.
973
+
974
+
975
+ ### Private consumer policies
976
+
977
+ `createResource` accepts `private: true` and `consumer_access` with mode
978
+ `keys`, `invite_only`, `email_capture`, `review` or `contract`, and version `1`.
979
+ `updateResource` changes the policy with `expected_consumer_access_version` and
980
+ the next version. Send policy changes separately from metadata updates.
981
+
982
+ For a URL, `protectUrl(url, { consumerAccess: { mode: "invite_only", version: 1 } })`
983
+ creates the private resource and checks the returned policy. For a local
984
+ Connector, pass the policy as the third argument to `ensureConnectorResource`.
985
+ A private policy cannot be combined with public access. A service that ignores
986
+ the requested policy is refused.
987
+
988
+ Publisher methods are `inviteConsumer`, `listConsumerInvitations`,
989
+ `resendConsumerInvitation`, `revokeConsumerInvitation`, `listConsumerGrants`,
990
+ `approveConsumerGrant`, `denyConsumerGrant` and `revokeConsumerGrant`. Decisions
991
+ use the version read from the invitation or grant. Lists return the usual
992
+ `nextCursor`. Contract entitlement approval requires a trusted publisher backend
993
+ receipt and expiry; the SDK does not verify payment. Contract replies omit email
994
+ and globally reusable credential data.
995
+
996
+ A removal receipt reports `session_cleanup: pending` until connection cleanup
997
+ is confirmed. Retry the same removal and version to confirm `complete`. These
998
+ APIs require the matching service feature and remain off by default during
999
+ integration. Consumer Noise enrollment and browser access are in the NHP agent.
@@ -1,5 +1,5 @@
1
1
  import { ShareLink } from "./share.js";
2
- import type { AccessCodeListOutput, ApiKey, ApiKeyListOutput, ApiKeyRequestScope, BillingInvoiceListOutput, BatchCreateInput, BatchCreateOutput, ClientOptions, ConnectorInstallation, ConnectorInstallationListOutput, CreateAccessCodeInput, CreateAccessCodeOutput, CreateApiKeyInput, CreateApiKeyOutput, CreateBillingCheckoutInput, CreateDelegatedQurlBatchInput, CreateDelegatedQurlBatchOptions, CreateInput, CreateOutput, CreatePortalOptions, CreateQurlForResourceInput, CreateResourceInput, CreateWebhookInput, Customer, CheckoutSession, Domain, DomainListOutput, DomainVerifyResult, DelegatedQurl, DelegatedQurlBatchAccepted, GetDelegatedQurlBatchOptions, GetDelegatedQurlBatchOutput, ExtendInput, Invoice, ListInput, ListOutput, ListApiKeysInput, ListBillingInvoicesInput, ListConnectorInstallationsInput, ListDomainsInput, ListWebhookDeliveriesInput, ListWebhooksInput, MintInput, MintOutput, Portal, PortalSession, ProtectUrlOptions, QURL, QurlSummary, Quota, RedeemAccessCodeInput, RedeemAccessCodeOutput, RegisterDomainInput, Resource, ResourceDetail, ResourceHandle, ResourceListInput, ResourceListOutput, RequestOptions, ResolveInput, ResolveOutput, SessionListOutput, SessionTerminateOutput, ShareResourceOptions, UpdateApiKeyInput, UpdateCustomerInput, UpdateInput, UpdateResourceInput, UpdateResourceQurlInput, UpdateWebhookInput, UsageCurrentPeriod, UsageDaily, Webhook, WebhookDelivery, WebhookDeliveryListOutput, WebhookEventTypeInfo, WebhookListOutput, WebhookWithSecret } from "./types.js";
2
+ import type { ConsumerAccessPolicy, ConsumerInvitation, ConsumerGrant, ConsumerGrantApprovalInput, ConsumerListInput, ConsumerInvitationListOutput, ConsumerGrantListOutput, AccessCodeListOutput, ApiKey, ApiKeyListOutput, ApiKeyRequestScope, BillingInvoiceListOutput, BatchCreateInput, BatchCreateOutput, ClientOptions, ConnectorInstallation, ConnectorInstallationListOutput, CreateAccessCodeInput, CreateAccessCodeOutput, CreateApiKeyInput, CreateApiKeyOutput, CreateBillingCheckoutInput, CreateDelegatedQurlBatchInput, CreateDelegatedQurlBatchOptions, CreateInput, CreateOutput, CreatePortalOptions, CreateQurlForResourceInput, CreateResourceInput, CreateWebhookInput, Customer, CheckoutSession, Domain, DomainListOutput, DomainVerifyResult, DelegatedQurl, DelegatedQurlBatchAccepted, GetDelegatedQurlBatchOptions, GetDelegatedQurlBatchOutput, ExtendInput, Invoice, ListInput, ListOutput, ListApiKeysInput, ListBillingInvoicesInput, ListConnectorInstallationsInput, ListDomainsInput, ListWebhookDeliveriesInput, ListWebhooksInput, MintInput, MintOutput, Portal, PortalSession, ProtectUrlOptions, QURL, QurlSummary, Quota, RedeemAccessCodeInput, RedeemAccessCodeOutput, RegisterDomainInput, Resource, ResourceDetail, ResourceHandle, ResourceListInput, ResourceListOutput, RequestOptions, ResolveInput, ResolveOutput, SessionListOutput, SessionTerminateOutput, ShareResourceOptions, UpdateApiKeyInput, UpdateCustomerInput, UpdateInput, UpdateResourceInput, UpdateResourceQurlInput, UpdateWebhookInput, UsageCurrentPeriod, UsageDaily, Webhook, WebhookDelivery, WebhookDeliveryListOutput, WebhookEventTypeInfo, WebhookListOutput, WebhookWithSecret } from "./types.js";
3
3
  /** Narrow a response scope before reusing it in a create/update request. */
4
4
  export declare function isApiKeyRequestScope(scope: string): scope is ApiKeyRequestScope;
5
5
  /**
@@ -96,7 +96,7 @@ export declare class QURLClient {
96
96
  */
97
97
  resourceByCrid(id: string): ProtectedResource;
98
98
  /** Find or create the active qURL Connector resource for an immutable slug. */
99
- ensureConnectorResource(slug: string, requestOptions?: RequestOptions): Promise<EnsureConnectorResourceResult>;
99
+ ensureConnectorResource(slug: string, requestOptions?: RequestOptions, consumerAccess?: ConsumerAccessPolicy): Promise<EnsureConnectorResourceResult>;
100
100
  /** Fetch a qURL Connector resource by its CRID. */
101
101
  getConnectorResource(crid: string): Promise<ConnectorResource>;
102
102
  /** Fetch the single active qURL Connector resource for an immutable slug. */
@@ -358,6 +358,22 @@ export declare class QURLClient {
358
358
  getResource(id: string): Promise<ResourceDetail>;
359
359
  /** Update resource metadata. */
360
360
  updateResource(id: string, input: UpdateResourceInput, options?: RequestOptions): Promise<Resource>;
361
+ /** Send a publisher-only invitation. Mail delivery does not prove enrollment. */
362
+ inviteConsumer(id: string, email: string): Promise<ConsumerInvitation>;
363
+ listConsumerInvitations(id: string, input?: ConsumerListInput): Promise<ConsumerInvitationListOutput>;
364
+ listConsumerGrants(id: string, input?: ConsumerListInput): Promise<ConsumerGrantListOutput>;
365
+ resendConsumerInvitation(id: string, invitationId: string, expectedVersion: number): Promise<ConsumerInvitation>;
366
+ /** Remove the invitation and its grant. Inspect session_cleanup before reporting stream removal. */
367
+ revokeConsumerInvitation(id: string, invitationId: string, expectedVersion: number): Promise<ConsumerInvitation>;
368
+ /** Review approval, key opt-in with grantId new, or trusted publisher entitlement. */
369
+ approveConsumerGrant(id: string, grantId: string, input: ConsumerGrantApprovalInput): Promise<ConsumerGrant>;
370
+ denyConsumerGrant(id: string, grantId: string, expectedVersion: number): Promise<ConsumerGrant>;
371
+ revokeConsumerGrant(id: string, grantId: string, expectedVersion: number): Promise<ConsumerGrant>;
372
+ /**
373
+ * Revoke an API resource identifier retained by a client migrating to CRIDs.
374
+ * New callers should use deleteResource with the resource CRID.
375
+ */
376
+ deleteResourceByIdentifier(id: string): Promise<void>;
361
377
  /** Revoke a resource and all of its qURLs. */
362
378
  deleteResource(id: string): Promise<void>;
363
379
  /**
@@ -377,15 +393,13 @@ export declare class QURLClient {
377
393
  revokeResourceQurl(id: string, qurlId: string): Promise<void>;
378
394
  /** Update a specific qURL token on a resource. */
379
395
  updateResourceQurl(id: string, qurlId: string, input: UpdateResourceQurlInput, options?: RequestOptions): Promise<QurlSummary>;
396
+ /** List every active session, following bounded service pages. */
397
+ listResourceSessions(id: string): Promise<SessionListOutput>;
380
398
  /**
381
- * List active access sessions for a resource.
382
- *
383
- * The current API contract has no cursor query params for this endpoint.
384
- * If the service later advertises pagination metadata, the SDK returns
385
- * this page and emits a debug log rather than pretending it fetched all pages.
399
+ * End every session page. This is not atomic: a failed page throws after earlier
400
+ * pages may have closed sessions. List sessions to reconcile, then retry the call.
401
+ * A retry can close newly admitted sessions and counts only its own transitions.
386
402
  */
387
- listResourceSessions(id: string): Promise<SessionListOutput>;
388
- /** Terminate all active sessions for a resource and return the server count. */
389
403
  terminateAllResourceSessions(id: string): Promise<SessionTerminateOutput>;
390
404
  /** Terminate a specific resource session. */
391
405
  terminateResourceSession(id: string, sessionId: string): Promise<void>;
@@ -175,6 +175,8 @@ const RESOURCE_LIST_PARAM_KEYS = [
175
175
  ];
176
176
  assertExhaustive(true);
177
177
  const CREATE_RESOURCE_FIELD_KEYS = [
178
+ "private",
179
+ "consumer_access",
178
180
  "type",
179
181
  "target_url",
180
182
  "description",
@@ -186,6 +188,8 @@ const CREATE_RESOURCE_FIELD_KEYS = [
186
188
  ];
187
189
  assertExhaustive(true);
188
190
  const UPDATE_RESOURCE_FIELD_KEYS = [
191
+ "consumer_access",
192
+ "expected_consumer_access_version",
189
193
  "description",
190
194
  "tags",
191
195
  "custom_domain",
@@ -214,6 +218,8 @@ assertExhaustive(true);
214
218
  * fields the REST-shaped methods send.
215
219
  */
216
220
  const PROTECT_URL_OPTION_KEYS = [
221
+ "private",
222
+ "consumerAccess",
217
223
  "description",
218
224
  "tags",
219
225
  "customDomain",
@@ -1740,6 +1746,13 @@ function normalizePatchFields(input, fields, options = {}) {
1740
1746
  }
1741
1747
  return normalized;
1742
1748
  }
1749
+ function requireConsumerDecision(id, version, allowNew = false) {
1750
+ if (typeof id !== "string" ||
1751
+ (!/^[a-f0-9]{64}$/.test(id) && !(allowNew && id === "new")) ||
1752
+ !Number.isSafeInteger(version) ||
1753
+ version < (allowNew ? 0 : 1))
1754
+ throw clientValidationError("consumer decision: invalid id or expected version");
1755
+ }
1743
1756
  function validateResourceWriteFields(input, options = {}) {
1744
1757
  const errors = [];
1745
1758
  const collect = (fn) => {
@@ -1754,6 +1767,50 @@ function validateResourceWriteFields(input, options = {}) {
1754
1767
  throw err;
1755
1768
  }
1756
1769
  };
1770
+ collect(() => {
1771
+ if (input.private !== undefined && typeof input.private !== "boolean") {
1772
+ throw clientValidationError("private: must be a boolean");
1773
+ }
1774
+ if (input.consumer_access !== undefined) {
1775
+ const policy = input.consumer_access;
1776
+ requireObjectInput(policy, "consumer_access");
1777
+ const p = policy;
1778
+ requireNoUnknownFields(p, ["mode", "version", "contract_id"], "consumer_access");
1779
+ if (!["keys", "invite_only", "email_capture", "review", "contract"].includes(p.mode) ||
1780
+ !Number.isSafeInteger(p.version) ||
1781
+ p.version < 1) {
1782
+ throw clientValidationError("consumer_access: invalid mode or version");
1783
+ }
1784
+ if (p.mode === "contract") {
1785
+ if (typeof p.contract_id !== "string" ||
1786
+ !p.contract_id.trim() ||
1787
+ new TextEncoder().encode(p.contract_id).length > 128) {
1788
+ throw clientValidationError("consumer_access: contract mode requires a bounded contract_id");
1789
+ }
1790
+ }
1791
+ else if (p.contract_id !== undefined) {
1792
+ throw clientValidationError("consumer_access: contract_id requires contract mode");
1793
+ }
1794
+ if (input.private === false)
1795
+ throw clientValidationError("consumer_access: requires a private resource");
1796
+ if (options.requireUrlTarget) {
1797
+ if (p.version !== 1 || input.private !== true)
1798
+ throw clientValidationError("consumer_access: creation requires private true and version one");
1799
+ }
1800
+ else if (options.allowAliasClear) {
1801
+ const expected = input.expected_consumer_access_version;
1802
+ if (!Number.isSafeInteger(expected) ||
1803
+ expected < 0 ||
1804
+ p.version !== expected + 1 ||
1805
+ Object.keys(input).some((key) => key !== "consumer_access" && key !== "expected_consumer_access_version")) {
1806
+ throw clientValidationError("consumer_access: update requires its expected version and no metadata changes");
1807
+ }
1808
+ }
1809
+ }
1810
+ else if (input.expected_consumer_access_version !== undefined) {
1811
+ throw clientValidationError("expected_consumer_access_version: requires consumer_access");
1812
+ }
1813
+ });
1757
1814
  const resourceType = input.type;
1758
1815
  if (resourceType !== undefined && typeof resourceType !== "string") {
1759
1816
  errors.push(`type: must be a string (got ${resourceType === null ? "null" : typeof resourceType})`);
@@ -2426,6 +2483,12 @@ class QURLClient {
2426
2483
  // SDK-wide normalizePatchFields convention.
2427
2484
  const normalized = normalizePatchFields(opts, PROTECT_URL_OPTION_KEYS);
2428
2485
  const body = { target_url: targetUrl };
2486
+ if (normalized.private !== undefined)
2487
+ body.private = normalized.private;
2488
+ if (normalized.consumerAccess !== undefined) {
2489
+ body.private = normalized.private ?? true;
2490
+ body.consumer_access = normalized.consumerAccess;
2491
+ }
2429
2492
  if (normalized.description !== undefined)
2430
2493
  body.description = normalized.description;
2431
2494
  if (normalized.tags !== undefined)
@@ -2434,9 +2497,19 @@ class QURLClient {
2434
2497
  body.custom_domain = normalized.customDomain;
2435
2498
  if (normalized.alias !== undefined)
2436
2499
  body.alias = normalized.alias;
2437
- validateResourceWriteFields(body);
2438
- const resource = await this.request("POST", "/v1/resources", body, requestOptions);
2500
+ validateResourceWriteFields(body, { requireUrlTarget: true });
2501
+ const response = await this.rawRequest("POST", "/v1/resources", body, {
2502
+ requestOptions,
2503
+ });
2504
+ if (response.__http_status === 204)
2505
+ throw unexpectedResponseError("protectUrl: expected a resource response body");
2506
+ const resource = response.data;
2439
2507
  await verifyResourceIdentity(resource, "protectUrl");
2508
+ if (normalized.consumerAccess &&
2509
+ (resource.private !== true ||
2510
+ !sameConsumerPolicy(resource.consumer_access, normalized.consumerAccess, response.meta?.found_existing === true))) {
2511
+ throw unexpectedResponseError("protectUrl: private policy was not confirmed");
2512
+ }
2440
2513
  return new ProtectedResource(this, resource.crid,
2441
2514
  // Some resource types redact target_url in responses; fall back to
2442
2515
  // the caller-supplied URL so the handle stays useful.
@@ -2462,8 +2535,13 @@ class QURLClient {
2462
2535
  return new ProtectedResource(this, id);
2463
2536
  }
2464
2537
  /** Find or create the active qURL Connector resource for an immutable slug. */
2465
- async ensureConnectorResource(slug, requestOptions) {
2538
+ async ensureConnectorResource(slug, requestOptions, consumerAccess) {
2466
2539
  requireConnectorSlug(slug, "ensureConnectorResource");
2540
+ if (consumerAccess !== undefined) {
2541
+ validateResourceWriteFields({ consumer_access: consumerAccess, private: true });
2542
+ if (consumerAccess.version !== 1)
2543
+ throw clientValidationError("consumer_access: creation requires version one");
2544
+ }
2467
2545
  // Response validation imports the producer's P-256 resource key. Preflight
2468
2546
  // before POST so a missing runtime capability cannot orphan a committed row.
2469
2547
  requireConnectorSubtleCrypto("ensureConnectorResource");
@@ -2478,12 +2556,18 @@ class QURLClient {
2478
2556
  type: "tunnel",
2479
2557
  slug,
2480
2558
  find_or_create: true,
2559
+ ...(consumerAccess ? { private: true, consumer_access: consumerAccess } : {}),
2481
2560
  }, { requestOptions, retry: false, generateIdempotencyKey: false });
2482
2561
  // The find-or-create endpoint returns 201 for both newly-created
2483
2562
  // and found-existing rows; meta.found_existing distinguishes the arms.
2484
2563
  if (response.__http_status !== 201) {
2485
2564
  throw unexpectedResponseError(`ensureConnectorResource: expected HTTP 201, got ${response.__http_status ?? "unknown"}`);
2486
2565
  }
2566
+ if (consumerAccess &&
2567
+ (response.data.private !== true ||
2568
+ !sameConsumerPolicy(response.data.consumer_access, consumerAccess, response.meta?.found_existing === true))) {
2569
+ throw unexpectedResponseError("ensureConnectorResource: private policy was not confirmed");
2570
+ }
2487
2571
  resource = await parseConnectorResource(this, response.data, "ensureConnectorResource", {
2488
2572
  slug,
2489
2573
  });
@@ -3270,6 +3354,76 @@ class QURLClient {
3270
3354
  });
3271
3355
  return this.request("PATCH", `/v1/resources/${encodeURIComponent(id)}`, validationInput, options);
3272
3356
  }
3357
+ /** Send a publisher-only invitation. Mail delivery does not prove enrollment. */
3358
+ async inviteConsumer(id, email) {
3359
+ requireResourceCrid(id, "inviteConsumer");
3360
+ requireNonEmptyIfPresent(email, "email");
3361
+ if (typeof email !== "string" || email.length > 320)
3362
+ throw clientValidationError("email: must be a bounded string");
3363
+ return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/invitations`, { email });
3364
+ }
3365
+ async listConsumerInvitations(id, input = {}) {
3366
+ requireResourceCrid(id, "listConsumerInvitations");
3367
+ const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/invitations`, input, ["cursor", "limit"], "listConsumerInvitations"));
3368
+ return pageFromMeta({ invitations: data ?? [] }, meta);
3369
+ }
3370
+ async listConsumerGrants(id, input = {}) {
3371
+ requireResourceCrid(id, "listConsumerGrants");
3372
+ const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/consumer-grants`, input, ["cursor", "limit"], "listConsumerGrants"));
3373
+ return pageFromMeta({ grants: data ?? [] }, meta);
3374
+ }
3375
+ async resendConsumerInvitation(id, invitationId, expectedVersion) {
3376
+ requireResourceCrid(id, "resendConsumerInvitation");
3377
+ requireConsumerDecision(invitationId, expectedVersion);
3378
+ return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/invitations/${encodeURIComponent(invitationId)}/resend`, { expected_version: expectedVersion });
3379
+ }
3380
+ /** Remove the invitation and its grant. Inspect session_cleanup before reporting stream removal. */
3381
+ async revokeConsumerInvitation(id, invitationId, expectedVersion) {
3382
+ requireResourceCrid(id, "revokeConsumerInvitation");
3383
+ requireConsumerDecision(invitationId, expectedVersion);
3384
+ return this.request("DELETE", `/v1/resources/${encodeURIComponent(id)}/invitations/${encodeURIComponent(invitationId)}?expected_version=${expectedVersion}`);
3385
+ }
3386
+ /** Review approval, key opt-in with grantId new, or trusted publisher entitlement. */
3387
+ async approveConsumerGrant(id, grantId, input) {
3388
+ requireResourceCrid(id, "approveConsumerGrant");
3389
+ requireObjectInput(input, "approveConsumerGrant");
3390
+ requireNoUnknownFields(input, [
3391
+ "expected_version",
3392
+ "credential_kind",
3393
+ "credential_id",
3394
+ "credential_public_key_b64",
3395
+ "entitlement_id",
3396
+ "expires_at",
3397
+ ], "approveConsumerGrant");
3398
+ requireConsumerDecision(grantId, input.expected_version, grantId === "new");
3399
+ return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/consumer-grants/${encodeURIComponent(grantId)}/approve`, input);
3400
+ }
3401
+ async denyConsumerGrant(id, grantId, expectedVersion) {
3402
+ requireResourceCrid(id, "denyConsumerGrant");
3403
+ requireConsumerDecision(grantId, expectedVersion);
3404
+ return this.request("POST", `/v1/resources/${encodeURIComponent(id)}/consumer-grants/${encodeURIComponent(grantId)}/deny`, { expected_version: expectedVersion });
3405
+ }
3406
+ async revokeConsumerGrant(id, grantId, expectedVersion) {
3407
+ requireResourceCrid(id, "revokeConsumerGrant");
3408
+ requireConsumerDecision(grantId, expectedVersion);
3409
+ return this.request("DELETE", `/v1/resources/${encodeURIComponent(id)}/consumer-grants/${encodeURIComponent(grantId)}?expected_version=${expectedVersion}`);
3410
+ }
3411
+ /**
3412
+ * Revoke an API resource identifier retained by a client migrating to CRIDs.
3413
+ * New callers should use deleteResource with the resource CRID.
3414
+ */
3415
+ async deleteResourceByIdentifier(id) {
3416
+ if (typeof id !== "string")
3417
+ throw clientValidationError("id: invalid resource identifier");
3418
+ if (/^r_[a-z0-9_-]{11}$/.test(id)) {
3419
+ await this.requestNoContent(`/v1/qurls/${encodeURIComponent(id)}`);
3420
+ return;
3421
+ }
3422
+ const publicKey = /^[A-Za-z0-9_-]{107,214}$/.test(id) && id.length % 4 !== 1;
3423
+ if (!publicKey)
3424
+ requireResourceCrid(id, "deleteResourceByIdentifier");
3425
+ await this.requestNoContent(`/v1/resources/${encodeURIComponent(id)}`);
3426
+ }
3273
3427
  /** Revoke a resource and all of its qURLs. */
3274
3428
  async deleteResource(id) {
3275
3429
  requireResourceCrid(id, "deleteResource");
@@ -3376,43 +3530,63 @@ class QURLClient {
3376
3530
  validateResourceQurlUpdateInput(normalized);
3377
3531
  return this.request("PATCH", `/v1/resources/${encodeURIComponent(id)}/qurls/${encodeURIComponent(qurlId)}`, normalized, options);
3378
3532
  }
3379
- /**
3380
- * List active access sessions for a resource.
3381
- *
3382
- * The current API contract has no cursor query params for this endpoint.
3383
- * If the service later advertises pagination metadata, the SDK returns
3384
- * this page and emits a debug log rather than pretending it fetched all pages.
3385
- */
3533
+ /** List every active session, following bounded service pages. */
3386
3534
  async listResourceSessions(id) {
3387
3535
  requireResourceCrid(id, "listResourceSessions");
3388
- const { data, meta } = await this.rawRequest("GET", `/v1/resources/${encodeURIComponent(id)}/sessions`);
3389
- if (meta?.has_more || meta?.next_cursor) {
3390
- this.log("listResourceSessions: pagination metadata surfaced on unpaginated endpoint", {
3391
- has_more: meta.has_more,
3392
- next_cursor: meta.next_cursor,
3393
- });
3536
+ const sessions = [];
3537
+ let request_id;
3538
+ let page_size;
3539
+ for await (const session of this.paginateAll("listResourceSessions", {}, async ({ cursor }) => {
3540
+ const { data, meta } = await this.rawRequest("GET", appendQuery(`/v1/resources/${encodeURIComponent(id)}/sessions`, { paged: "true", cursor }, ["paged", "cursor"], "listResourceSessions"));
3541
+ if (meta?.has_more && !meta.next_cursor) {
3542
+ throw unexpectedResponseError("Session page has no continuation cursor", {
3543
+ requestId: meta.request_id,
3544
+ });
3545
+ }
3546
+ request_id = meta?.request_id;
3547
+ page_size = meta?.page_size;
3548
+ return { ...meta, sessions: data ?? [] };
3549
+ }, (page) => page.sessions)) {
3550
+ sessions.push(session);
3394
3551
  }
3395
- return {
3396
- sessions: data ?? [],
3397
- request_id: meta?.request_id,
3398
- has_more: false,
3399
- page_size: meta?.page_size,
3400
- };
3552
+ return { sessions, request_id, has_more: false, page_size };
3401
3553
  }
3402
- /** Terminate all active sessions for a resource and return the server count. */
3554
+ /**
3555
+ * End every session page. This is not atomic: a failed page throws after earlier
3556
+ * pages may have closed sessions. List sessions to reconcile, then retry the call.
3557
+ * A retry can close newly admitted sessions and counts only its own transitions.
3558
+ */
3403
3559
  async terminateAllResourceSessions(id) {
3404
3560
  requireResourceCrid(id, "terminateAllResourceSessions");
3405
3561
  const path = `/v1/resources/${encodeURIComponent(id)}/sessions`;
3406
- const { data, meta, __http_status } = await this.rawRequest("DELETE", path);
3407
- if (__http_status === 204) {
3408
- throw unexpectedResponseError(`Unexpected 204 No Content from DELETE ${path}; expected response body`);
3409
- }
3410
- if (data?.terminated === undefined) {
3411
- this.log("terminateAllResourceSessions: missing terminated count; defaulting to zero", {
3412
- request_id: meta?.request_id,
3413
- });
3562
+ let terminated = 0;
3563
+ let request_id;
3564
+ for await (const count of this.paginateAll("terminateAllResourceSessions", {}, async ({ cursor }) => {
3565
+ const { data, meta, __http_status } = await this.rawRequest("DELETE", appendQuery(path, { paged: "true", cursor }, ["paged", "cursor"], "terminateAllResourceSessions"));
3566
+ if (__http_status === 204) {
3567
+ throw unexpectedResponseError(`Unexpected 204 No Content from DELETE ${path}; expected response body`, { status: 204, requestId: meta?.request_id });
3568
+ }
3569
+ if (meta?.has_more && !meta.next_cursor) {
3570
+ throw unexpectedResponseError("Session page has no continuation cursor", {
3571
+ requestId: meta.request_id,
3572
+ });
3573
+ }
3574
+ if (data?.terminated === undefined) {
3575
+ this.log("terminateAllResourceSessions: missing terminated count; defaulting to zero", {
3576
+ request_id: meta?.request_id,
3577
+ });
3578
+ }
3579
+ else if (!Number.isSafeInteger(data.terminated) || data.terminated < 0) {
3580
+ throw unexpectedResponseError("terminateAllResourceSessions: invalid terminated count", {
3581
+ requestId: meta?.request_id,
3582
+ });
3583
+ }
3584
+ request_id = meta?.request_id;
3585
+ return { ...meta, counts: [data?.terminated ?? 0] };
3586
+ }, (page) => page.counts)) {
3587
+ terminated += count;
3414
3588
  }
3415
- return { terminated: data?.terminated ?? 0, request_id: meta?.request_id };
3589
+ return { terminated, request_id };
3416
3590
  }
3417
3591
  /** Terminate a specific resource session. */
3418
3592
  async terminateResourceSession(id, sessionId) {
@@ -4294,3 +4468,11 @@ class ConnectorResource {
4294
4468
  }
4295
4469
  }
4296
4470
  exports.ConnectorResource = ConnectorResource;
4471
+ function sameConsumerPolicy(actual, expected, foundExisting) {
4472
+ return (actual?.mode === expected.mode &&
4473
+ (actual?.version === expected.version ||
4474
+ (foundExisting &&
4475
+ Number.isSafeInteger(actual?.version) &&
4476
+ Number(actual?.version) > expected.version)) &&
4477
+ actual?.contract_id === expected.contract_id);
4478
+ }
@@ -4,4 +4,4 @@ export { CRIDVerificationError, ShareLink } from "./share.js";
4
4
  export type { CRIDVerificationErrorCode, ShareLinkInit, ShareLinkJSON } from "./share.js";
5
5
  export { AuthenticationError, AuthorizationError, ConnectorResourceOutcomeUnknownError, createError, DelegatedBatchOutcomeUnknownError, ERROR_CODE_AMBIGUOUS_RESOURCE, ERROR_CODE_CLIENT_VALIDATION, ERROR_CODE_CONNECTOR_RESOURCE_OUTCOME_UNKNOWN, ERROR_CODE_CONNECTOR_RESOURCE_REVOKED, ERROR_CODE_CRID_MISMATCH, ERROR_CODE_DELEGATED_BATCH_OUTCOME_UNKNOWN, ERROR_CODE_INVALID_CRID, ERROR_CODE_INVALID_CRID_KEY, ERROR_CODE_MISSING_CRID, ERROR_CODE_NETWORK, ERROR_CODE_RESOURCE_NOT_FOUND, ERROR_CODE_RUNTIME, ERROR_CODE_TIMEOUT, ERROR_CODE_UNEXPECTED_RESPONSE, ERROR_CODE_UNKNOWN, NetworkError, NotFoundError, QURLError, RateLimitError, RuntimeError, ServerError, TimeoutError, ValidationError, } from "./errors.js";
6
6
  export { VERSION } from "./version.js";
7
- export type { AccessCode, AccessCodeListOutput, AccessGrant, AccessPolicy, AccessToken, AIAgentCategory, AIAgentPolicy, ApiKey, ApiKeyListOutput, ApiKeyRequestScope, ApiKeyScope, BatchCreateInput, BatchCreateOutput, BatchItemFailure, BatchItemResult, BatchItemSuccess, BillingInvoiceListOutput, ClientOptions, ConnectorInstallation, ConnectorInstallationCapabilities, ConnectorInstallationListOutput, ConnectorInstallationStats, CreateAccessCodeInput, CreateAccessCodeOutput, CreateApiKeyInput, CreateApiKeyOutput, CreateDurableApiKeyInput, CreateEnrollmentTokenInput, CreateBillingCheckoutInput, CreateDelegatedQurlBatchInput, CreateDelegatedQurlBatchOptions, CreateInput, CreateOutput, CreatePortalOptions, CreateQurlForResourceInput, CreateResourceInput, CreateWebhookInput, CredentialClaim, CredentialClaimInput, CredentialKind, CredentialTarget, CredentialTargetInput, Customer, CheckoutSession, DnsRecord, Domain, DomainCheckDetail, DomainListOutput, DomainVerifyResult, DelegatedQurl, DelegatedQurlBatchAccepted, DelegatedQurlBatchItemFailure, DelegatedQurlBatchItemResult, DelegatedQurlBatchItemSuccess, DelegatedQurlBatchNotModified, DelegatedQurlBatchPending, DelegatedQurlBatchStatus, DelegatedQurlBatchTerminal, DelegatedQurlCreatedData, DelegatedQurlGrant, ExtendInput, Invoice, GetDelegatedQurlBatchOptions, GetDelegatedQurlBatchOutput, ListApiKeysInput, ListBillingInvoicesInput, ListConnectorInstallationsInput, ListDomainsInput, ListInput, ListOutput, ListWebhooksInput, ListWebhookDeliveriesInput, Meta, MintInput, MintOutput, PaginatedOutput, Portal, PortalSession, ProtectUrlOptions, QURL, QURLErrorData, QurlSummary, Quota, RedeemAccessCodeInput, RedeemAccessCodeOutput, RegisterDomainInput, Resource, ResourceDetail, ResourceHandle, ResourceListInput, ResourceListOutput, ResourceType, RequestOptions, ResolveInput, ResolveOutput, Session, SessionListOutput, SessionTerminateOutput, ShareResourceOptions, TombstoneInfo, UnpaginatedOutput, UpdateApiKeyInput, UpdateCustomerInput, UpdateInput, UpdateResourceInput, UpdateResourceQurlInput, UpdateWebhookInput, UsageCostEstimate, UsageCurrentPeriod, UsageDaily, UsageDailyEntry, Webhook, WebhookDelivery, WebhookDeliveryListOutput, WebhookEventType, WebhookEventTypeInfo, WebhookListOutput, WebhookPayload, WebhookWithSecret, } from "./types.js";
7
+ export type { ConsumerAccessPolicy, AccessCode, AccessCodeListOutput, AccessGrant, ConsumerInvitation, ConsumerGrant, ConsumerGrantApprovalInput, ConsumerListInput, ConsumerInvitationListOutput, ConsumerGrantListOutput, AccessPolicy, AccessToken, AIAgentCategory, AIAgentPolicy, ApiKey, ApiKeyListOutput, ApiKeyRequestScope, ApiKeyScope, BatchCreateInput, BatchCreateOutput, BatchItemFailure, BatchItemResult, BatchItemSuccess, BillingInvoiceListOutput, ClientOptions, ConnectorInstallation, ConnectorInstallationCapabilities, ConnectorInstallationListOutput, ConnectorInstallationStats, CreateAccessCodeInput, CreateAccessCodeOutput, CreateApiKeyInput, CreateApiKeyOutput, CreateDurableApiKeyInput, CreateEnrollmentTokenInput, CreateBillingCheckoutInput, CreateDelegatedQurlBatchInput, CreateDelegatedQurlBatchOptions, CreateInput, CreateOutput, CreatePortalOptions, CreateQurlForResourceInput, CreateResourceInput, CreateWebhookInput, CredentialClaim, CredentialClaimInput, CredentialKind, CredentialTarget, CredentialTargetInput, Customer, CheckoutSession, DnsRecord, Domain, DomainCheckDetail, DomainListOutput, DomainVerifyResult, DelegatedQurl, DelegatedQurlBatchAccepted, DelegatedQurlBatchItemFailure, DelegatedQurlBatchItemResult, DelegatedQurlBatchItemSuccess, DelegatedQurlBatchNotModified, DelegatedQurlBatchPending, DelegatedQurlBatchStatus, DelegatedQurlBatchTerminal, DelegatedQurlCreatedData, DelegatedQurlGrant, ExtendInput, Invoice, GetDelegatedQurlBatchOptions, GetDelegatedQurlBatchOutput, ListApiKeysInput, ListBillingInvoicesInput, ListConnectorInstallationsInput, ListDomainsInput, ListInput, ListOutput, ListWebhooksInput, ListWebhookDeliveriesInput, Meta, MintInput, MintOutput, PaginatedOutput, Portal, PortalSession, ProtectUrlOptions, QURL, QURLErrorData, QurlSummary, Quota, RedeemAccessCodeInput, RedeemAccessCodeOutput, RegisterDomainInput, Resource, ResourceDetail, ResourceHandle, ResourceListInput, ResourceListOutput, ResourceType, RequestOptions, ResolveInput, ResolveOutput, Session, SessionListOutput, SessionTerminateOutput, ShareResourceOptions, TombstoneInfo, UnpaginatedOutput, UpdateApiKeyInput, UpdateCustomerInput, UpdateInput, UpdateResourceInput, UpdateResourceQurlInput, UpdateWebhookInput, UsageCostEstimate, UsageCurrentPeriod, UsageDaily, UsageDailyEntry, Webhook, WebhookDelivery, WebhookDeliveryListOutput, WebhookEventType, WebhookEventTypeInfo, WebhookListOutput, WebhookPayload, WebhookWithSecret, } from "./types.js";
@@ -235,6 +235,8 @@ export interface MintOutput {
235
235
  }
236
236
  /** Options for `QURLClient.protectUrl`. */
237
237
  export interface ProtectUrlOptions {
238
+ private?: boolean;
239
+ consumerAccess?: ConsumerAccessPolicy;
238
240
  /** Optional resource-level metadata. Max 500 chars. */
239
241
  description?: string;
240
242
  /** Optional resource-level tags. Max 10 items. */
@@ -356,7 +358,6 @@ export interface Quota {
356
358
  list_per_minute?: number;
357
359
  resolve_per_minute?: number;
358
360
  max_active_qurls?: number;
359
- max_tokens_per_qurl?: number;
360
361
  max_expiry_seconds?: number;
361
362
  };
362
363
  usage?: {
@@ -509,8 +510,16 @@ export interface DelegatedQurl {
509
510
  session_duration: number;
510
511
  access_policy?: AccessPolicy;
511
512
  }
513
+ /** Private resource policy. Contract fulfillment comes from the publisher backend. */
514
+ export interface ConsumerAccessPolicy {
515
+ mode: "keys" | "invite_only" | "email_capture" | "review" | "contract";
516
+ version: number;
517
+ contract_id?: string;
518
+ }
512
519
  /** Input for creating a resource directly. */
513
520
  export interface CreateResourceInput {
521
+ private?: boolean;
522
+ consumer_access?: ConsumerAccessPolicy;
514
523
  type?: ResourceType;
515
524
  target_url?: string;
516
525
  description?: string;
@@ -522,6 +531,8 @@ export interface CreateResourceInput {
522
531
  }
523
532
  /** Input for updating resource metadata. */
524
533
  export interface UpdateResourceInput {
534
+ consumer_access?: ConsumerAccessPolicy;
535
+ expected_consumer_access_version?: number;
525
536
  description?: string;
526
537
  tags?: string[];
527
538
  /** Omit to leave unchanged; send `""` to clear. `null` is not a clear signal. */
@@ -532,6 +543,9 @@ export interface UpdateResourceInput {
532
543
  }
533
544
  /** Resource data returned by the `/v1/resources` surface. */
534
545
  export interface Resource {
546
+ private?: boolean;
547
+ consumer_access?: ConsumerAccessPolicy;
548
+ consumer_access_version?: number;
535
549
  resource_id: string;
536
550
  /** Cryptographic Resource ID derived from the resource public key. */
537
551
  crid?: string;
@@ -632,13 +646,13 @@ export interface UnpaginatedOutput {
632
646
  has_more: false;
633
647
  page_size?: number;
634
648
  }
635
- export interface SessionListOutput extends UnpaginatedOutput {
636
- /**
637
- * Active sessions. The current OpenAPI contract has no cursor query params;
638
- * if pagination metadata appears, the SDK surfaces it for observability but
639
- * returns this page only.
640
- */
649
+ /** All session pages collected by the SDK; request_id and page_size describe the last page. */
650
+ export interface SessionListOutput {
641
651
  sessions: Session[];
652
+ request_id?: string;
653
+ /** Always false because the SDK follows every page; retained for compatibility. */
654
+ has_more: false;
655
+ page_size?: number;
642
656
  }
643
657
  export interface SessionTerminateOutput {
644
658
  terminated: number;
@@ -775,7 +789,7 @@ export interface DomainVerifyResult {
775
789
  traffic_routing?: DomainCheckDetail;
776
790
  };
777
791
  }
778
- export type WebhookEventType = OpenString<"qurl.created" | "qurl.expired" | "qurl.revoked" | "qurl.updated" | "resource.closed" | "qurl.accessed" | "qurl.access_denied" | "qurl.token_exhausted" | "quota.warning" | "quota.exceeded" | "token.minted" | "token.expired" | "domain.verified" | "domain.failed" | "domain.deleted">;
792
+ export type WebhookEventType = OpenString<"qurl.created" | "qurl.expired" | "qurl.revoked" | "qurl.updated" | "resource.closed" | "qurl.accessed" | "consumer.subscribed" | "qurl.access_denied" | "qurl.token_exhausted" | "quota.warning" | "quota.exceeded" | "token.minted" | "token.expired" | "domain.verified" | "domain.failed" | "domain.deleted">;
779
793
  export interface CreateWebhookInput {
780
794
  /** Webhook receiver URL. The service accepts public http/https URLs; prefer https in production. */
781
795
  url: string;
@@ -1019,4 +1033,46 @@ export interface ClientOptions {
1019
1033
  /** Enable debug logging. Pass `true` for console.debug, or a custom callback. */
1020
1034
  debug?: boolean | ((message: string, data?: Record<string, unknown>) => void);
1021
1035
  }
1036
+ /** Publisher-only invitation status; no link secret is returned. */
1037
+ export interface ConsumerInvitation {
1038
+ id: string;
1039
+ email: string;
1040
+ status: "open" | "enrolled" | "revoked";
1041
+ delivery: "queued" | "accepted" | "failed";
1042
+ version: number;
1043
+ expires_at: string;
1044
+ session_cleanup?: "pending" | "complete";
1045
+ }
1046
+ /** Resource-scoped consumer decision. Contract mode omits identity. */
1047
+ export interface ConsumerGrant {
1048
+ id: string;
1049
+ status: "pending" | "active" | "revoked";
1050
+ version: number;
1051
+ credential_kind?: "passkey" | "device";
1052
+ credential_id?: string;
1053
+ credential_public_key_b64?: string;
1054
+ email?: string;
1055
+ email_verified_at?: string;
1056
+ entitlement_id?: string;
1057
+ expires_at?: string;
1058
+ session_cleanup?: "pending" | "complete";
1059
+ }
1060
+ export interface ConsumerGrantApprovalInput {
1061
+ expected_version: number;
1062
+ credential_kind?: "passkey" | "device";
1063
+ credential_id?: string;
1064
+ credential_public_key_b64?: string;
1065
+ entitlement_id?: string;
1066
+ expires_at?: string;
1067
+ }
1068
+ export interface ConsumerListInput {
1069
+ cursor?: string;
1070
+ limit?: number;
1071
+ }
1072
+ export interface ConsumerInvitationListOutput extends PaginatedOutput {
1073
+ invitations: ConsumerInvitation[];
1074
+ }
1075
+ export interface ConsumerGrantListOutput extends PaginatedOutput {
1076
+ grants: ConsumerGrant[];
1077
+ }
1022
1078
  export {};
@@ -1 +1 @@
1
- export declare const VERSION = "2.0.0";
1
+ export declare const VERSION = "3.0.0";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = "2.0.0"; // x-release-please-version
4
+ exports.VERSION = "3.0.0"; // x-release-please-version