@particle-academy/fancy-connector-core 0.6.2 → 0.7.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
@@ -1388,6 +1388,67 @@ declare function idempotencyKeyFor(ctx: unknown, nodeId: string, options?: Idemp
1388
1388
  */
1389
1389
  declare const NO_IDEMPOTENCY_KEY_WARNING: string;
1390
1390
 
1391
+ /**
1392
+ * A subscription that EXPIRES, and when the host must act on it.
1393
+ *
1394
+ * `DeliveryMechanism` has said since 0.1.0 that a `subscription` trigger is a
1395
+ * webhook somebody has to renew, forever, and that if nobody does the workflow
1396
+ * stops firing with no error anywhere. This is the value for that duty — ONE
1397
+ * shape every expiring trigger declares, so a host runs one renewal scheduler
1398
+ * rather than learning a loop per connector, each with its own boundary bugs.
1399
+ *
1400
+ * Three facts and two verbs:
1401
+ *
1402
+ * - `expiresAt` — the provider's expiry, as an RFC 3339 instant. Google Calendar
1403
+ * hands back `expiration` as epoch MILLISECONDS in a string and Microsoft
1404
+ * Graph hands back `expirationDateTime` as ISO 8601; the CONNECTOR converts on
1405
+ * the way in, and this module refuses anything that is not an instant rather
1406
+ * than guessing units.
1407
+ * - `renewBeforeSeconds` — the connector's margin. Positive, because zero would
1408
+ * make `due` unreachable (the moment it applied, `expired` would win) — a
1409
+ * lease nobody ever renews, declared in a way nothing would report.
1410
+ * - `renewOperation` — the operation the host calls when the lease is due. For
1411
+ * Graph that is a renew; for a Google Calendar channel, which cannot be
1412
+ * renewed, it is the create again (the connector stops the old channel
1413
+ * itself). The lease says WHEN; the connector says WHAT.
1414
+ *
1415
+ * - `leaseState` — where the lease IS: `active`, `due` (inclusive at renewAt),
1416
+ * `expired` (inclusive at expiresAt, and it wins over `due`: a lease that has
1417
+ * just expired cannot be renewed).
1418
+ * - `leaseAction` — what the host DOES: `none`, `renew`, or `resync`. A missed
1419
+ * lease is never a quiet re-create: notifications during the gap are gone, so
1420
+ * the host must re-list (sync token or full) AND re-subscribe.
1421
+ *
1422
+ * The boundaries are DECIDED, not measured, and they are pinned in
1423
+ * `fixtures/subscription-lease/cases.json`, which the PHP twin reads too.
1424
+ */
1425
+
1426
+ type SubscriptionLease = {
1427
+ /** The provider's expiry, an RFC 3339 instant. */
1428
+ expiresAt: string;
1429
+ /** How early the host renews, in seconds. Positive. */
1430
+ renewBeforeSeconds: number;
1431
+ /** The operation the host calls when the lease is due. */
1432
+ renewOperation: string;
1433
+ };
1434
+ type LeaseState = "active" | "due" | "expired";
1435
+ type LeaseAction = "none" | "renew" | "resync";
1436
+ /**
1437
+ * Validate a declaration and return the lease — a plain value a host stores
1438
+ * beside the trigger and hands back to `leaseState` / `leaseAction` later.
1439
+ */
1440
+ declare function subscriptionLease(input: {
1441
+ expiresAt: string;
1442
+ renewBeforeSeconds: number;
1443
+ renewOperation: string;
1444
+ }, at?: ConnectorErrorContext): SubscriptionLease;
1445
+ /** The instant the lease becomes due: `expiresAt - renewBeforeSeconds`. */
1446
+ declare function leaseRenewAt(lease: SubscriptionLease): string;
1447
+ /** Where the lease is at `now`. Both boundaries are inclusive; `expired` wins. */
1448
+ declare function leaseState(lease: SubscriptionLease, now: string | Date): LeaseState;
1449
+ /** What the host does about the lease at `now`. */
1450
+ declare function leaseAction(lease: SubscriptionLease, now: string | Date): LeaseAction;
1451
+
1391
1452
  /**
1392
1453
  * Measuring and slicing text the way providers actually measure and slice it.
1393
1454
  *
@@ -2058,6 +2119,58 @@ declare function providerProblems(provider: ProviderAdapter): string[];
2058
2119
  /** True when a provider's declared sandbox can actually be selected at run time. */
2059
2120
  declare function providerSandboxIsSelectable(provider: ProviderAdapter): boolean;
2060
2121
 
2122
+ /**
2123
+ * Raw MIME in, headers / parts / attachments out — the payload TRANSFORM an
2124
+ * inbound-email trigger declares.
2125
+ *
2126
+ * Written from scratch (the owner: no third-party code), as ONE implementation
2127
+ * in two runtimes — `php/src/Mime.php` is the twin — and held to the authored
2128
+ * corpus under `fixtures/mime/`, which both must reproduce byte for byte.
2129
+ * `fixtures/mime/README.md` lists every decision a case pins; the ones that
2130
+ * are easy to get differently in two languages are restated here:
2131
+ *
2132
+ * - Header lines are read as Latin-1 (one byte, one char), unfolded with a
2133
+ * single space, names lowercased, values RFC 2047-decoded (B and Q; adjacent
2134
+ * encoded-words join without the whitespace between them).
2135
+ * - The line break before a boundary delimiter belongs to the DELIMITER.
2136
+ * - `size` is the transfer-decoded byte length, before any charset conversion.
2137
+ * - A text part comes back as UTF-8 with the message's own line endings. A
2138
+ * charset this module does not decode (anything but utf-8, us-ascii,
2139
+ * iso-8859-1, iso-8859-15, windows-1252) leaves the part with no text.
2140
+ * - Every non-text leaf is an attachment, as is a text leaf declared
2141
+ * `attachment`; attachments carry their bytes as base64. `filename*` (RFC
2142
+ * 2231) wins over `filename`, which wins over Content-Type's `name`.
2143
+ * - `messageId` is the header verbatim; `contentId` has its angle brackets
2144
+ * stripped, because `cid:` references omit them.
2145
+ */
2146
+ type MimeHeader = {
2147
+ name: string;
2148
+ value: string;
2149
+ };
2150
+ type MimePart = {
2151
+ index: number;
2152
+ contentType: string;
2153
+ charset: string | null;
2154
+ disposition: "inline" | "attachment" | null;
2155
+ filename: string | null;
2156
+ contentId: string | null;
2157
+ size: number;
2158
+ headers: MimeHeader[];
2159
+ };
2160
+ type MimeAttachment = MimePart & {
2161
+ content: string;
2162
+ };
2163
+ type MimeMessage = {
2164
+ headers: MimeHeader[];
2165
+ subject: string | null;
2166
+ messageId: string | null;
2167
+ text: string | null;
2168
+ html: string | null;
2169
+ parts: MimePart[];
2170
+ attachments: MimeAttachment[];
2171
+ };
2172
+ declare function parseMime(raw: Uint8Array | string): MimeMessage;
2173
+
2061
2174
  /**
2062
2175
  * Dry-verify — proving the transport with a credential that cannot work.
2063
2176
  *
@@ -2323,4 +2436,4 @@ declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDel
2323
2436
  */
2324
2437
  declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
2325
2438
 
2326
- export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, 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 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, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SpecSource, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isUnconditionallyRetryable, linkRanges, measure, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, withResolvedLimit };
2439
+ export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, 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 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, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type LeaseAction, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isUnconditionallyRetryable, leaseAction, leaseRenewAt, leaseState, linkRanges, measure, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, withResolvedLimit };
package/dist/index.d.ts CHANGED
@@ -1388,6 +1388,67 @@ declare function idempotencyKeyFor(ctx: unknown, nodeId: string, options?: Idemp
1388
1388
  */
1389
1389
  declare const NO_IDEMPOTENCY_KEY_WARNING: string;
1390
1390
 
1391
+ /**
1392
+ * A subscription that EXPIRES, and when the host must act on it.
1393
+ *
1394
+ * `DeliveryMechanism` has said since 0.1.0 that a `subscription` trigger is a
1395
+ * webhook somebody has to renew, forever, and that if nobody does the workflow
1396
+ * stops firing with no error anywhere. This is the value for that duty — ONE
1397
+ * shape every expiring trigger declares, so a host runs one renewal scheduler
1398
+ * rather than learning a loop per connector, each with its own boundary bugs.
1399
+ *
1400
+ * Three facts and two verbs:
1401
+ *
1402
+ * - `expiresAt` — the provider's expiry, as an RFC 3339 instant. Google Calendar
1403
+ * hands back `expiration` as epoch MILLISECONDS in a string and Microsoft
1404
+ * Graph hands back `expirationDateTime` as ISO 8601; the CONNECTOR converts on
1405
+ * the way in, and this module refuses anything that is not an instant rather
1406
+ * than guessing units.
1407
+ * - `renewBeforeSeconds` — the connector's margin. Positive, because zero would
1408
+ * make `due` unreachable (the moment it applied, `expired` would win) — a
1409
+ * lease nobody ever renews, declared in a way nothing would report.
1410
+ * - `renewOperation` — the operation the host calls when the lease is due. For
1411
+ * Graph that is a renew; for a Google Calendar channel, which cannot be
1412
+ * renewed, it is the create again (the connector stops the old channel
1413
+ * itself). The lease says WHEN; the connector says WHAT.
1414
+ *
1415
+ * - `leaseState` — where the lease IS: `active`, `due` (inclusive at renewAt),
1416
+ * `expired` (inclusive at expiresAt, and it wins over `due`: a lease that has
1417
+ * just expired cannot be renewed).
1418
+ * - `leaseAction` — what the host DOES: `none`, `renew`, or `resync`. A missed
1419
+ * lease is never a quiet re-create: notifications during the gap are gone, so
1420
+ * the host must re-list (sync token or full) AND re-subscribe.
1421
+ *
1422
+ * The boundaries are DECIDED, not measured, and they are pinned in
1423
+ * `fixtures/subscription-lease/cases.json`, which the PHP twin reads too.
1424
+ */
1425
+
1426
+ type SubscriptionLease = {
1427
+ /** The provider's expiry, an RFC 3339 instant. */
1428
+ expiresAt: string;
1429
+ /** How early the host renews, in seconds. Positive. */
1430
+ renewBeforeSeconds: number;
1431
+ /** The operation the host calls when the lease is due. */
1432
+ renewOperation: string;
1433
+ };
1434
+ type LeaseState = "active" | "due" | "expired";
1435
+ type LeaseAction = "none" | "renew" | "resync";
1436
+ /**
1437
+ * Validate a declaration and return the lease — a plain value a host stores
1438
+ * beside the trigger and hands back to `leaseState` / `leaseAction` later.
1439
+ */
1440
+ declare function subscriptionLease(input: {
1441
+ expiresAt: string;
1442
+ renewBeforeSeconds: number;
1443
+ renewOperation: string;
1444
+ }, at?: ConnectorErrorContext): SubscriptionLease;
1445
+ /** The instant the lease becomes due: `expiresAt - renewBeforeSeconds`. */
1446
+ declare function leaseRenewAt(lease: SubscriptionLease): string;
1447
+ /** Where the lease is at `now`. Both boundaries are inclusive; `expired` wins. */
1448
+ declare function leaseState(lease: SubscriptionLease, now: string | Date): LeaseState;
1449
+ /** What the host does about the lease at `now`. */
1450
+ declare function leaseAction(lease: SubscriptionLease, now: string | Date): LeaseAction;
1451
+
1391
1452
  /**
1392
1453
  * Measuring and slicing text the way providers actually measure and slice it.
1393
1454
  *
@@ -2058,6 +2119,58 @@ declare function providerProblems(provider: ProviderAdapter): string[];
2058
2119
  /** True when a provider's declared sandbox can actually be selected at run time. */
2059
2120
  declare function providerSandboxIsSelectable(provider: ProviderAdapter): boolean;
2060
2121
 
2122
+ /**
2123
+ * Raw MIME in, headers / parts / attachments out — the payload TRANSFORM an
2124
+ * inbound-email trigger declares.
2125
+ *
2126
+ * Written from scratch (the owner: no third-party code), as ONE implementation
2127
+ * in two runtimes — `php/src/Mime.php` is the twin — and held to the authored
2128
+ * corpus under `fixtures/mime/`, which both must reproduce byte for byte.
2129
+ * `fixtures/mime/README.md` lists every decision a case pins; the ones that
2130
+ * are easy to get differently in two languages are restated here:
2131
+ *
2132
+ * - Header lines are read as Latin-1 (one byte, one char), unfolded with a
2133
+ * single space, names lowercased, values RFC 2047-decoded (B and Q; adjacent
2134
+ * encoded-words join without the whitespace between them).
2135
+ * - The line break before a boundary delimiter belongs to the DELIMITER.
2136
+ * - `size` is the transfer-decoded byte length, before any charset conversion.
2137
+ * - A text part comes back as UTF-8 with the message's own line endings. A
2138
+ * charset this module does not decode (anything but utf-8, us-ascii,
2139
+ * iso-8859-1, iso-8859-15, windows-1252) leaves the part with no text.
2140
+ * - Every non-text leaf is an attachment, as is a text leaf declared
2141
+ * `attachment`; attachments carry their bytes as base64. `filename*` (RFC
2142
+ * 2231) wins over `filename`, which wins over Content-Type's `name`.
2143
+ * - `messageId` is the header verbatim; `contentId` has its angle brackets
2144
+ * stripped, because `cid:` references omit them.
2145
+ */
2146
+ type MimeHeader = {
2147
+ name: string;
2148
+ value: string;
2149
+ };
2150
+ type MimePart = {
2151
+ index: number;
2152
+ contentType: string;
2153
+ charset: string | null;
2154
+ disposition: "inline" | "attachment" | null;
2155
+ filename: string | null;
2156
+ contentId: string | null;
2157
+ size: number;
2158
+ headers: MimeHeader[];
2159
+ };
2160
+ type MimeAttachment = MimePart & {
2161
+ content: string;
2162
+ };
2163
+ type MimeMessage = {
2164
+ headers: MimeHeader[];
2165
+ subject: string | null;
2166
+ messageId: string | null;
2167
+ text: string | null;
2168
+ html: string | null;
2169
+ parts: MimePart[];
2170
+ attachments: MimeAttachment[];
2171
+ };
2172
+ declare function parseMime(raw: Uint8Array | string): MimeMessage;
2173
+
2061
2174
  /**
2062
2175
  * Dry-verify — proving the transport with a credential that cannot work.
2063
2176
  *
@@ -2323,4 +2436,4 @@ declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDel
2323
2436
  */
2324
2437
  declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
2325
2438
 
2326
- export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, 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 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, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SpecSource, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isUnconditionallyRetryable, linkRanges, measure, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, withResolvedLimit };
2439
+ export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, 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 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, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type LeaseAction, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isUnconditionallyRetryable, leaseAction, leaseRenewAt, leaseState, linkRanges, measure, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, withResolvedLimit };
package/dist/index.js CHANGED
@@ -595,21 +595,21 @@ async function prepare(service, connection, options) {
595
595
  }
596
596
  function encodeForm(form) {
597
597
  const pairs = [];
598
- const walk = (prefix, value) => {
598
+ const walk2 = (prefix, value) => {
599
599
  if (value === void 0 || value === null) return;
600
600
  if (Array.isArray(value)) {
601
- value.forEach((item, index) => walk(`${prefix}[${index}]`, item));
601
+ value.forEach((item, index) => walk2(`${prefix}[${index}]`, item));
602
602
  return;
603
603
  }
604
604
  if (typeof value === "object") {
605
605
  for (const [key, nested] of Object.entries(value)) {
606
- walk(prefix === "" ? key : `${prefix}[${key}]`, nested);
606
+ walk2(prefix === "" ? key : `${prefix}[${key}]`, nested);
607
607
  }
608
608
  return;
609
609
  }
610
610
  pairs.push(`${encodeURIComponent(prefix)}=${encodeURIComponent(String(value))}`);
611
611
  };
612
- walk("", form);
612
+ walk2("", form);
613
613
  return pairs.join("&");
614
614
  }
615
615
  var fetchTransport = async (request) => {
@@ -913,6 +913,59 @@ function fnv1a(value) {
913
913
  return hash.toString(16).padStart(8, "0");
914
914
  }
915
915
 
916
+ // src/lease.ts
917
+ var INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
918
+ var DEFAULT_CONTEXT = { service: "", operation: "subscription" };
919
+ function instantMs(value, field, at) {
920
+ if (typeof value !== "string" || !INSTANT.test(value)) {
921
+ throw new ConnectorConfigError(
922
+ `${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.`,
923
+ at
924
+ );
925
+ }
926
+ const ms = Date.parse(value);
927
+ if (Number.isNaN(ms)) {
928
+ throw new ConnectorConfigError(`${field} is not a real instant: ${JSON.stringify(value)}`, at);
929
+ }
930
+ return ms;
931
+ }
932
+ function subscriptionLease(input, at = DEFAULT_CONTEXT) {
933
+ instantMs(input.expiresAt, "expiresAt", at);
934
+ if (!Number.isInteger(input.renewBeforeSeconds) || input.renewBeforeSeconds <= 0) {
935
+ throw new ConnectorConfigError(
936
+ `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.`,
937
+ at
938
+ );
939
+ }
940
+ if (typeof input.renewOperation !== "string" || input.renewOperation.trim() === "") {
941
+ throw new ConnectorConfigError(
942
+ "renewOperation must name the operation the host calls when the lease is due; a due lease with nothing to call is one nobody renews.",
943
+ at
944
+ );
945
+ }
946
+ return { expiresAt: input.expiresAt, renewBeforeSeconds: input.renewBeforeSeconds, renewOperation: input.renewOperation };
947
+ }
948
+ function leaseRenewAt(lease) {
949
+ return new Date(instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT) - lease.renewBeforeSeconds * 1e3).toISOString();
950
+ }
951
+ function leaseState(lease, now) {
952
+ const at = now instanceof Date ? now.getTime() : instantMs(now, "now", DEFAULT_CONTEXT);
953
+ const expires = instantMs(lease.expiresAt, "expiresAt", DEFAULT_CONTEXT);
954
+ if (at >= expires) return "expired";
955
+ if (at >= expires - lease.renewBeforeSeconds * 1e3) return "due";
956
+ return "active";
957
+ }
958
+ function leaseAction(lease, now) {
959
+ switch (leaseState(lease, now)) {
960
+ case "active":
961
+ return "none";
962
+ case "due":
963
+ return "renew";
964
+ case "expired":
965
+ return "resync";
966
+ }
967
+ }
968
+
916
969
  // src/metrics.ts
917
970
  function compareShape(connectorId, declared, produced) {
918
971
  const declaredKeys = new Set((declared ?? []).map((metric) => metric.key));
@@ -1007,6 +1060,245 @@ function providerSandboxIsSelectable(provider) {
1007
1060
  return sandboxIsSelectable(provider.sandbox);
1008
1061
  }
1009
1062
 
1063
+ // src/mime.ts
1064
+ var CR = 13;
1065
+ var LF = 10;
1066
+ function latin1(bytes) {
1067
+ let s = "";
1068
+ for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
1069
+ return s;
1070
+ }
1071
+ function bytesOfLatin1(s) {
1072
+ const out = new Uint8Array(s.length);
1073
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
1074
+ return out;
1075
+ }
1076
+ function splitEntity(bytes) {
1077
+ for (let i = 0; i < bytes.length; i++) {
1078
+ if (bytes[i] === LF) {
1079
+ if (bytes[i + 1] === LF) return [i, i + 2];
1080
+ if (bytes[i + 1] === CR && bytes[i + 2] === LF) return [i, i + 3];
1081
+ }
1082
+ }
1083
+ return [bytes.length, bytes.length];
1084
+ }
1085
+ function parseHeaders(block) {
1086
+ const lines = block.split(/\r?\n/);
1087
+ const unfolded = [];
1088
+ for (const line of lines) {
1089
+ if (line === "") continue;
1090
+ if ((line.startsWith(" ") || line.startsWith(" ")) && unfolded.length > 0) {
1091
+ unfolded[unfolded.length - 1] += " " + line.trim();
1092
+ } else {
1093
+ unfolded.push(line);
1094
+ }
1095
+ }
1096
+ const headers = [];
1097
+ for (const line of unfolded) {
1098
+ const colon = line.indexOf(":");
1099
+ if (colon <= 0) continue;
1100
+ headers.push({ name: line.slice(0, colon).trim().toLowerCase(), value: decodeWords(line.slice(colon + 1).trim()) });
1101
+ }
1102
+ return headers;
1103
+ }
1104
+ function header(headers, name) {
1105
+ return headers.find((h) => h.name === name)?.value ?? null;
1106
+ }
1107
+ var ENCODED_WORD = /=\?([^?\s]+)\?([BbQq])\?([^?\s]*)\?=/g;
1108
+ function decodeWords(value) {
1109
+ const joined = value.replace(/(\?=)[ \t\r\n]+(=\?)/g, "$1$2");
1110
+ return joined.replace(ENCODED_WORD, (whole, charset, encoding, text) => {
1111
+ const bytes = encoding.toUpperCase() === "B" ? base64Bytes(text) : bytesOfLatin1(text.replace(/_/g, " ").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16))));
1112
+ const decoded = decodeCharset(bytes, charset);
1113
+ return decoded ?? whole;
1114
+ });
1115
+ }
1116
+ function parseParams(value) {
1117
+ const [head, ...rest] = value.split(";");
1118
+ const params = {};
1119
+ const continued = {};
1120
+ for (const piece of rest) {
1121
+ const eq = piece.indexOf("=");
1122
+ if (eq < 0) continue;
1123
+ let key = piece.slice(0, eq).trim().toLowerCase();
1124
+ let raw = piece.slice(eq + 1).trim();
1125
+ if (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) raw = raw.slice(1, -1).replace(/\\(.)/g, "$1");
1126
+ const segment = /^(.+?)\*(\d+)(\*)?$/.exec(key);
1127
+ if (segment) {
1128
+ const [, base, n, star] = segment;
1129
+ (continued[base] ??= [])[Number(n)] = star ? decode2231(raw, Number(n) === 0) : raw;
1130
+ continue;
1131
+ }
1132
+ if (key.endsWith("*")) {
1133
+ key = key.slice(0, -1);
1134
+ params[key] = decode2231(raw, true);
1135
+ continue;
1136
+ }
1137
+ params[key] ??= raw;
1138
+ }
1139
+ for (const [base, parts] of Object.entries(continued)) params[base] = parts.join("");
1140
+ return { main: (head ?? "").trim().toLowerCase(), params };
1141
+ }
1142
+ var pendingCharset2231 = "utf-8";
1143
+ function decode2231(raw, first) {
1144
+ let text = raw;
1145
+ if (first) {
1146
+ const m = /^([^']*)'[^']*'(.*)$/s.exec(raw);
1147
+ if (m) {
1148
+ pendingCharset2231 = m[1] || "utf-8";
1149
+ text = m[2];
1150
+ }
1151
+ }
1152
+ const bytes = bytesOfLatin1(text.replace(/%([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16))));
1153
+ return decodeCharset(bytes, pendingCharset2231) ?? text;
1154
+ }
1155
+ function base64Bytes(text) {
1156
+ return new Uint8Array(Buffer.from(text.replace(/[^A-Za-z0-9+/=]/g, ""), "base64"));
1157
+ }
1158
+ function quotedPrintableBytes(bytes) {
1159
+ const out = [];
1160
+ for (let i = 0; i < bytes.length; i++) {
1161
+ const b = bytes[i];
1162
+ if (b !== 61) {
1163
+ out.push(b);
1164
+ continue;
1165
+ }
1166
+ const a = bytes[i + 1];
1167
+ const c = bytes[i + 2];
1168
+ if (a === LF) {
1169
+ i += 1;
1170
+ continue;
1171
+ }
1172
+ if (a === CR && c === LF) {
1173
+ i += 2;
1174
+ continue;
1175
+ }
1176
+ if (a !== void 0 && c !== void 0 && isHex(a) && isHex(c)) {
1177
+ out.push(parseInt(String.fromCharCode(a, c), 16));
1178
+ i += 2;
1179
+ continue;
1180
+ }
1181
+ out.push(b);
1182
+ }
1183
+ return new Uint8Array(out);
1184
+ }
1185
+ function isHex(b) {
1186
+ return b >= 48 && b <= 57 || b >= 65 && b <= 70 || b >= 97 && b <= 102;
1187
+ }
1188
+ function transferDecode(body, encoding) {
1189
+ switch ((encoding ?? "7bit").trim().toLowerCase()) {
1190
+ case "base64":
1191
+ return base64Bytes(latin1(body));
1192
+ case "quoted-printable":
1193
+ return quotedPrintableBytes(body);
1194
+ default:
1195
+ return body;
1196
+ }
1197
+ }
1198
+ function decodeCharset(bytes, charset) {
1199
+ switch (charset.trim().toLowerCase()) {
1200
+ case "utf-8":
1201
+ case "utf8":
1202
+ return new TextDecoder("utf-8").decode(bytes);
1203
+ case "us-ascii":
1204
+ case "ascii":
1205
+ case "iso-8859-1":
1206
+ case "latin1":
1207
+ return Buffer.from(bytes).toString("latin1");
1208
+ case "windows-1252":
1209
+ case "cp1252":
1210
+ return new TextDecoder("windows-1252").decode(bytes);
1211
+ case "iso-8859-15":
1212
+ case "latin9":
1213
+ return new TextDecoder("iso-8859-15").decode(bytes);
1214
+ default:
1215
+ return null;
1216
+ }
1217
+ }
1218
+ function walk(bytes, entityHeaders, acc) {
1219
+ const [headerEnd, bodyStart] = splitEntity(bytes);
1220
+ const headers = entityHeaders ?? parseHeaders(latin1(bytes.subarray(0, headerEnd)));
1221
+ const body = bytes.subarray(entityHeaders ? 0 : bodyStart);
1222
+ const type = parseParams(header(headers, "content-type") ?? "text/plain");
1223
+ if (type.main.startsWith("multipart/") && type.params.boundary) {
1224
+ for (const child of splitMultipart(body, type.params.boundary)) walk(child, null, acc);
1225
+ return;
1226
+ }
1227
+ const disposition = parseParams(header(headers, "content-disposition") ?? "");
1228
+ const decoded = transferDecode(body, header(headers, "content-transfer-encoding"));
1229
+ const declaredDisposition = disposition.main === "attachment" || disposition.main === "inline" ? disposition.main : null;
1230
+ const filename = disposition.params.filename ?? type.params.name ?? null;
1231
+ const contentId = header(headers, "content-id")?.replace(/^<|>$/g, "") ?? null;
1232
+ const isText = type.main.startsWith("text/");
1233
+ const isAttachment = declaredDisposition === "attachment" || !isText;
1234
+ const part = {
1235
+ index: acc.parts.length,
1236
+ contentType: type.main,
1237
+ charset: type.params.charset?.toLowerCase() ?? null,
1238
+ disposition: declaredDisposition,
1239
+ filename,
1240
+ contentId,
1241
+ size: decoded.length,
1242
+ headers
1243
+ };
1244
+ acc.parts.push(part);
1245
+ if (isAttachment) {
1246
+ acc.attachments.push({ ...part, content: Buffer.from(decoded).toString("base64") });
1247
+ return;
1248
+ }
1249
+ const text = decodeCharset(decoded, type.params.charset ?? "utf-8");
1250
+ if (text === null) return;
1251
+ if (type.main === "text/plain" && acc.text === null) acc.text = text;
1252
+ if (type.main === "text/html" && acc.html === null) acc.html = text;
1253
+ }
1254
+ function splitMultipart(body, boundary) {
1255
+ const delimiter = bytesOfLatin1(`--${boundary}`);
1256
+ const children = [];
1257
+ let partStart = -1;
1258
+ for (let i = 0; i <= body.length - delimiter.length; i++) {
1259
+ if (i !== 0 && body[i - 1] !== LF) continue;
1260
+ if (!startsWith(body, i, delimiter)) continue;
1261
+ let partEnd = i;
1262
+ if (partEnd > 0 && body[partEnd - 1] === LF) partEnd -= 1;
1263
+ if (partEnd > 0 && body[partEnd - 1] === CR) partEnd -= 1;
1264
+ if (partStart >= 0) children.push(body.subarray(partStart, partEnd));
1265
+ let j = i + delimiter.length;
1266
+ const closing = body[j] === 45 && body[j + 1] === 45;
1267
+ if (closing) return children;
1268
+ while (j < body.length && body[j] !== LF) j++;
1269
+ partStart = j + 1;
1270
+ i = j;
1271
+ }
1272
+ if (partStart >= 0 && partStart <= body.length) children.push(body.subarray(partStart));
1273
+ return children;
1274
+ }
1275
+ function startsWith(haystack, at, needle) {
1276
+ for (let k = 0; k < needle.length; k++) if (haystack[at + k] !== needle[k]) return false;
1277
+ return true;
1278
+ }
1279
+ function parseMime(raw) {
1280
+ const bytes = typeof raw === "string" ? new Uint8Array(Buffer.from(raw, "utf8")) : raw;
1281
+ const [headerEnd, bodyStart] = splitEntity(bytes);
1282
+ const headers = parseHeaders(latin1(bytes.subarray(0, headerEnd)));
1283
+ const body = bytes.subarray(bodyStart);
1284
+ const acc = { parts: [], attachments: [], text: null, html: null };
1285
+ const type = parseParams(header(headers, "content-type") ?? "text/plain");
1286
+ if (type.main.startsWith("multipart/") && type.params.boundary) {
1287
+ for (const child of splitMultipart(body, type.params.boundary)) walk(child, null, acc);
1288
+ } else {
1289
+ walk(body, headers.filter((h) => h.name.startsWith("content-")), acc);
1290
+ }
1291
+ return {
1292
+ headers,
1293
+ subject: header(headers, "subject"),
1294
+ messageId: header(headers, "message-id"),
1295
+ text: acc.text,
1296
+ html: acc.html,
1297
+ parts: acc.parts,
1298
+ attachments: acc.attachments
1299
+ };
1300
+ }
1301
+
1010
1302
  // src/probe.ts
1011
1303
  async function probe(spec) {
1012
1304
  let response;
@@ -1266,7 +1558,7 @@ function constantTimeEquals(a, b) {
1266
1558
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1267
1559
  return diff === 0;
1268
1560
  }
1269
- function header(headers, name) {
1561
+ function header2(headers, name) {
1270
1562
  const wanted = name.toLowerCase();
1271
1563
  for (const [key, value] of Object.entries(headers)) {
1272
1564
  if (key.toLowerCase() !== wanted) continue;
@@ -1284,9 +1576,9 @@ async function verifyDelivery(trigger, delivery, secret, now) {
1284
1576
  reason: `${trigger.service}.${trigger.operation} declares no signature scheme, so a delivery cannot be trusted.`
1285
1577
  };
1286
1578
  }
1287
- const rawHeader = header(delivery.headers, spec.signatureHeader);
1579
+ const rawHeader = header2(delivery.headers, spec.signatureHeader);
1288
1580
  const parsed = spec.parse && rawHeader ? spec.parse(rawHeader) : { signature: rawHeader };
1289
- const timestamp = parsed.timestamp ?? (spec.timestampHeader ? header(delivery.headers, spec.timestampHeader) : void 0);
1581
+ const timestamp = parsed.timestamp ?? (spec.timestampHeader ? header2(delivery.headers, spec.timestampHeader) : void 0);
1290
1582
  return verifyHmac({
1291
1583
  raw: delivery.raw,
1292
1584
  signature: parsed.signature,
@@ -1359,15 +1651,19 @@ export {
1359
1651
  fakeValues,
1360
1652
  getConnectionHost,
1361
1653
  graphemes,
1362
- header,
1654
+ header2 as header,
1363
1655
  hmac,
1364
1656
  httpFailure,
1365
1657
  idempotencyKeyFor,
1366
1658
  isEmptyPayload,
1367
1659
  isRemote,
1368
1660
  isUnconditionallyRetryable,
1661
+ leaseAction,
1662
+ leaseRenewAt,
1663
+ leaseState,
1369
1664
  linkRanges,
1370
1665
  measure,
1666
+ parseMime,
1371
1667
  payloadHash,
1372
1668
  postChain,
1373
1669
  probe,
@@ -1392,6 +1688,7 @@ export {
1392
1688
  shouldRetry,
1393
1689
  sliceByteRange,
1394
1690
  splitToFit,
1691
+ subscriptionLease,
1395
1692
  triggerEvent,
1396
1693
  unchecked,
1397
1694
  utf8Length,