@secondlayer/sdk 12.2.0 → 12.3.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.ts +39 -3
- package/dist/index.js +285 -10
- package/dist/index.js.map +3 -3
- package/dist/webhooks/doctor.d.ts +39 -3
- package/dist/webhooks/doctor.js +285 -10
- package/dist/webhooks/doctor.js.map +3 -3
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -2800,17 +2800,40 @@ declare function getSubgraph<T extends {
|
|
|
2800
2800
|
}>(def: T, options?: Partial<SecondLayerOptions> | SecondLayer | Subgraphs): InferSubgraphClient2<T>;
|
|
2801
2801
|
import { SubgraphAgentSchema as SubgraphAgentSchema3, SubgraphSpecFormat as SubgraphSpecFormat2, SubgraphSpecOptions as SubgraphSpecOptions3 } from "@secondlayer/shared/subgraphs/spec";
|
|
2802
2802
|
import { SubgraphDetail as SubgraphDetail2 } from "@secondlayer/shared/schemas/subgraphs";
|
|
2803
|
-
import { DeadRow as DeadRow3, DeliveryRow as DeliveryRow3, WebhookDetail as WebhookDetail3 } from "@secondlayer/shared/schemas/webhooks";
|
|
2803
|
+
import { DeadRow as DeadRow3, DeliveryRow as DeliveryRow3, WebhookDetail as WebhookDetail3, WebhookSummary as WebhookSummary3 } from "@secondlayer/shared/schemas/webhooks";
|
|
2804
2804
|
/**
|
|
2805
2805
|
* Webhook diagnosis, shared by `secondlayer webhooks doctor` and the
|
|
2806
2806
|
* dashboard's webhook detail page. Moved out of the CLI (unchanged behavior)
|
|
2807
2807
|
* so the web can build the same judgment without printing CLI-command text.
|
|
2808
|
+
*
|
|
2809
|
+
* Plan 068 adds five deterministic, numbers-only detectors on top of the
|
|
2810
|
+
* existing flag checks: no model, no history beyond the deliveries window
|
|
2811
|
+
* already fetched. Thresholds are named constants below, conservative on
|
|
2812
|
+
* purpose — a wrong insight costs more trust than a missed one.
|
|
2808
2813
|
*/
|
|
2809
2814
|
declare function isSuccessDelivery(row: DeliveryRow3): boolean;
|
|
2810
|
-
type
|
|
2815
|
+
type DoctorSeverity = "bad" | "warn" | "calm";
|
|
2816
|
+
interface DoctorEvidence {
|
|
2817
|
+
label: string;
|
|
2818
|
+
value: string;
|
|
2819
|
+
}
|
|
2820
|
+
interface DoctorFix {
|
|
2821
|
+
text: string;
|
|
2822
|
+
/** A `secondlayer webhooks ...` command the reader can copy. */
|
|
2823
|
+
command?: string;
|
|
2824
|
+
/** Anchor into the docs' webhooks troubleshooting section. */
|
|
2825
|
+
docsPath?: string;
|
|
2826
|
+
}
|
|
2827
|
+
type DoctorIssueCode = "warning" | "paused" | "last_error" | "circuit" | "dead_letters" | "subgraph_gaps" | "subgraph_catching_up" | "no_deliveries" | "receiver_rate_limited" | "receiver_down" | "receiver_rejects" | "receiver_slow" | "delivery_lag";
|
|
2811
2828
|
interface DoctorIssue {
|
|
2812
2829
|
code: DoctorIssueCode;
|
|
2813
2830
|
detail?: string;
|
|
2831
|
+
severity: DoctorSeverity;
|
|
2832
|
+
/** 2-4 numbers-only facts backing the issue. Only the five new detectors
|
|
2833
|
+
* and the list-summary circuit issue carry this — the older flag checks
|
|
2834
|
+
* keep their plain `detail` string. */
|
|
2835
|
+
evidence?: DoctorEvidence[];
|
|
2836
|
+
fix?: DoctorFix;
|
|
2814
2837
|
}
|
|
2815
2838
|
interface DoctorReport {
|
|
2816
2839
|
webhook: WebhookDetail3;
|
|
@@ -2833,6 +2856,10 @@ interface DoctorReport {
|
|
|
2833
2856
|
/** CLI-facing next steps, one per issue, in the same order as `issues`. */
|
|
2834
2857
|
hints: string[];
|
|
2835
2858
|
issues: DoctorIssue[];
|
|
2859
|
+
/** The single most severe issue (bad > warn > calm, then rule priority),
|
|
2860
|
+
* or `null` when there are none. The UI leads with this and collapses
|
|
2861
|
+
* the rest underneath. */
|
|
2862
|
+
primary: DoctorIssue | null;
|
|
2836
2863
|
}
|
|
2837
2864
|
declare function buildDoctorReport(input: {
|
|
2838
2865
|
webhook: WebhookDetail3;
|
|
@@ -2841,6 +2868,15 @@ declare function buildDoctorReport(input: {
|
|
|
2841
2868
|
subgraph?: SubgraphDetail2 | null;
|
|
2842
2869
|
}): DoctorReport;
|
|
2843
2870
|
/**
|
|
2871
|
+
* The list page's own reduced detector: computed from `WebhookSummary` alone
|
|
2872
|
+
* (no deliveries, no dead-letter rows — the list endpoint returns neither,
|
|
2873
|
+
* and fetching per-row detail or deliveries here would fan out one request
|
|
2874
|
+
* per row). Only `status` and `circuitOpenedAt` distinguish a real problem
|
|
2875
|
+
* from a manual pause at this level; deliveries-based rules (rate limits,
|
|
2876
|
+
* downtime, rejects, slowness, lag) only ever show on the detail page.
|
|
2877
|
+
*/
|
|
2878
|
+
declare function buildListIssue(webhook: Pick<WebhookSummary3, "status" | "circuitOpenedAt" | "lastSuccessAt">): DoctorIssue | null;
|
|
2879
|
+
/**
|
|
2844
2880
|
* The two pieces every "deploy a consumer" guide leaves out, packaged:
|
|
2845
2881
|
* a liveness endpoint and a graceful-shutdown signal. Both are
|
|
2846
2882
|
* runtime-agnostic — the SDK never binds a port or assumes Bun/Node.
|
|
@@ -3269,4 +3305,4 @@ declare const Cursor: {
|
|
|
3269
3305
|
};
|
|
3270
3306
|
};
|
|
3271
3307
|
import { decodeClarityValue, toJsonSafe } from "@secondlayer/shared/streams-rows";
|
|
3272
|
-
export { ACCOUNT_KEY_ENV, ActiveSubgraphOperation, ApiError, ArchiveAuthError, ArchiveClient, ArchiveCreditsBalance, ArchiveDataset, ArchiveFetchItem, ArchiveFetchResult, ArchiveFlow, ArchiveGateNotConfiguredError, ArchiveLoadOptions, ArchiveManifest, ArchivePartition, ArchiveQuote, ArchiveSignatureError, ArchiveStatus, ArchiveVerifyInput, ArchiveVerifyRangeStatus, ArchiveVerifyResult, AuthError, BlockEnvelope, BlocksEnvelope, BlocksListParams, BlocksWalkParams, CanonicalEnvelope, CanonicalListParams, CanonicalWalkParams, ChainApplyDeliveryOf, ChainApplyEnvelope, ChainApplyEnvelopeOf, ChainEventEnvelope, ChainFtBurnData, ChainFtMintData, ChainFtTransferData, ChainNftBurnData, ChainNftMintData, ChainNftTransferData, ChainPrintEventData, ChainReorgOrphanedEntry, ChainReorgRollbackDelivery, ChainReorgRollbackEnvelope, ChainStxBurnData, ChainStxLockData, ChainStxMintData, ChainStxTransferData, ChainTestDelivery, ChainTrigger, ChainTriggerType, ChainTxLevelEvent, ChainWebhookDelivery2 as ChainWebhookDelivery, ChainWebhookEnvelope, ConsumerBatchContext, ConsumerHealth, ConsumerSink, ContextAccount, ContextField, ContextFieldError, ContextSnapshot, ContractCallsConsumeParams, ContractCallsEnvelope, ContractCallsListParams, ContractCallsWalkParams, ContractConformance, ContractSummary, Contracts, ContractsEnvelope, ContractsListParams, CreateArchiveClientOptions, CreateWebhookRequest2 as CreateWebhookRequest, CreateWebhookResponse2 as CreateWebhookResponse, Cursor, DEFAULT_REQUEST_TIMEOUT_MS, DeadRow2 as DeadRow, DecodedEventColumns, DecodedEventRow, DecodedFtBurn, DecodedFtBurnPayload, DecodedFtMint, DecodedFtMintPayload, DecodedFtTransfer, DecodedFtTransferPayload, DecodedNftBurn, DecodedNftBurnPayload, DecodedNftMint, DecodedNftMintPayload, DecodedNftTransfer, DecodedNftTransferPayload, DecodedPrint, DecodedPrintPayload, DecodedPrintValue, DecodedStxBurn, DecodedStxBurnPayload, DecodedStxLock, DecodedStxLockPayload, DecodedStxMint, DecodedStxMintPayload, DecodedStxTransfer, DecodedStxTransferPayload, DeliveryRow2 as DeliveryRow, DoctorIssue, DoctorIssueCode, DoctorReport, EtagCache, EtagEntry, EventsConsumeParams, EventsEnvelope, EventsListParams, EventsWalkParams, FetchLike2 as FetchLike, FtTransfer, FtTransferEvent, FtTransferPayload2 as FtTransferPayload, FtTransfersEnvelope, FtTransfersListParams, FtTransfersResource, FtTransfersWalkParams, INDEX_MAX_PAGE_SIZE, INSTANCE_TOKEN_ENV, Index, IndexAlwaysFields, IndexBlock, IndexCanonicalBlock, IndexConsumeOptions, IndexContractCall, IndexDiscovery, IndexEvent, IndexEventFields, IndexEventOf, IndexEventType, IndexEventTypeFilters, IndexEventsResource, IndexFeedEnvelope, IndexFeedFetcher, IndexFeedItem, IndexFtBurn, IndexFtMint, IndexFtTransfer, IndexMapDelete, IndexMapInsert, IndexMapSet, IndexMempoolTransaction, IndexNestedContractCall, IndexNftBurn, IndexNftMint, IndexNftTransfer, IndexPostCondition, IndexPox5Event, IndexPox5EventTopic, IndexPoxCycle, IndexPoxFunctionCount, IndexPrint, IndexReorg, IndexSbtcDeposit, IndexSbtcDepositDetail, IndexSbtcEvent, IndexSbtcSummary, IndexSbtcWithdrawal, IndexSbtcWithdrawalDetail, IndexSbtcWithdrawalPhase, IndexStackingAction, IndexStxBurn, IndexStxLock, IndexStxMint, IndexStxTransfer, IndexTip, IndexTransaction, IndexVarSet, InstanceClient, InstanceDiagnosis3 as InstanceDiagnosis, InstanceIssue, InstanceState, InstanceStatus, InsufficientArchiveCreditsError, LOCAL_API_URL, LoadedArchive, MemoryEtagCache, MempoolEnvelope, MempoolListParams, MempoolTransactionEnvelope, MempoolWalkParams, NftTransfer, NftTransferEvent, NftTransferPayload2 as NftTransferPayload, NftTransfersEnvelope, NftTransfersListParams, NftTransfersResource, NftTransfersWalkParams, Pox5EventsEnvelope, Pox5EventsListParams, Pox5EventsWalkParams, Pox5Resource, PoxCycleEnvelope, PoxCyclesEnvelope, PoxCyclesListParams, PoxCyclesWalkParams, PoxResource, PrintSchemaResponse, PublicStatus2 as PublicStatus, RateLimitError, ReplayResult2 as ReplayResult, RequestOptions, RewardSet2 as RewardSet, RotateSecretResponse2 as RotateSecretResponse, SbtcDepositEnvelope, SbtcDepositEvent, SbtcDepositsConsumeParams, SbtcDepositsEnvelope, SbtcDepositsListParams, SbtcDepositsWalkParams, SbtcEventTopic2 as SbtcEventTopic, SbtcEventsConsumeParams, SbtcEventsEnvelope, SbtcEventsListParams, SbtcEventsWalkParams, SbtcResource, SbtcSummaryEnvelope, SbtcWithdrawalEnvelope, SbtcWithdrawalEvent, SbtcWithdrawalStatus, SbtcWithdrawalSweptConfirmedEvent, SbtcWithdrawalsEnvelope, SbtcWithdrawalsListParams, SbtcWithdrawalsWalkParams, SecondLayer, SecondLayerArchive, SecondLayerError, SecondLayerErrorOptions, SecondLayerOptions, SinkTx, StackingEnvelope, StackingListParams, StackingWalkParams, StandardWebhooksHeaders2 as StandardWebhooksHeaders, StreamsBatch, StreamsBatchContext, StreamsCanonicalBlock, StreamsClient, StreamsConsumeParams, StreamsDumpFile, StreamsDumps, StreamsDumpsManifest, StreamsEvent, StreamsEventPayload, StreamsEventType, StreamsEventsConsumeParams, StreamsEventsConsumeResult, StreamsEventsEnvelope, StreamsEventsListEnvelope, StreamsEventsListParams, StreamsEventsStreamParams, StreamsEventsSubscribeParams, StreamsReorg, StreamsReorgContext, StreamsReorgsListEnvelope, StreamsReorgsListParams, StreamsServerError, StreamsSignatureError, StreamsSubscription, StreamsTip, StreamsWireEvent2 as StreamsWireEvent, SubgraphAgentSchema3 as SubgraphAgentSchema, SubgraphOperationStatus, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphSpecOptions3 as SubgraphSpecOptions, Subgraphs, TransactionEnvelope, TransactionProof, TransactionProofVerifyResult, TransactionsEnvelope, TransactionsListParams, TransactionsWalkParams, UpdateWebhookRequest2 as UpdateWebhookRequest, ValidationError, VmStreamsEvent, WalkOptions, WebhookDetail2 as WebhookDetail, WebhookFormat, WebhookHeaderInput, WebhookKind, WebhookRuntime, WebhookStatus, WebhookSummary2 as WebhookSummary, WebhookTestResult2 as WebhookTestResult, Webhooks, WithSinkTx, buildDoctorReport, consumeIndexFeed, consumerHealth, contextField, createArchiveClient, createStreamsClient, decode, decodeChainWebhook, decodeClarityValue, decodeFtBurn, decodeFtMint, decodeFtTransfer, decodeNftBurn, decodeNftMint, decodeNftTransfer, decodePrint, decodeStxBurn, decodeStxLock, decodeStxMint, decodeStxTransfer, fetchRewardSet, getSubgraph, isFtBurn, isFtMint, isFtTransfer, isNftBurn, isNftMint, isNftTransfer, isPrint, isStxBurn, isStxLock, isStxMint, isStxTransfer, isSuccessDelivery, parseRetryAfter, resolveAccountKey, resolveApiKey, resolveBaseUrl, shutdownSignal, toJsonSafe, trigger, verifySecondlayerSignature, verify as verifyStandardWebhooksHeaders, verifyTransactionProof, verifyWebhookSignature };
|
|
3308
|
+
export { ACCOUNT_KEY_ENV, ActiveSubgraphOperation, ApiError, ArchiveAuthError, ArchiveClient, ArchiveCreditsBalance, ArchiveDataset, ArchiveFetchItem, ArchiveFetchResult, ArchiveFlow, ArchiveGateNotConfiguredError, ArchiveLoadOptions, ArchiveManifest, ArchivePartition, ArchiveQuote, ArchiveSignatureError, ArchiveStatus, ArchiveVerifyInput, ArchiveVerifyRangeStatus, ArchiveVerifyResult, AuthError, BlockEnvelope, BlocksEnvelope, BlocksListParams, BlocksWalkParams, CanonicalEnvelope, CanonicalListParams, CanonicalWalkParams, ChainApplyDeliveryOf, ChainApplyEnvelope, ChainApplyEnvelopeOf, ChainEventEnvelope, ChainFtBurnData, ChainFtMintData, ChainFtTransferData, ChainNftBurnData, ChainNftMintData, ChainNftTransferData, ChainPrintEventData, ChainReorgOrphanedEntry, ChainReorgRollbackDelivery, ChainReorgRollbackEnvelope, ChainStxBurnData, ChainStxLockData, ChainStxMintData, ChainStxTransferData, ChainTestDelivery, ChainTrigger, ChainTriggerType, ChainTxLevelEvent, ChainWebhookDelivery2 as ChainWebhookDelivery, ChainWebhookEnvelope, ConsumerBatchContext, ConsumerHealth, ConsumerSink, ContextAccount, ContextField, ContextFieldError, ContextSnapshot, ContractCallsConsumeParams, ContractCallsEnvelope, ContractCallsListParams, ContractCallsWalkParams, ContractConformance, ContractSummary, Contracts, ContractsEnvelope, ContractsListParams, CreateArchiveClientOptions, CreateWebhookRequest2 as CreateWebhookRequest, CreateWebhookResponse2 as CreateWebhookResponse, Cursor, DEFAULT_REQUEST_TIMEOUT_MS, DeadRow2 as DeadRow, DecodedEventColumns, DecodedEventRow, DecodedFtBurn, DecodedFtBurnPayload, DecodedFtMint, DecodedFtMintPayload, DecodedFtTransfer, DecodedFtTransferPayload, DecodedNftBurn, DecodedNftBurnPayload, DecodedNftMint, DecodedNftMintPayload, DecodedNftTransfer, DecodedNftTransferPayload, DecodedPrint, DecodedPrintPayload, DecodedPrintValue, DecodedStxBurn, DecodedStxBurnPayload, DecodedStxLock, DecodedStxLockPayload, DecodedStxMint, DecodedStxMintPayload, DecodedStxTransfer, DecodedStxTransferPayload, DeliveryRow2 as DeliveryRow, DoctorEvidence, DoctorFix, DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSeverity, EtagCache, EtagEntry, EventsConsumeParams, EventsEnvelope, EventsListParams, EventsWalkParams, FetchLike2 as FetchLike, FtTransfer, FtTransferEvent, FtTransferPayload2 as FtTransferPayload, FtTransfersEnvelope, FtTransfersListParams, FtTransfersResource, FtTransfersWalkParams, INDEX_MAX_PAGE_SIZE, INSTANCE_TOKEN_ENV, Index, IndexAlwaysFields, IndexBlock, IndexCanonicalBlock, IndexConsumeOptions, IndexContractCall, IndexDiscovery, IndexEvent, IndexEventFields, IndexEventOf, IndexEventType, IndexEventTypeFilters, IndexEventsResource, IndexFeedEnvelope, IndexFeedFetcher, IndexFeedItem, IndexFtBurn, IndexFtMint, IndexFtTransfer, IndexMapDelete, IndexMapInsert, IndexMapSet, IndexMempoolTransaction, IndexNestedContractCall, IndexNftBurn, IndexNftMint, IndexNftTransfer, IndexPostCondition, IndexPox5Event, IndexPox5EventTopic, IndexPoxCycle, IndexPoxFunctionCount, IndexPrint, IndexReorg, IndexSbtcDeposit, IndexSbtcDepositDetail, IndexSbtcEvent, IndexSbtcSummary, IndexSbtcWithdrawal, IndexSbtcWithdrawalDetail, IndexSbtcWithdrawalPhase, IndexStackingAction, IndexStxBurn, IndexStxLock, IndexStxMint, IndexStxTransfer, IndexTip, IndexTransaction, IndexVarSet, InstanceClient, InstanceDiagnosis3 as InstanceDiagnosis, InstanceIssue, InstanceState, InstanceStatus, InsufficientArchiveCreditsError, LOCAL_API_URL, LoadedArchive, MemoryEtagCache, MempoolEnvelope, MempoolListParams, MempoolTransactionEnvelope, MempoolWalkParams, NftTransfer, NftTransferEvent, NftTransferPayload2 as NftTransferPayload, NftTransfersEnvelope, NftTransfersListParams, NftTransfersResource, NftTransfersWalkParams, Pox5EventsEnvelope, Pox5EventsListParams, Pox5EventsWalkParams, Pox5Resource, PoxCycleEnvelope, PoxCyclesEnvelope, PoxCyclesListParams, PoxCyclesWalkParams, PoxResource, PrintSchemaResponse, PublicStatus2 as PublicStatus, RateLimitError, ReplayResult2 as ReplayResult, RequestOptions, RewardSet2 as RewardSet, RotateSecretResponse2 as RotateSecretResponse, SbtcDepositEnvelope, SbtcDepositEvent, SbtcDepositsConsumeParams, SbtcDepositsEnvelope, SbtcDepositsListParams, SbtcDepositsWalkParams, SbtcEventTopic2 as SbtcEventTopic, SbtcEventsConsumeParams, SbtcEventsEnvelope, SbtcEventsListParams, SbtcEventsWalkParams, SbtcResource, SbtcSummaryEnvelope, SbtcWithdrawalEnvelope, SbtcWithdrawalEvent, SbtcWithdrawalStatus, SbtcWithdrawalSweptConfirmedEvent, SbtcWithdrawalsEnvelope, SbtcWithdrawalsListParams, SbtcWithdrawalsWalkParams, SecondLayer, SecondLayerArchive, SecondLayerError, SecondLayerErrorOptions, SecondLayerOptions, SinkTx, StackingEnvelope, StackingListParams, StackingWalkParams, StandardWebhooksHeaders2 as StandardWebhooksHeaders, StreamsBatch, StreamsBatchContext, StreamsCanonicalBlock, StreamsClient, StreamsConsumeParams, StreamsDumpFile, StreamsDumps, StreamsDumpsManifest, StreamsEvent, StreamsEventPayload, StreamsEventType, StreamsEventsConsumeParams, StreamsEventsConsumeResult, StreamsEventsEnvelope, StreamsEventsListEnvelope, StreamsEventsListParams, StreamsEventsStreamParams, StreamsEventsSubscribeParams, StreamsReorg, StreamsReorgContext, StreamsReorgsListEnvelope, StreamsReorgsListParams, StreamsServerError, StreamsSignatureError, StreamsSubscription, StreamsTip, StreamsWireEvent2 as StreamsWireEvent, SubgraphAgentSchema3 as SubgraphAgentSchema, SubgraphOperationStatus, SubgraphSpecFormat2 as SubgraphSpecFormat, SubgraphSpecOptions3 as SubgraphSpecOptions, Subgraphs, TransactionEnvelope, TransactionProof, TransactionProofVerifyResult, TransactionsEnvelope, TransactionsListParams, TransactionsWalkParams, UpdateWebhookRequest2 as UpdateWebhookRequest, ValidationError, VmStreamsEvent, WalkOptions, WebhookDetail2 as WebhookDetail, WebhookFormat, WebhookHeaderInput, WebhookKind, WebhookRuntime, WebhookStatus, WebhookSummary2 as WebhookSummary, WebhookTestResult2 as WebhookTestResult, Webhooks, WithSinkTx, buildDoctorReport, buildListIssue, consumeIndexFeed, consumerHealth, contextField, createArchiveClient, createStreamsClient, decode, decodeChainWebhook, decodeClarityValue, decodeFtBurn, decodeFtMint, decodeFtTransfer, decodeNftBurn, decodeNftMint, decodeNftTransfer, decodePrint, decodeStxBurn, decodeStxLock, decodeStxMint, decodeStxTransfer, fetchRewardSet, getSubgraph, isFtBurn, isFtMint, isFtTransfer, isNftBurn, isNftMint, isNftTransfer, isPrint, isStxBurn, isStxLock, isStxMint, isStxTransfer, isSuccessDelivery, parseRetryAfter, resolveAccountKey, resolveApiKey, resolveBaseUrl, shutdownSignal, toJsonSafe, trigger, verifySecondlayerSignature, verify as verifyStandardWebhooksHeaders, verifyTransactionProof, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -2674,6 +2674,229 @@ function getSubgraph(def, options = {}) {
|
|
|
2674
2674
|
function isSuccessDelivery(row) {
|
|
2675
2675
|
return row.statusCode !== null && row.statusCode >= 200 && row.statusCode < 300;
|
|
2676
2676
|
}
|
|
2677
|
+
var RATE_LIMITED_MIN_ATTEMPTS = 10;
|
|
2678
|
+
var RATE_LIMITED_MIN_RATIO = 0.5;
|
|
2679
|
+
var DOWN_MIN_CONSECUTIVE = 5;
|
|
2680
|
+
var REJECTS_MIN_CONSECUTIVE = 5;
|
|
2681
|
+
var SLOW_WINDOW = 20;
|
|
2682
|
+
var SLOW_MIN_SAMPLES = 5;
|
|
2683
|
+
var SLOW_TIMEOUT_RATIO = 0.5;
|
|
2684
|
+
var LAG_WINDOW = 20;
|
|
2685
|
+
var LAG_MIN_SAMPLES = 5;
|
|
2686
|
+
var LAG_THRESHOLD_MS = 60000;
|
|
2687
|
+
var ERROR_SNIPPET_MAX = 200;
|
|
2688
|
+
function median(values) {
|
|
2689
|
+
if (values.length === 0)
|
|
2690
|
+
return 0;
|
|
2691
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2692
|
+
return sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
2693
|
+
}
|
|
2694
|
+
function percentile(values, p) {
|
|
2695
|
+
if (values.length === 0)
|
|
2696
|
+
return 0;
|
|
2697
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2698
|
+
const idx = Math.min(sorted.length - 1, Math.floor(p * sorted.length));
|
|
2699
|
+
return sorted[idx] ?? 0;
|
|
2700
|
+
}
|
|
2701
|
+
function formatMs(ms) {
|
|
2702
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`;
|
|
2703
|
+
}
|
|
2704
|
+
function formatShortDate(iso) {
|
|
2705
|
+
return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
|
|
2706
|
+
}
|
|
2707
|
+
function consecutiveFromStart(rows, predicate) {
|
|
2708
|
+
const out = [];
|
|
2709
|
+
for (const row of rows) {
|
|
2710
|
+
if (!predicate(row))
|
|
2711
|
+
break;
|
|
2712
|
+
out.push(row);
|
|
2713
|
+
}
|
|
2714
|
+
return out;
|
|
2715
|
+
}
|
|
2716
|
+
function summarizeStatusBreakdown(rows) {
|
|
2717
|
+
const counts = new Map;
|
|
2718
|
+
for (const row of rows) {
|
|
2719
|
+
const key = row.statusCode === null ? "timeout/no response" : String(row.statusCode);
|
|
2720
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
2721
|
+
}
|
|
2722
|
+
return [...counts.entries()].map(([key, n]) => `${n}× ${key}`).join(", ");
|
|
2723
|
+
}
|
|
2724
|
+
var RETRY_AFTER_RE = /retry-after:?\s*(\d+)/i;
|
|
2725
|
+
function extractRetryAfterSeconds(rows) {
|
|
2726
|
+
for (const row of rows) {
|
|
2727
|
+
if (row.statusCode !== 429 || !row.errorMessage)
|
|
2728
|
+
continue;
|
|
2729
|
+
const match = row.errorMessage.match(RETRY_AFTER_RE);
|
|
2730
|
+
if (match?.[1])
|
|
2731
|
+
return match[1];
|
|
2732
|
+
}
|
|
2733
|
+
return null;
|
|
2734
|
+
}
|
|
2735
|
+
function detectReceiverRateLimited(webhook, deliveries) {
|
|
2736
|
+
if (deliveries.length < RATE_LIMITED_MIN_ATTEMPTS)
|
|
2737
|
+
return null;
|
|
2738
|
+
const count429 = deliveries.filter((d) => d.statusCode === 429).length;
|
|
2739
|
+
if (count429 / deliveries.length < RATE_LIMITED_MIN_RATIO)
|
|
2740
|
+
return null;
|
|
2741
|
+
const oldest = deliveries[deliveries.length - 1];
|
|
2742
|
+
const newest = deliveries[0];
|
|
2743
|
+
const evidence = [
|
|
2744
|
+
{
|
|
2745
|
+
label: "429 responses",
|
|
2746
|
+
value: `${count429} of ${deliveries.length} attempts`
|
|
2747
|
+
},
|
|
2748
|
+
{
|
|
2749
|
+
label: "window",
|
|
2750
|
+
value: oldest && newest ? `${formatShortDate(oldest.dispatchedAt)} → ${formatShortDate(newest.dispatchedAt)}` : "unknown"
|
|
2751
|
+
}
|
|
2752
|
+
];
|
|
2753
|
+
const retryAfter = extractRetryAfterSeconds(deliveries);
|
|
2754
|
+
if (retryAfter) {
|
|
2755
|
+
evidence.push({ label: "latest Retry-After", value: `${retryAfter}s` });
|
|
2756
|
+
}
|
|
2757
|
+
const suggestedConcurrency = Math.max(1, Math.floor(webhook.concurrency / 2));
|
|
2758
|
+
return {
|
|
2759
|
+
code: "receiver_rate_limited",
|
|
2760
|
+
severity: "warn",
|
|
2761
|
+
evidence,
|
|
2762
|
+
fix: {
|
|
2763
|
+
text: "Lower concurrency, or honor the Retry-After header on your receiver.",
|
|
2764
|
+
command: `secondlayer webhooks update ${webhook.id} --concurrency ${suggestedConcurrency}`,
|
|
2765
|
+
docsPath: "/docs/webhooks#receiver-rate-limited"
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
function detectReceiverDown(webhook, deliveries) {
|
|
2770
|
+
const streak = consecutiveFromStart(deliveries, (d) => d.statusCode === null || d.statusCode >= 500);
|
|
2771
|
+
if (streak.length < DOWN_MIN_CONSECUTIVE)
|
|
2772
|
+
return null;
|
|
2773
|
+
return {
|
|
2774
|
+
code: "receiver_down",
|
|
2775
|
+
severity: "bad",
|
|
2776
|
+
evidence: [
|
|
2777
|
+
{ label: "consecutive failures", value: String(streak.length) },
|
|
2778
|
+
{ label: "breakdown", value: summarizeStatusBreakdown(streak) },
|
|
2779
|
+
{
|
|
2780
|
+
label: "last success",
|
|
2781
|
+
value: webhook.lastSuccessAt ? formatShortDate(webhook.lastSuccessAt) : "never"
|
|
2782
|
+
}
|
|
2783
|
+
],
|
|
2784
|
+
fix: {
|
|
2785
|
+
text: "Check your receiver's health and logs, then send a test event.",
|
|
2786
|
+
command: `secondlayer webhooks test ${webhook.id}`,
|
|
2787
|
+
docsPath: "/docs/webhooks#receiver-down"
|
|
2788
|
+
}
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
function detectReceiverRejects(webhook, deliveries) {
|
|
2792
|
+
const streak = consecutiveFromStart(deliveries, (d) => d.statusCode !== null && d.statusCode >= 400 && d.statusCode < 500 && d.statusCode !== 429);
|
|
2793
|
+
if (streak.length < REJECTS_MIN_CONSECUTIVE)
|
|
2794
|
+
return null;
|
|
2795
|
+
const evidence = [
|
|
2796
|
+
{ label: "consecutive rejects", value: String(streak.length) },
|
|
2797
|
+
{ label: "breakdown", value: summarizeStatusBreakdown(streak) }
|
|
2798
|
+
];
|
|
2799
|
+
const withText = streak.find((d) => d.errorMessage || d.responseBody);
|
|
2800
|
+
const snippet = withText ? (withText.errorMessage ?? withText.responseBody ?? "").slice(0, ERROR_SNIPPET_MAX) : null;
|
|
2801
|
+
if (snippet)
|
|
2802
|
+
evidence.push({ label: "first error", value: snippet });
|
|
2803
|
+
return {
|
|
2804
|
+
code: "receiver_rejects",
|
|
2805
|
+
severity: "bad",
|
|
2806
|
+
evidence,
|
|
2807
|
+
fix: {
|
|
2808
|
+
text: "Check your receiver's auth config or signature verification.",
|
|
2809
|
+
command: `secondlayer webhooks test ${webhook.id}`,
|
|
2810
|
+
docsPath: "/docs/webhooks#receiver-rejects"
|
|
2811
|
+
}
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
function detectReceiverSlow(webhook, deliveries) {
|
|
2815
|
+
const durations = deliveries.slice(0, SLOW_WINDOW).map((d) => d.durationMs).filter((d) => d !== null);
|
|
2816
|
+
if (durations.length < SLOW_MIN_SAMPLES)
|
|
2817
|
+
return null;
|
|
2818
|
+
const med = median(durations);
|
|
2819
|
+
if (med < webhook.timeoutMs * SLOW_TIMEOUT_RATIO)
|
|
2820
|
+
return null;
|
|
2821
|
+
return {
|
|
2822
|
+
code: "receiver_slow",
|
|
2823
|
+
severity: "warn",
|
|
2824
|
+
evidence: [
|
|
2825
|
+
{ label: "median response time", value: formatMs(med) },
|
|
2826
|
+
{
|
|
2827
|
+
label: "p95 response time",
|
|
2828
|
+
value: formatMs(percentile(durations, 0.95))
|
|
2829
|
+
},
|
|
2830
|
+
{ label: "timeout", value: formatMs(webhook.timeoutMs) }
|
|
2831
|
+
],
|
|
2832
|
+
fix: {
|
|
2833
|
+
text: "Speed up your receiver, or raise the timeout (up to 30s hosted).",
|
|
2834
|
+
command: `secondlayer webhooks update ${webhook.id} --timeout-ms 30000`,
|
|
2835
|
+
docsPath: "/docs/webhooks#receiver-slow"
|
|
2836
|
+
}
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
function detectDeliveryLag(deliveries) {
|
|
2840
|
+
const window = deliveries.slice(0, LAG_WINDOW).filter((d) => d.blockTime !== null);
|
|
2841
|
+
if (window.length < LAG_MIN_SAMPLES)
|
|
2842
|
+
return null;
|
|
2843
|
+
const lags = window.map((d) => new Date(d.dispatchedAt).getTime() - new Date(d.blockTime).getTime());
|
|
2844
|
+
const med = median(lags);
|
|
2845
|
+
if (med <= LAG_THRESHOLD_MS)
|
|
2846
|
+
return null;
|
|
2847
|
+
const blockTimes = window.map((d) => d.blockTime).sort();
|
|
2848
|
+
const oldest = blockTimes[0];
|
|
2849
|
+
const newest = blockTimes[blockTimes.length - 1];
|
|
2850
|
+
return {
|
|
2851
|
+
code: "delivery_lag",
|
|
2852
|
+
severity: "warn",
|
|
2853
|
+
evidence: [
|
|
2854
|
+
{ label: "median lag", value: formatMs(med) },
|
|
2855
|
+
{
|
|
2856
|
+
label: "block time range",
|
|
2857
|
+
value: oldest && newest ? `${formatShortDate(oldest)} → ${formatShortDate(newest)}` : "unknown"
|
|
2858
|
+
}
|
|
2859
|
+
],
|
|
2860
|
+
fix: {
|
|
2861
|
+
text: "We're behind on delivery; no action needed on your end."
|
|
2862
|
+
}
|
|
2863
|
+
};
|
|
2864
|
+
}
|
|
2865
|
+
var SEVERITY_RANK = {
|
|
2866
|
+
bad: 0,
|
|
2867
|
+
warn: 1,
|
|
2868
|
+
calm: 2
|
|
2869
|
+
};
|
|
2870
|
+
var CODE_PRIORITY = [
|
|
2871
|
+
"circuit",
|
|
2872
|
+
"receiver_down",
|
|
2873
|
+
"receiver_rejects",
|
|
2874
|
+
"dead_letters",
|
|
2875
|
+
"paused",
|
|
2876
|
+
"last_error",
|
|
2877
|
+
"warning",
|
|
2878
|
+
"receiver_rate_limited",
|
|
2879
|
+
"receiver_slow",
|
|
2880
|
+
"delivery_lag",
|
|
2881
|
+
"no_deliveries",
|
|
2882
|
+
"subgraph_gaps",
|
|
2883
|
+
"subgraph_catching_up"
|
|
2884
|
+
];
|
|
2885
|
+
function pickPrimary(issues) {
|
|
2886
|
+
if (issues.length === 0)
|
|
2887
|
+
return null;
|
|
2888
|
+
return [...issues].sort((a, b) => {
|
|
2889
|
+
const sevDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
2890
|
+
if (sevDiff !== 0)
|
|
2891
|
+
return sevDiff;
|
|
2892
|
+
return CODE_PRIORITY.indexOf(a.code) - CODE_PRIORITY.indexOf(b.code);
|
|
2893
|
+
})[0] ?? null;
|
|
2894
|
+
}
|
|
2895
|
+
function hintFor(issue) {
|
|
2896
|
+
if (!issue.fix)
|
|
2897
|
+
return issue.detail ?? issue.code;
|
|
2898
|
+
return issue.fix.command ? `${issue.fix.text} ${issue.fix.command}` : issue.fix.text;
|
|
2899
|
+
}
|
|
2677
2900
|
function buildDoctorReport(input) {
|
|
2678
2901
|
const successful = input.deliveries.filter(isSuccessDelivery).length;
|
|
2679
2902
|
const failed = input.deliveries.length - successful;
|
|
@@ -2690,35 +2913,60 @@ function buildDoctorReport(input) {
|
|
|
2690
2913
|
const issues = [];
|
|
2691
2914
|
if (input.webhook.warning) {
|
|
2692
2915
|
hints.push(input.webhook.warning);
|
|
2693
|
-
issues.push({
|
|
2916
|
+
issues.push({
|
|
2917
|
+
code: "warning",
|
|
2918
|
+
detail: input.webhook.warning,
|
|
2919
|
+
severity: "warn"
|
|
2920
|
+
});
|
|
2694
2921
|
}
|
|
2695
2922
|
if (input.webhook.status === "paused") {
|
|
2696
2923
|
hints.push(`Resume when the receiver is healthy: secondlayer webhooks resume ${input.webhook.id}`);
|
|
2697
|
-
issues.push({ code: "paused" });
|
|
2924
|
+
issues.push({ code: "paused", severity: "warn" });
|
|
2698
2925
|
}
|
|
2699
2926
|
if (input.webhook.lastError) {
|
|
2700
2927
|
hints.push("Run secondlayer webhooks test to reproduce the receiver request.");
|
|
2701
|
-
issues.push({
|
|
2928
|
+
issues.push({
|
|
2929
|
+
code: "last_error",
|
|
2930
|
+
detail: input.webhook.lastError,
|
|
2931
|
+
severity: "warn"
|
|
2932
|
+
});
|
|
2702
2933
|
}
|
|
2703
2934
|
if (input.webhook.circuitOpenedAt || input.webhook.circuitFailures > 0) {
|
|
2704
2935
|
hints.push("Circuit breaker has failures; inspect receiver logs and delivery status codes.");
|
|
2705
|
-
issues.push({ code: "circuit" });
|
|
2936
|
+
issues.push({ code: "circuit", severity: "bad" });
|
|
2706
2937
|
}
|
|
2707
2938
|
if (input.dead.length > 0) {
|
|
2708
2939
|
hints.push(`Dead-letter rows exist; inspect with secondlayer webhooks dead ${input.webhook.id} and requeue selected rows.`);
|
|
2709
|
-
issues.push({ code: "dead_letters" });
|
|
2940
|
+
issues.push({ code: "dead_letters", severity: "bad" });
|
|
2710
2941
|
}
|
|
2711
2942
|
if (subgraph?.gapCount && subgraph.gapCount > 0) {
|
|
2712
2943
|
hints.push(`Linked subgraph has gaps; run secondlayer subgraphs gaps ${input.webhook.subgraphName}.`);
|
|
2713
|
-
issues.push({ code: "subgraph_gaps" });
|
|
2944
|
+
issues.push({ code: "subgraph_gaps", severity: "calm" });
|
|
2714
2945
|
}
|
|
2715
2946
|
if (subgraph?.syncStatus === "catching_up") {
|
|
2716
2947
|
hints.push("Linked subgraph is still catching up; new matching rows may arrive later.");
|
|
2717
|
-
issues.push({ code: "subgraph_catching_up" });
|
|
2948
|
+
issues.push({ code: "subgraph_catching_up", severity: "calm" });
|
|
2949
|
+
}
|
|
2950
|
+
for (const detector of [
|
|
2951
|
+
detectReceiverRateLimited,
|
|
2952
|
+
detectReceiverDown,
|
|
2953
|
+
detectReceiverRejects,
|
|
2954
|
+
detectReceiverSlow
|
|
2955
|
+
]) {
|
|
2956
|
+
const issue = detector(input.webhook, input.deliveries);
|
|
2957
|
+
if (issue) {
|
|
2958
|
+
issues.push(issue);
|
|
2959
|
+
hints.push(hintFor(issue));
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
const lagIssue = detectDeliveryLag(input.deliveries);
|
|
2963
|
+
if (lagIssue) {
|
|
2964
|
+
issues.push(lagIssue);
|
|
2965
|
+
hints.push(hintFor(lagIssue));
|
|
2718
2966
|
}
|
|
2719
2967
|
if (input.deliveries.length === 0) {
|
|
2720
2968
|
hints.push("No deliveries yet; confirm the table is receiving inserted rows that match the filter.");
|
|
2721
|
-
issues.push({ code: "no_deliveries" });
|
|
2969
|
+
issues.push({ code: "no_deliveries", severity: "calm" });
|
|
2722
2970
|
}
|
|
2723
2971
|
if (hints.length === 0) {
|
|
2724
2972
|
hints.push("No immediate action needed.");
|
|
@@ -2734,9 +2982,35 @@ function buildDoctorReport(input) {
|
|
|
2734
2982
|
deadCount: input.dead.length,
|
|
2735
2983
|
subgraph,
|
|
2736
2984
|
hints,
|
|
2737
|
-
issues
|
|
2985
|
+
issues,
|
|
2986
|
+
primary: pickPrimary(issues)
|
|
2738
2987
|
};
|
|
2739
2988
|
}
|
|
2989
|
+
function buildListIssue(webhook) {
|
|
2990
|
+
if (webhook.status !== "paused")
|
|
2991
|
+
return null;
|
|
2992
|
+
if (webhook.circuitOpenedAt) {
|
|
2993
|
+
return {
|
|
2994
|
+
code: "circuit",
|
|
2995
|
+
severity: "bad",
|
|
2996
|
+
evidence: [
|
|
2997
|
+
{
|
|
2998
|
+
label: "circuit opened",
|
|
2999
|
+
value: formatShortDate(webhook.circuitOpenedAt)
|
|
3000
|
+
},
|
|
3001
|
+
{
|
|
3002
|
+
label: "last success",
|
|
3003
|
+
value: webhook.lastSuccessAt ? formatShortDate(webhook.lastSuccessAt) : "never"
|
|
3004
|
+
}
|
|
3005
|
+
],
|
|
3006
|
+
fix: {
|
|
3007
|
+
text: "Check your receiver's health and logs, then send a test event.",
|
|
3008
|
+
docsPath: "/docs/webhooks#receiver-down"
|
|
3009
|
+
}
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
return { code: "paused", severity: "warn" };
|
|
3013
|
+
}
|
|
2740
3014
|
// src/consumer-ops.ts
|
|
2741
3015
|
function consumerHealth(options = {}) {
|
|
2742
3016
|
const staleAfterMs = options.staleAfterMs ?? 120000;
|
|
@@ -2990,6 +3264,7 @@ export {
|
|
|
2990
3264
|
ValidationError,
|
|
2991
3265
|
Webhooks,
|
|
2992
3266
|
buildDoctorReport,
|
|
3267
|
+
buildListIssue,
|
|
2993
3268
|
consumeIndexFeed,
|
|
2994
3269
|
consumerHealth,
|
|
2995
3270
|
contextField,
|
|
@@ -3036,5 +3311,5 @@ export {
|
|
|
3036
3311
|
verifyWebhookSignature
|
|
3037
3312
|
};
|
|
3038
3313
|
|
|
3039
|
-
//# debugId=
|
|
3314
|
+
//# debugId=17DA719CAEA1958964756E2164756E21
|
|
3040
3315
|
//# sourceMappingURL=index.js.map
|