@particle-academy/fancy-connector-core 0.13.0 → 0.14.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/dist/index.d.cts CHANGED
@@ -916,6 +916,72 @@ type ConnectorResult<T = unknown> = {
916
916
  declare function registerTransport(next: Transport | null): void;
917
917
  declare function callConnector<T = unknown>(service: ServiceDescriptor, options: CallOptions): Promise<ConnectorResult<T>>;
918
918
 
919
+ /**
920
+ * A read that resumes from a HOST-HELD cursor, and pages.
921
+ *
922
+ * Gmail's push and Drive's channel notifications carry no changes: they say
923
+ * "something moved", and the follow-up read fetches what did, starting from
924
+ * the position the HOST stored last time (Gmail's `startHistoryId`, Drive's
925
+ * `pageToken`). Reading from the delivery's own position instead misses the
926
+ * very change that caused it. The package holds no state: the host stores the
927
+ * cursor, passes it in, and persists `cursor.next` when the run succeeds.
928
+ *
929
+ * The PHP twin is `CursorRead::read`; both read `fixtures/cursor-read/`.
930
+ *
931
+ * The decisions, each pinned by a case:
932
+ *
933
+ * - **`next` comes ONLY from the LAST page.** Gmail's `historyId` is the
934
+ * mailbox's CURRENT one on every page, and Google says to store it only "if
935
+ * you receive no nextPageToken". Taking it after page one would skip every
936
+ * page not yet read, silently.
937
+ * - **Too many pages is refused** (`cursor-overflow`), never a partial read
938
+ * with a cursor that skips the rest. The host's answer is a bounded resync.
939
+ * - **The provider's own "cursor out of range"** (`expiredStatus`, Gmail's
940
+ * 404) is `cursor-expired`; every OTHER failure is rethrown unchanged, so a
941
+ * 500 is never mistaken for a lapse.
942
+ * - **No stored cursor is `cursor-missing`**, and the provider is not called:
943
+ * a read with no starting point is a read from the beginning of time.
944
+ * - `collect` absent is `[]` (Gmail omits `history` when nothing changed); not
945
+ * a list is `bad-page`. The cursor and `next` are strings: history ids are
946
+ * uint64.
947
+ */
948
+ declare const CURSOR_READ_REASONS: readonly ["cursor-missing", "cursor-expired", "cursor-overflow", "bad-page"];
949
+ type CursorReadReason = (typeof CURSOR_READ_REASONS)[number];
950
+ type CursorReadSpec = {
951
+ /** The read's parameter the stored cursor fills (Gmail: `startHistoryId`). */
952
+ param: string;
953
+ /** Where the NEXT cursor is read in the last page's data (Gmail: `historyId`). */
954
+ advanceFrom: string;
955
+ /** Paging, when the read pages (Gmail: `pageToken` / `nextPageToken`, `history`). */
956
+ page?: {
957
+ param: string;
958
+ from: string;
959
+ collect: string;
960
+ maxPages: number;
961
+ };
962
+ /** The status the provider answers for a cursor out of range (Gmail: 404). */
963
+ expiredStatus?: number;
964
+ };
965
+ type CursorReadResult = {
966
+ data: unknown;
967
+ cursor: {
968
+ used: string;
969
+ next: string | null;
970
+ };
971
+ };
972
+ /** Refused BY NAME; `reason` is the same string PHP's exception carries. */
973
+ declare class CursorReadError extends Error {
974
+ readonly reason: CursorReadReason;
975
+ /** The host's bounded resync, when the caller declared one (Gmail: `newer_than:7d`). */
976
+ readonly resyncQuery?: string | undefined;
977
+ constructor(reason: CursorReadReason, message: string,
978
+ /** The host's bounded resync, when the caller declared one (Gmail: `newer_than:7d`). */
979
+ resyncQuery?: string | undefined);
980
+ }
981
+ declare function readWithCursor(spec: CursorReadSpec, cursor: unknown, call: (params: Record<string, string>) => Promise<unknown>, options?: {
982
+ resyncQuery?: string;
983
+ }): Promise<CursorReadResult>;
984
+
919
985
  /**
920
986
  * API drift — being told the provider changed, rather than discovering it.
921
987
  *
@@ -2946,4 +3012,4 @@ oidc?: OidcExpectation): Promise<WebhookVerification>;
2946
3012
  */
2947
3013
  declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
2948
3014
 
2949
- export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type Attendee, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, DELIVERY_DECODE_KINDS, type DeliveryDeclaration, type DeliveryDecode, type DeliveryMechanism, type DeliveryOutcome, type DeliveryRoute, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, FORM_FIELD_JSON_REASONS, type FailureKind, type FakeRequest, type FakeValues, FormFieldJsonError, type FormFieldJsonReason, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type Invite, type InviteMethod, JWT_ALGORITHMS, type JoinTarget, type JoinTargetKind, type Jwk, type JwtVerification, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, OIDC_JWT_SCHEME_KIND, type OidcExpectation, type OidcJwtScheme, type OidcJwtVerificationSpec, PROBLEM_SEVERITIES, PUBSUB_PUSH_REASONS, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, PubSubPushError, type PubSubPushMessage, type PubSubPushReason, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RecurrenceRule, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SIGNED_URI_RULES, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SignedRequestLine, type SignedUriRule, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyJwtOptions, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, decodeDelivery, decodeFormFieldJson, decodePubSubPush, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isOidcJwtScheme, isOidcJwtSpec, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, joinTargets, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, nextOccurrence, normaliseSignedUri, parseInvite, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, secretKeyBytes, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifyJwt, verifyOidcDelivery, verifySharedToken, withResolvedLimit };
3015
+ export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type Attendee, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, CURSOR_READ_REASONS, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, CursorReadError, type CursorReadReason, type CursorReadResult, type CursorReadSpec, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, DELIVERY_DECODE_KINDS, type DeliveryDeclaration, type DeliveryDecode, type DeliveryMechanism, type DeliveryOutcome, type DeliveryRoute, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, FORM_FIELD_JSON_REASONS, type FailureKind, type FakeRequest, type FakeValues, FormFieldJsonError, type FormFieldJsonReason, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type Invite, type InviteMethod, JWT_ALGORITHMS, type JoinTarget, type JoinTargetKind, type Jwk, type JwtVerification, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, OIDC_JWT_SCHEME_KIND, type OidcExpectation, type OidcJwtScheme, type OidcJwtVerificationSpec, PROBLEM_SEVERITIES, PUBSUB_PUSH_REASONS, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, PubSubPushError, type PubSubPushMessage, type PubSubPushReason, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RecurrenceRule, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SIGNED_URI_RULES, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SignedRequestLine, type SignedUriRule, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyJwtOptions, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, decodeDelivery, decodeFormFieldJson, decodePubSubPush, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isOidcJwtScheme, isOidcJwtSpec, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, joinTargets, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, nextOccurrence, normaliseSignedUri, parseInvite, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, readWithCursor, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, secretKeyBytes, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifyJwt, verifyOidcDelivery, verifySharedToken, withResolvedLimit };
package/dist/index.d.ts CHANGED
@@ -916,6 +916,72 @@ type ConnectorResult<T = unknown> = {
916
916
  declare function registerTransport(next: Transport | null): void;
917
917
  declare function callConnector<T = unknown>(service: ServiceDescriptor, options: CallOptions): Promise<ConnectorResult<T>>;
918
918
 
919
+ /**
920
+ * A read that resumes from a HOST-HELD cursor, and pages.
921
+ *
922
+ * Gmail's push and Drive's channel notifications carry no changes: they say
923
+ * "something moved", and the follow-up read fetches what did, starting from
924
+ * the position the HOST stored last time (Gmail's `startHistoryId`, Drive's
925
+ * `pageToken`). Reading from the delivery's own position instead misses the
926
+ * very change that caused it. The package holds no state: the host stores the
927
+ * cursor, passes it in, and persists `cursor.next` when the run succeeds.
928
+ *
929
+ * The PHP twin is `CursorRead::read`; both read `fixtures/cursor-read/`.
930
+ *
931
+ * The decisions, each pinned by a case:
932
+ *
933
+ * - **`next` comes ONLY from the LAST page.** Gmail's `historyId` is the
934
+ * mailbox's CURRENT one on every page, and Google says to store it only "if
935
+ * you receive no nextPageToken". Taking it after page one would skip every
936
+ * page not yet read, silently.
937
+ * - **Too many pages is refused** (`cursor-overflow`), never a partial read
938
+ * with a cursor that skips the rest. The host's answer is a bounded resync.
939
+ * - **The provider's own "cursor out of range"** (`expiredStatus`, Gmail's
940
+ * 404) is `cursor-expired`; every OTHER failure is rethrown unchanged, so a
941
+ * 500 is never mistaken for a lapse.
942
+ * - **No stored cursor is `cursor-missing`**, and the provider is not called:
943
+ * a read with no starting point is a read from the beginning of time.
944
+ * - `collect` absent is `[]` (Gmail omits `history` when nothing changed); not
945
+ * a list is `bad-page`. The cursor and `next` are strings: history ids are
946
+ * uint64.
947
+ */
948
+ declare const CURSOR_READ_REASONS: readonly ["cursor-missing", "cursor-expired", "cursor-overflow", "bad-page"];
949
+ type CursorReadReason = (typeof CURSOR_READ_REASONS)[number];
950
+ type CursorReadSpec = {
951
+ /** The read's parameter the stored cursor fills (Gmail: `startHistoryId`). */
952
+ param: string;
953
+ /** Where the NEXT cursor is read in the last page's data (Gmail: `historyId`). */
954
+ advanceFrom: string;
955
+ /** Paging, when the read pages (Gmail: `pageToken` / `nextPageToken`, `history`). */
956
+ page?: {
957
+ param: string;
958
+ from: string;
959
+ collect: string;
960
+ maxPages: number;
961
+ };
962
+ /** The status the provider answers for a cursor out of range (Gmail: 404). */
963
+ expiredStatus?: number;
964
+ };
965
+ type CursorReadResult = {
966
+ data: unknown;
967
+ cursor: {
968
+ used: string;
969
+ next: string | null;
970
+ };
971
+ };
972
+ /** Refused BY NAME; `reason` is the same string PHP's exception carries. */
973
+ declare class CursorReadError extends Error {
974
+ readonly reason: CursorReadReason;
975
+ /** The host's bounded resync, when the caller declared one (Gmail: `newer_than:7d`). */
976
+ readonly resyncQuery?: string | undefined;
977
+ constructor(reason: CursorReadReason, message: string,
978
+ /** The host's bounded resync, when the caller declared one (Gmail: `newer_than:7d`). */
979
+ resyncQuery?: string | undefined);
980
+ }
981
+ declare function readWithCursor(spec: CursorReadSpec, cursor: unknown, call: (params: Record<string, string>) => Promise<unknown>, options?: {
982
+ resyncQuery?: string;
983
+ }): Promise<CursorReadResult>;
984
+
919
985
  /**
920
986
  * API drift — being told the provider changed, rather than discovering it.
921
987
  *
@@ -2946,4 +3012,4 @@ oidc?: OidcExpectation): Promise<WebhookVerification>;
2946
3012
  */
2947
3013
  declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
2948
3014
 
2949
- export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type Attendee, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, DELIVERY_DECODE_KINDS, type DeliveryDeclaration, type DeliveryDecode, type DeliveryMechanism, type DeliveryOutcome, type DeliveryRoute, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, FORM_FIELD_JSON_REASONS, type FailureKind, type FakeRequest, type FakeValues, FormFieldJsonError, type FormFieldJsonReason, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type Invite, type InviteMethod, JWT_ALGORITHMS, type JoinTarget, type JoinTargetKind, type Jwk, type JwtVerification, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, OIDC_JWT_SCHEME_KIND, type OidcExpectation, type OidcJwtScheme, type OidcJwtVerificationSpec, PROBLEM_SEVERITIES, PUBSUB_PUSH_REASONS, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, PubSubPushError, type PubSubPushMessage, type PubSubPushReason, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RecurrenceRule, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SIGNED_URI_RULES, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SignedRequestLine, type SignedUriRule, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyJwtOptions, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, decodeDelivery, decodeFormFieldJson, decodePubSubPush, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isOidcJwtScheme, isOidcJwtSpec, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, joinTargets, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, nextOccurrence, normaliseSignedUri, parseInvite, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, secretKeyBytes, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifyJwt, verifyOidcDelivery, verifySharedToken, withResolvedLimit };
3015
+ export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type Attendee, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, CURSOR_READ_REASONS, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, CursorReadError, type CursorReadReason, type CursorReadResult, type CursorReadSpec, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, DELIVERY_DECODE_KINDS, type DeliveryDeclaration, type DeliveryDecode, type DeliveryMechanism, type DeliveryOutcome, type DeliveryRoute, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, FORM_FIELD_JSON_REASONS, type FailureKind, type FakeRequest, type FakeValues, FormFieldJsonError, type FormFieldJsonReason, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type Invite, type InviteMethod, JWT_ALGORITHMS, type JoinTarget, type JoinTargetKind, type Jwk, type JwtVerification, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, OIDC_JWT_SCHEME_KIND, type OidcExpectation, type OidcJwtScheme, type OidcJwtVerificationSpec, PROBLEM_SEVERITIES, PUBSUB_PUSH_REASONS, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, PubSubPushError, type PubSubPushMessage, type PubSubPushReason, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RecurrenceRule, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SIGNED_URI_RULES, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SignedRequestLine, type SignedUriRule, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyJwtOptions, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, decodeDelivery, decodeFormFieldJson, decodePubSubPush, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isOidcJwtScheme, isOidcJwtSpec, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, joinTargets, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, nextOccurrence, normaliseSignedUri, parseInvite, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, readWithCursor, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, secretKeyBytes, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifyJwt, verifyOidcDelivery, verifySharedToken, withResolvedLimit };
package/dist/index.js CHANGED
@@ -163,10 +163,10 @@ var lastCallAt = /* @__PURE__ */ new Map();
163
163
  async function respectRate(channel, minIntervalMs, now = () => Date.now(), sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
164
164
  if (minIntervalMs <= 0) return { waitedMs: 0 };
165
165
  const previous = lastCallAt.get(channel);
166
- const at = now();
167
- const waitedMs = previous === void 0 ? 0 : Math.max(0, previous + minIntervalMs - at);
166
+ const at2 = now();
167
+ const waitedMs = previous === void 0 ? 0 : Math.max(0, previous + minIntervalMs - at2);
168
168
  if (waitedMs > 0) await sleep(waitedMs);
169
- lastCallAt.set(channel, at + waitedMs);
169
+ lastCallAt.set(channel, at2 + waitedMs);
170
170
  return { waitedMs };
171
171
  }
172
172
  function resetRateState() {
@@ -664,6 +664,71 @@ function readString(config, key) {
664
664
  return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
665
665
  }
666
666
 
667
+ // src/cursor-read.ts
668
+ var CURSOR_READ_REASONS = ["cursor-missing", "cursor-expired", "cursor-overflow", "bad-page"];
669
+ var CursorReadError = class extends Error {
670
+ constructor(reason, message, resyncQuery) {
671
+ super(message);
672
+ this.reason = reason;
673
+ this.resyncQuery = resyncQuery;
674
+ this.name = "CursorReadError";
675
+ }
676
+ reason;
677
+ resyncQuery;
678
+ };
679
+ async function readWithCursor(spec, cursor, call, options = {}) {
680
+ const refuse = (reason, message) => new CursorReadError(reason, message, options.resyncQuery);
681
+ const used = asCursor(cursor);
682
+ if (used === null) throw refuse("cursor-missing", `no stored ${spec.param}: the host seeds one before the first read`);
683
+ const collected = [];
684
+ let last;
685
+ let token = null;
686
+ let pages = 0;
687
+ for (; ; ) {
688
+ if (spec.page && pages === spec.page.maxPages) {
689
+ throw refuse("cursor-overflow", `more than ${spec.page.maxPages} pages since ${spec.param} ${used}`);
690
+ }
691
+ const params = { [spec.param]: used };
692
+ if (spec.page && token !== null) params[spec.page.param] = token;
693
+ try {
694
+ last = await call(params);
695
+ } catch (error) {
696
+ const status = error?.status;
697
+ if (spec.expiredStatus !== void 0 && status === spec.expiredStatus) {
698
+ throw refuse("cursor-expired", `${spec.param} ${used} is out of the provider's range (${status})`);
699
+ }
700
+ throw error;
701
+ }
702
+ pages++;
703
+ if (!spec.page) break;
704
+ const items = at(last, spec.page.collect);
705
+ if (items !== void 0 && !Array.isArray(items)) {
706
+ throw refuse("bad-page", `${spec.page.collect} is not a list`);
707
+ }
708
+ if (items) collected.push(...items);
709
+ token = asCursor(at(last, spec.page.from));
710
+ if (token === null) break;
711
+ }
712
+ const data = spec.page ? withList(last, spec.page.collect, collected) : last;
713
+ return { data, cursor: { used, next: asCursor(at(last, spec.advanceFrom)) } };
714
+ }
715
+ function asCursor(value) {
716
+ if (typeof value === "string") return value === "" ? null : value;
717
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
718
+ return null;
719
+ }
720
+ function at(value, path) {
721
+ return path.split(".").reduce(
722
+ (v, key) => v !== null && typeof v === "object" && !Array.isArray(v) ? v[key] : void 0,
723
+ value
724
+ );
725
+ }
726
+ function withList(data, path, list) {
727
+ const [head, ...rest] = path.split(".");
728
+ const base = data !== null && typeof data === "object" && !Array.isArray(data) ? data : {};
729
+ return { ...base, [head]: rest.length ? withList(base[head], rest.join("."), list) : list };
730
+ }
731
+
667
732
  // src/drift.ts
668
733
  function resolveRef(document, node, depth = 0) {
669
734
  if (depth > 8 || node === null || typeof node !== "object") return node;
@@ -1318,32 +1383,32 @@ function base64urlBytes(segment) {
1318
1383
  var EXPIRES_AT_UNITS = ["rfc3339", "epoch-ms"];
1319
1384
  var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
1320
1385
  var DEFAULT_CONTEXT = { service: "", operation: "subscription" };
1321
- function instantMs(value, field, at) {
1386
+ function instantMs(value, field, at2) {
1322
1387
  if (typeof value !== "string" || !INSTANT.test(value)) {
1323
1388
  throw new ConnectorConfigError(
1324
1389
  `${field} must be an RFC 3339 instant such as "2026-09-22T00:00:00Z", got ${JSON.stringify(value)}. A provider's own shape \u2014 Google's epoch milliseconds, say \u2014 is converted by the connector, never guessed at here.`,
1325
- at
1390
+ at2
1326
1391
  );
1327
1392
  }
1328
1393
  const ms = Date.parse(value.replace(/(\.\d{3})\d+/, "$1"));
1329
1394
  if (Number.isNaN(ms)) {
1330
- throw new ConnectorConfigError(`${field} is not a real instant: ${JSON.stringify(value)}`, at);
1395
+ throw new ConnectorConfigError(`${field} is not a real instant: ${JSON.stringify(value)}`, at2);
1331
1396
  }
1332
1397
  return ms;
1333
1398
  }
1334
- function leaseFromResponse(declaration, response, at = DEFAULT_CONTEXT) {
1399
+ function leaseFromResponse(declaration, response, at2 = DEFAULT_CONTEXT) {
1335
1400
  const { expiresAtFrom: path, expiresAtUnit: unit } = declaration;
1336
1401
  if (!EXPIRES_AT_UNITS.includes(unit)) {
1337
1402
  throw new ConnectorConfigError(
1338
1403
  `expiresAtUnit must be one of ${EXPIRES_AT_UNITS.join(" | ")}, got ${JSON.stringify(unit)}.`,
1339
- at
1404
+ at2
1340
1405
  );
1341
1406
  }
1342
1407
  const value = readPath(response, path);
1343
1408
  if (value === void 0 || value === null) {
1344
1409
  throw new ConnectorConfigError(
1345
1410
  `${path} is not present in the response, so no lease can be built \u2014 a subscription with no known expiry is one nobody renews.`,
1346
- at
1411
+ at2
1347
1412
  );
1348
1413
  }
1349
1414
  let expiresAt;
@@ -1352,7 +1417,7 @@ function leaseFromResponse(declaration, response, at = DEFAULT_CONTEXT) {
1352
1417
  if (!Number.isSafeInteger(ms)) {
1353
1418
  throw new ConnectorConfigError(
1354
1419
  `${path} must be epoch milliseconds as digits under "epoch-ms", got ${JSON.stringify(value)}.`,
1355
- at
1420
+ at2
1356
1421
  );
1357
1422
  }
1358
1423
  expiresAt = new Date(ms).toISOString();
@@ -1360,14 +1425,14 @@ function leaseFromResponse(declaration, response, at = DEFAULT_CONTEXT) {
1360
1425
  if (typeof value !== "string" || !INSTANT.test(value)) {
1361
1426
  throw new ConnectorConfigError(
1362
1427
  `${path} must be an RFC 3339 instant under "rfc3339", got ${JSON.stringify(value)}.`,
1363
- at
1428
+ at2
1364
1429
  );
1365
1430
  }
1366
- expiresAt = new Date(instantMs(value, path, at)).toISOString();
1431
+ expiresAt = new Date(instantMs(value, path, at2)).toISOString();
1367
1432
  }
1368
1433
  return subscriptionLease(
1369
1434
  { expiresAt, renewBeforeSeconds: declaration.renewBeforeSeconds, renewOperation: declaration.renewOperation },
1370
- at
1435
+ at2
1371
1436
  );
1372
1437
  }
1373
1438
  function readPath(value, path) {
@@ -1378,18 +1443,18 @@ function readPath(value, path) {
1378
1443
  }
1379
1444
  return current;
1380
1445
  }
1381
- function subscriptionLease(input, at = DEFAULT_CONTEXT) {
1382
- instantMs(input.expiresAt, "expiresAt", at);
1446
+ function subscriptionLease(input, at2 = DEFAULT_CONTEXT) {
1447
+ instantMs(input.expiresAt, "expiresAt", at2);
1383
1448
  if (!Number.isInteger(input.renewBeforeSeconds) || input.renewBeforeSeconds <= 0) {
1384
1449
  throw new ConnectorConfigError(
1385
1450
  `renewBeforeSeconds must be a positive whole number of seconds, got ${JSON.stringify(input.renewBeforeSeconds)}. Zero would make the lease due only at the instant it expires, which is a lease nobody renews.`,
1386
- at
1451
+ at2
1387
1452
  );
1388
1453
  }
1389
1454
  if (typeof input.renewOperation !== "string" || input.renewOperation.trim() === "") {
1390
1455
  throw new ConnectorConfigError(
1391
1456
  "renewOperation must name the operation the host calls when the lease is due; a due lease with nothing to call is one nobody renews.",
1392
- at
1457
+ at2
1393
1458
  );
1394
1459
  }
1395
1460
  return { expiresAt: input.expiresAt, renewBeforeSeconds: input.renewBeforeSeconds, renewOperation: input.renewOperation };
@@ -1398,10 +1463,10 @@ function leaseRenewAt(lease) {
1398
1463
  return new Date(instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT) - lease.renewBeforeSeconds * 1e3).toISOString();
1399
1464
  }
1400
1465
  function leaseState(lease, now) {
1401
- const at = now instanceof Date ? now.getTime() : instantMs(now, "now", DEFAULT_CONTEXT);
1466
+ const at2 = now instanceof Date ? now.getTime() : instantMs(now, "now", DEFAULT_CONTEXT);
1402
1467
  const expires = instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT);
1403
- if (at >= expires) return "expired";
1404
- if (at >= expires - lease.renewBeforeSeconds * 1e3) return "due";
1468
+ if (at2 >= expires) return "expired";
1469
+ if (at2 >= expires - lease.renewBeforeSeconds * 1e3) return "due";
1405
1470
  return "active";
1406
1471
  }
1407
1472
  function leaseAction(lease, now) {
@@ -1721,8 +1786,8 @@ function splitMultipart(body, boundary) {
1721
1786
  if (partStart >= 0 && partStart <= body.length) children.push(body.subarray(partStart));
1722
1787
  return children;
1723
1788
  }
1724
- function startsWith(haystack, at, needle) {
1725
- for (let k = 0; k < needle.length; k++) if (haystack[at + k] !== needle[k]) return false;
1789
+ function startsWith(haystack, at2, needle) {
1790
+ for (let k = 0; k < needle.length; k++) if (haystack[at2 + k] !== needle[k]) return false;
1726
1791
  return true;
1727
1792
  }
1728
1793
  function parseMime(raw) {
@@ -2363,6 +2428,7 @@ export {
2363
2428
  CONNECTOR_API_VERSION,
2364
2429
  CONNECTOR_MODES,
2365
2430
  CREDENTIAL_SCOPES,
2431
+ CURSOR_READ_REASONS,
2366
2432
  ConnectorAmbiguous,
2367
2433
  ConnectorApiMismatch,
2368
2434
  ConnectorAuthError,
@@ -2374,6 +2440,7 @@ export {
2374
2440
  ConnectorRequestError,
2375
2441
  ConnectorTransient,
2376
2442
  ConnectorUnreachable,
2443
+ CursorReadError,
2377
2444
  DEFAULT_IDEMPOTENCY_WINDOW_SECONDS,
2378
2445
  DEFAULT_RETRY,
2379
2446
  DELIVERY_DECODE_KINDS,
@@ -2442,6 +2509,7 @@ export {
2442
2509
  probe,
2443
2510
  providerProblems,
2444
2511
  providerSandboxIsSelectable,
2512
+ readWithCursor,
2445
2513
  registerConnectionHost,
2446
2514
  registerTransport,
2447
2515
  render,