@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.
@@ -1,15 +1,38 @@
1
1
  import { SubgraphDetail } from "@secondlayer/shared/schemas/subgraphs";
2
- import { DeadRow, DeliveryRow, WebhookDetail } from "@secondlayer/shared/schemas/webhooks";
2
+ import { DeadRow, DeliveryRow, WebhookDetail, WebhookSummary } from "@secondlayer/shared/schemas/webhooks";
3
3
  /**
4
4
  * Webhook diagnosis, shared by `secondlayer webhooks doctor` and the
5
5
  * dashboard's webhook detail page. Moved out of the CLI (unchanged behavior)
6
6
  * so the web can build the same judgment without printing CLI-command text.
7
+ *
8
+ * Plan 068 adds five deterministic, numbers-only detectors on top of the
9
+ * existing flag checks: no model, no history beyond the deliveries window
10
+ * already fetched. Thresholds are named constants below, conservative on
11
+ * purpose — a wrong insight costs more trust than a missed one.
7
12
  */
8
13
  declare function isSuccessDelivery(row: DeliveryRow): boolean;
9
- type DoctorIssueCode = "warning" | "paused" | "last_error" | "circuit" | "dead_letters" | "subgraph_gaps" | "subgraph_catching_up" | "no_deliveries";
14
+ type DoctorSeverity = "bad" | "warn" | "calm";
15
+ interface DoctorEvidence {
16
+ label: string;
17
+ value: string;
18
+ }
19
+ interface DoctorFix {
20
+ text: string;
21
+ /** A `secondlayer webhooks ...` command the reader can copy. */
22
+ command?: string;
23
+ /** Anchor into the docs' webhooks troubleshooting section. */
24
+ docsPath?: string;
25
+ }
26
+ 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";
10
27
  interface DoctorIssue {
11
28
  code: DoctorIssueCode;
12
29
  detail?: string;
30
+ severity: DoctorSeverity;
31
+ /** 2-4 numbers-only facts backing the issue. Only the five new detectors
32
+ * and the list-summary circuit issue carry this — the older flag checks
33
+ * keep their plain `detail` string. */
34
+ evidence?: DoctorEvidence[];
35
+ fix?: DoctorFix;
13
36
  }
14
37
  interface DoctorReport {
15
38
  webhook: WebhookDetail;
@@ -32,6 +55,10 @@ interface DoctorReport {
32
55
  /** CLI-facing next steps, one per issue, in the same order as `issues`. */
33
56
  hints: string[];
34
57
  issues: DoctorIssue[];
58
+ /** The single most severe issue (bad > warn > calm, then rule priority),
59
+ * or `null` when there are none. The UI leads with this and collapses
60
+ * the rest underneath. */
61
+ primary: DoctorIssue | null;
35
62
  }
36
63
  declare function buildDoctorReport(input: {
37
64
  webhook: WebhookDetail;
@@ -39,4 +66,13 @@ declare function buildDoctorReport(input: {
39
66
  dead: DeadRow[];
40
67
  subgraph?: SubgraphDetail | null;
41
68
  }): DoctorReport;
42
- export { DoctorIssue, DoctorIssueCode, DoctorReport, buildDoctorReport, isSuccessDelivery };
69
+ /**
70
+ * The list page's own reduced detector: computed from `WebhookSummary` alone
71
+ * (no deliveries, no dead-letter rows — the list endpoint returns neither,
72
+ * and fetching per-row detail or deliveries here would fan out one request
73
+ * per row). Only `status` and `circuitOpenedAt` distinguish a real problem
74
+ * from a manual pause at this level; deliveries-based rules (rate limits,
75
+ * downtime, rejects, slowness, lag) only ever show on the detail page.
76
+ */
77
+ declare function buildListIssue(webhook: Pick<WebhookSummary, "status" | "circuitOpenedAt" | "lastSuccessAt">): DoctorIssue | null;
78
+ export { DoctorEvidence, DoctorFix, DoctorIssue, DoctorIssueCode, DoctorReport, DoctorSeverity, buildDoctorReport, buildListIssue, isSuccessDelivery };
@@ -2,6 +2,229 @@
2
2
  function isSuccessDelivery(row) {
3
3
  return row.statusCode !== null && row.statusCode >= 200 && row.statusCode < 300;
4
4
  }
5
+ var RATE_LIMITED_MIN_ATTEMPTS = 10;
6
+ var RATE_LIMITED_MIN_RATIO = 0.5;
7
+ var DOWN_MIN_CONSECUTIVE = 5;
8
+ var REJECTS_MIN_CONSECUTIVE = 5;
9
+ var SLOW_WINDOW = 20;
10
+ var SLOW_MIN_SAMPLES = 5;
11
+ var SLOW_TIMEOUT_RATIO = 0.5;
12
+ var LAG_WINDOW = 20;
13
+ var LAG_MIN_SAMPLES = 5;
14
+ var LAG_THRESHOLD_MS = 60000;
15
+ var ERROR_SNIPPET_MAX = 200;
16
+ function median(values) {
17
+ if (values.length === 0)
18
+ return 0;
19
+ const sorted = [...values].sort((a, b) => a - b);
20
+ return sorted[Math.floor(sorted.length / 2)] ?? 0;
21
+ }
22
+ function percentile(values, p) {
23
+ if (values.length === 0)
24
+ return 0;
25
+ const sorted = [...values].sort((a, b) => a - b);
26
+ const idx = Math.min(sorted.length - 1, Math.floor(p * sorted.length));
27
+ return sorted[idx] ?? 0;
28
+ }
29
+ function formatMs(ms) {
30
+ return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`;
31
+ }
32
+ function formatShortDate(iso) {
33
+ return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
34
+ }
35
+ function consecutiveFromStart(rows, predicate) {
36
+ const out = [];
37
+ for (const row of rows) {
38
+ if (!predicate(row))
39
+ break;
40
+ out.push(row);
41
+ }
42
+ return out;
43
+ }
44
+ function summarizeStatusBreakdown(rows) {
45
+ const counts = new Map;
46
+ for (const row of rows) {
47
+ const key = row.statusCode === null ? "timeout/no response" : String(row.statusCode);
48
+ counts.set(key, (counts.get(key) ?? 0) + 1);
49
+ }
50
+ return [...counts.entries()].map(([key, n]) => `${n}× ${key}`).join(", ");
51
+ }
52
+ var RETRY_AFTER_RE = /retry-after:?\s*(\d+)/i;
53
+ function extractRetryAfterSeconds(rows) {
54
+ for (const row of rows) {
55
+ if (row.statusCode !== 429 || !row.errorMessage)
56
+ continue;
57
+ const match = row.errorMessage.match(RETRY_AFTER_RE);
58
+ if (match?.[1])
59
+ return match[1];
60
+ }
61
+ return null;
62
+ }
63
+ function detectReceiverRateLimited(webhook, deliveries) {
64
+ if (deliveries.length < RATE_LIMITED_MIN_ATTEMPTS)
65
+ return null;
66
+ const count429 = deliveries.filter((d) => d.statusCode === 429).length;
67
+ if (count429 / deliveries.length < RATE_LIMITED_MIN_RATIO)
68
+ return null;
69
+ const oldest = deliveries[deliveries.length - 1];
70
+ const newest = deliveries[0];
71
+ const evidence = [
72
+ {
73
+ label: "429 responses",
74
+ value: `${count429} of ${deliveries.length} attempts`
75
+ },
76
+ {
77
+ label: "window",
78
+ value: oldest && newest ? `${formatShortDate(oldest.dispatchedAt)} → ${formatShortDate(newest.dispatchedAt)}` : "unknown"
79
+ }
80
+ ];
81
+ const retryAfter = extractRetryAfterSeconds(deliveries);
82
+ if (retryAfter) {
83
+ evidence.push({ label: "latest Retry-After", value: `${retryAfter}s` });
84
+ }
85
+ const suggestedConcurrency = Math.max(1, Math.floor(webhook.concurrency / 2));
86
+ return {
87
+ code: "receiver_rate_limited",
88
+ severity: "warn",
89
+ evidence,
90
+ fix: {
91
+ text: "Lower concurrency, or honor the Retry-After header on your receiver.",
92
+ command: `secondlayer webhooks update ${webhook.id} --concurrency ${suggestedConcurrency}`,
93
+ docsPath: "/docs/webhooks#receiver-rate-limited"
94
+ }
95
+ };
96
+ }
97
+ function detectReceiverDown(webhook, deliveries) {
98
+ const streak = consecutiveFromStart(deliveries, (d) => d.statusCode === null || d.statusCode >= 500);
99
+ if (streak.length < DOWN_MIN_CONSECUTIVE)
100
+ return null;
101
+ return {
102
+ code: "receiver_down",
103
+ severity: "bad",
104
+ evidence: [
105
+ { label: "consecutive failures", value: String(streak.length) },
106
+ { label: "breakdown", value: summarizeStatusBreakdown(streak) },
107
+ {
108
+ label: "last success",
109
+ value: webhook.lastSuccessAt ? formatShortDate(webhook.lastSuccessAt) : "never"
110
+ }
111
+ ],
112
+ fix: {
113
+ text: "Check your receiver's health and logs, then send a test event.",
114
+ command: `secondlayer webhooks test ${webhook.id}`,
115
+ docsPath: "/docs/webhooks#receiver-down"
116
+ }
117
+ };
118
+ }
119
+ function detectReceiverRejects(webhook, deliveries) {
120
+ const streak = consecutiveFromStart(deliveries, (d) => d.statusCode !== null && d.statusCode >= 400 && d.statusCode < 500 && d.statusCode !== 429);
121
+ if (streak.length < REJECTS_MIN_CONSECUTIVE)
122
+ return null;
123
+ const evidence = [
124
+ { label: "consecutive rejects", value: String(streak.length) },
125
+ { label: "breakdown", value: summarizeStatusBreakdown(streak) }
126
+ ];
127
+ const withText = streak.find((d) => d.errorMessage || d.responseBody);
128
+ const snippet = withText ? (withText.errorMessage ?? withText.responseBody ?? "").slice(0, ERROR_SNIPPET_MAX) : null;
129
+ if (snippet)
130
+ evidence.push({ label: "first error", value: snippet });
131
+ return {
132
+ code: "receiver_rejects",
133
+ severity: "bad",
134
+ evidence,
135
+ fix: {
136
+ text: "Check your receiver's auth config or signature verification.",
137
+ command: `secondlayer webhooks test ${webhook.id}`,
138
+ docsPath: "/docs/webhooks#receiver-rejects"
139
+ }
140
+ };
141
+ }
142
+ function detectReceiverSlow(webhook, deliveries) {
143
+ const durations = deliveries.slice(0, SLOW_WINDOW).map((d) => d.durationMs).filter((d) => d !== null);
144
+ if (durations.length < SLOW_MIN_SAMPLES)
145
+ return null;
146
+ const med = median(durations);
147
+ if (med < webhook.timeoutMs * SLOW_TIMEOUT_RATIO)
148
+ return null;
149
+ return {
150
+ code: "receiver_slow",
151
+ severity: "warn",
152
+ evidence: [
153
+ { label: "median response time", value: formatMs(med) },
154
+ {
155
+ label: "p95 response time",
156
+ value: formatMs(percentile(durations, 0.95))
157
+ },
158
+ { label: "timeout", value: formatMs(webhook.timeoutMs) }
159
+ ],
160
+ fix: {
161
+ text: "Speed up your receiver, or raise the timeout (up to 30s hosted).",
162
+ command: `secondlayer webhooks update ${webhook.id} --timeout-ms 30000`,
163
+ docsPath: "/docs/webhooks#receiver-slow"
164
+ }
165
+ };
166
+ }
167
+ function detectDeliveryLag(deliveries) {
168
+ const window = deliveries.slice(0, LAG_WINDOW).filter((d) => d.blockTime !== null);
169
+ if (window.length < LAG_MIN_SAMPLES)
170
+ return null;
171
+ const lags = window.map((d) => new Date(d.dispatchedAt).getTime() - new Date(d.blockTime).getTime());
172
+ const med = median(lags);
173
+ if (med <= LAG_THRESHOLD_MS)
174
+ return null;
175
+ const blockTimes = window.map((d) => d.blockTime).sort();
176
+ const oldest = blockTimes[0];
177
+ const newest = blockTimes[blockTimes.length - 1];
178
+ return {
179
+ code: "delivery_lag",
180
+ severity: "warn",
181
+ evidence: [
182
+ { label: "median lag", value: formatMs(med) },
183
+ {
184
+ label: "block time range",
185
+ value: oldest && newest ? `${formatShortDate(oldest)} → ${formatShortDate(newest)}` : "unknown"
186
+ }
187
+ ],
188
+ fix: {
189
+ text: "We're behind on delivery; no action needed on your end."
190
+ }
191
+ };
192
+ }
193
+ var SEVERITY_RANK = {
194
+ bad: 0,
195
+ warn: 1,
196
+ calm: 2
197
+ };
198
+ var CODE_PRIORITY = [
199
+ "circuit",
200
+ "receiver_down",
201
+ "receiver_rejects",
202
+ "dead_letters",
203
+ "paused",
204
+ "last_error",
205
+ "warning",
206
+ "receiver_rate_limited",
207
+ "receiver_slow",
208
+ "delivery_lag",
209
+ "no_deliveries",
210
+ "subgraph_gaps",
211
+ "subgraph_catching_up"
212
+ ];
213
+ function pickPrimary(issues) {
214
+ if (issues.length === 0)
215
+ return null;
216
+ return [...issues].sort((a, b) => {
217
+ const sevDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
218
+ if (sevDiff !== 0)
219
+ return sevDiff;
220
+ return CODE_PRIORITY.indexOf(a.code) - CODE_PRIORITY.indexOf(b.code);
221
+ })[0] ?? null;
222
+ }
223
+ function hintFor(issue) {
224
+ if (!issue.fix)
225
+ return issue.detail ?? issue.code;
226
+ return issue.fix.command ? `${issue.fix.text} ${issue.fix.command}` : issue.fix.text;
227
+ }
5
228
  function buildDoctorReport(input) {
6
229
  const successful = input.deliveries.filter(isSuccessDelivery).length;
7
230
  const failed = input.deliveries.length - successful;
@@ -18,35 +241,60 @@ function buildDoctorReport(input) {
18
241
  const issues = [];
19
242
  if (input.webhook.warning) {
20
243
  hints.push(input.webhook.warning);
21
- issues.push({ code: "warning", detail: input.webhook.warning });
244
+ issues.push({
245
+ code: "warning",
246
+ detail: input.webhook.warning,
247
+ severity: "warn"
248
+ });
22
249
  }
23
250
  if (input.webhook.status === "paused") {
24
251
  hints.push(`Resume when the receiver is healthy: secondlayer webhooks resume ${input.webhook.id}`);
25
- issues.push({ code: "paused" });
252
+ issues.push({ code: "paused", severity: "warn" });
26
253
  }
27
254
  if (input.webhook.lastError) {
28
255
  hints.push("Run secondlayer webhooks test to reproduce the receiver request.");
29
- issues.push({ code: "last_error", detail: input.webhook.lastError });
256
+ issues.push({
257
+ code: "last_error",
258
+ detail: input.webhook.lastError,
259
+ severity: "warn"
260
+ });
30
261
  }
31
262
  if (input.webhook.circuitOpenedAt || input.webhook.circuitFailures > 0) {
32
263
  hints.push("Circuit breaker has failures; inspect receiver logs and delivery status codes.");
33
- issues.push({ code: "circuit" });
264
+ issues.push({ code: "circuit", severity: "bad" });
34
265
  }
35
266
  if (input.dead.length > 0) {
36
267
  hints.push(`Dead-letter rows exist; inspect with secondlayer webhooks dead ${input.webhook.id} and requeue selected rows.`);
37
- issues.push({ code: "dead_letters" });
268
+ issues.push({ code: "dead_letters", severity: "bad" });
38
269
  }
39
270
  if (subgraph?.gapCount && subgraph.gapCount > 0) {
40
271
  hints.push(`Linked subgraph has gaps; run secondlayer subgraphs gaps ${input.webhook.subgraphName}.`);
41
- issues.push({ code: "subgraph_gaps" });
272
+ issues.push({ code: "subgraph_gaps", severity: "calm" });
42
273
  }
43
274
  if (subgraph?.syncStatus === "catching_up") {
44
275
  hints.push("Linked subgraph is still catching up; new matching rows may arrive later.");
45
- issues.push({ code: "subgraph_catching_up" });
276
+ issues.push({ code: "subgraph_catching_up", severity: "calm" });
277
+ }
278
+ for (const detector of [
279
+ detectReceiverRateLimited,
280
+ detectReceiverDown,
281
+ detectReceiverRejects,
282
+ detectReceiverSlow
283
+ ]) {
284
+ const issue = detector(input.webhook, input.deliveries);
285
+ if (issue) {
286
+ issues.push(issue);
287
+ hints.push(hintFor(issue));
288
+ }
289
+ }
290
+ const lagIssue = detectDeliveryLag(input.deliveries);
291
+ if (lagIssue) {
292
+ issues.push(lagIssue);
293
+ hints.push(hintFor(lagIssue));
46
294
  }
47
295
  if (input.deliveries.length === 0) {
48
296
  hints.push("No deliveries yet; confirm the table is receiving inserted rows that match the filter.");
49
- issues.push({ code: "no_deliveries" });
297
+ issues.push({ code: "no_deliveries", severity: "calm" });
50
298
  }
51
299
  if (hints.length === 0) {
52
300
  hints.push("No immediate action needed.");
@@ -62,13 +310,40 @@ function buildDoctorReport(input) {
62
310
  deadCount: input.dead.length,
63
311
  subgraph,
64
312
  hints,
65
- issues
313
+ issues,
314
+ primary: pickPrimary(issues)
66
315
  };
67
316
  }
317
+ function buildListIssue(webhook) {
318
+ if (webhook.status !== "paused")
319
+ return null;
320
+ if (webhook.circuitOpenedAt) {
321
+ return {
322
+ code: "circuit",
323
+ severity: "bad",
324
+ evidence: [
325
+ {
326
+ label: "circuit opened",
327
+ value: formatShortDate(webhook.circuitOpenedAt)
328
+ },
329
+ {
330
+ label: "last success",
331
+ value: webhook.lastSuccessAt ? formatShortDate(webhook.lastSuccessAt) : "never"
332
+ }
333
+ ],
334
+ fix: {
335
+ text: "Check your receiver's health and logs, then send a test event.",
336
+ docsPath: "/docs/webhooks#receiver-down"
337
+ }
338
+ };
339
+ }
340
+ return { code: "paused", severity: "warn" };
341
+ }
68
342
  export {
69
343
  buildDoctorReport,
344
+ buildListIssue,
70
345
  isSuccessDelivery
71
346
  };
72
347
 
73
- //# debugId=2B12BAB34B7CE6B164756E2164756E21
348
+ //# debugId=1D1950A610FFBD1864756E2164756E21
74
349
  //# sourceMappingURL=doctor.js.map
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["src/webhooks/doctor.ts"],
4
4
  "sourcesContent": [
5
- "import type { SubgraphDetail } from \"@secondlayer/shared/schemas/subgraphs\";\nimport type {\n\tDeadRow,\n\tDeliveryRow,\n\tWebhookDetail,\n} from \"@secondlayer/shared/schemas/webhooks\";\n\n/**\n * Webhook diagnosis, shared by `secondlayer webhooks doctor` and the\n * dashboard's webhook detail page. Moved out of the CLI (unchanged behavior)\n * so the web can build the same judgment without printing CLI-command text.\n */\n\nexport function isSuccessDelivery(row: DeliveryRow): boolean {\n\treturn (\n\t\trow.statusCode !== null && row.statusCode >= 200 && row.statusCode < 300\n\t);\n}\n\nexport type DoctorIssueCode =\n\t| \"warning\"\n\t| \"paused\"\n\t| \"last_error\"\n\t| \"circuit\"\n\t| \"dead_letters\"\n\t| \"subgraph_gaps\"\n\t| \"subgraph_catching_up\"\n\t| \"no_deliveries\";\n\nexport interface DoctorIssue {\n\tcode: DoctorIssueCode;\n\tdetail?: string;\n}\n\nexport interface DoctorReport {\n\twebhook: WebhookDetail;\n\tdeliverySummary: {\n\t\ttotal: number;\n\t\tsuccessful: number;\n\t\tfailed: number;\n\t\tlast: DeliveryRow | null;\n\t};\n\tdeadCount: number;\n\tsubgraph: {\n\t\tname: string;\n\t\tstatus: string;\n\t\tsyncStatus: string;\n\t\tlastProcessedBlock: number;\n\t\tchainTip: number;\n\t\tgapCount: number;\n\t\tintegrity: string;\n\t} | null;\n\t/** CLI-facing next steps, one per issue, in the same order as `issues`. */\n\thints: string[];\n\tissues: DoctorIssue[];\n}\n\nexport function buildDoctorReport(input: {\n\twebhook: WebhookDetail;\n\tdeliveries: DeliveryRow[];\n\tdead: DeadRow[];\n\tsubgraph?: SubgraphDetail | null;\n}): DoctorReport {\n\tconst successful = input.deliveries.filter(isSuccessDelivery).length;\n\tconst failed = input.deliveries.length - successful;\n\tconst subgraph = input.subgraph\n\t\t? {\n\t\t\t\tname: input.subgraph.name,\n\t\t\t\tstatus: input.subgraph.status,\n\t\t\t\tsyncStatus: input.subgraph.sync.status,\n\t\t\t\tlastProcessedBlock: input.subgraph.sync.lastProcessedBlock,\n\t\t\t\tchainTip: input.subgraph.sync.chainTip,\n\t\t\t\tgapCount: input.subgraph.sync.gaps.count,\n\t\t\t\tintegrity: input.subgraph.sync.integrity,\n\t\t\t}\n\t\t: null;\n\n\tconst hints: string[] = [];\n\tconst issues: DoctorIssue[] = [];\n\tif (input.webhook.warning) {\n\t\thints.push(input.webhook.warning);\n\t\tissues.push({ code: \"warning\", detail: input.webhook.warning });\n\t}\n\tif (input.webhook.status === \"paused\") {\n\t\thints.push(\n\t\t\t`Resume when the receiver is healthy: secondlayer webhooks resume ${input.webhook.id}`,\n\t\t);\n\t\tissues.push({ code: \"paused\" });\n\t}\n\tif (input.webhook.lastError) {\n\t\thints.push(\n\t\t\t\"Run secondlayer webhooks test to reproduce the receiver request.\",\n\t\t);\n\t\tissues.push({ code: \"last_error\", detail: input.webhook.lastError });\n\t}\n\tif (input.webhook.circuitOpenedAt || input.webhook.circuitFailures > 0) {\n\t\thints.push(\n\t\t\t\"Circuit breaker has failures; inspect receiver logs and delivery status codes.\",\n\t\t);\n\t\tissues.push({ code: \"circuit\" });\n\t}\n\tif (input.dead.length > 0) {\n\t\thints.push(\n\t\t\t`Dead-letter rows exist; inspect with secondlayer webhooks dead ${input.webhook.id} and requeue selected rows.`,\n\t\t);\n\t\tissues.push({ code: \"dead_letters\" });\n\t}\n\tif (subgraph?.gapCount && subgraph.gapCount > 0) {\n\t\thints.push(\n\t\t\t`Linked subgraph has gaps; run secondlayer subgraphs gaps ${input.webhook.subgraphName}.`,\n\t\t);\n\t\tissues.push({ code: \"subgraph_gaps\" });\n\t}\n\tif (subgraph?.syncStatus === \"catching_up\") {\n\t\thints.push(\n\t\t\t\"Linked subgraph is still catching up; new matching rows may arrive later.\",\n\t\t);\n\t\tissues.push({ code: \"subgraph_catching_up\" });\n\t}\n\tif (input.deliveries.length === 0) {\n\t\thints.push(\n\t\t\t\"No deliveries yet; confirm the table is receiving inserted rows that match the filter.\",\n\t\t);\n\t\tissues.push({ code: \"no_deliveries\" });\n\t}\n\tif (hints.length === 0) {\n\t\thints.push(\"No immediate action needed.\");\n\t}\n\n\treturn {\n\t\twebhook: input.webhook,\n\t\tdeliverySummary: {\n\t\t\ttotal: input.deliveries.length,\n\t\t\tsuccessful,\n\t\t\tfailed,\n\t\t\tlast: input.deliveries[0] ?? null,\n\t\t},\n\t\tdeadCount: input.dead.length,\n\t\tsubgraph,\n\t\thints,\n\t\tissues,\n\t};\n}\n"
5
+ "import type { SubgraphDetail } from \"@secondlayer/shared/schemas/subgraphs\";\nimport type {\n\tDeadRow,\n\tDeliveryRow,\n\tWebhookDetail,\n\tWebhookSummary,\n} from \"@secondlayer/shared/schemas/webhooks\";\n\n/**\n * Webhook diagnosis, shared by `secondlayer webhooks doctor` and the\n * dashboard's webhook detail page. Moved out of the CLI (unchanged behavior)\n * so the web can build the same judgment without printing CLI-command text.\n *\n * Plan 068 adds five deterministic, numbers-only detectors on top of the\n * existing flag checks: no model, no history beyond the deliveries window\n * already fetched. Thresholds are named constants below, conservative on\n * purpose — a wrong insight costs more trust than a missed one.\n */\n\nexport function isSuccessDelivery(row: DeliveryRow): boolean {\n\treturn (\n\t\trow.statusCode !== null && row.statusCode >= 200 && row.statusCode < 300\n\t);\n}\n\nexport type DoctorSeverity = \"bad\" | \"warn\" | \"calm\";\n\nexport interface DoctorEvidence {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface DoctorFix {\n\ttext: string;\n\t/** A `secondlayer webhooks ...` command the reader can copy. */\n\tcommand?: string;\n\t/** Anchor into the docs' webhooks troubleshooting section. */\n\tdocsPath?: string;\n}\n\nexport type DoctorIssueCode =\n\t| \"warning\"\n\t| \"paused\"\n\t| \"last_error\"\n\t| \"circuit\"\n\t| \"dead_letters\"\n\t| \"subgraph_gaps\"\n\t| \"subgraph_catching_up\"\n\t| \"no_deliveries\"\n\t| \"receiver_rate_limited\"\n\t| \"receiver_down\"\n\t| \"receiver_rejects\"\n\t| \"receiver_slow\"\n\t| \"delivery_lag\";\n\nexport interface DoctorIssue {\n\tcode: DoctorIssueCode;\n\tdetail?: string;\n\tseverity: DoctorSeverity;\n\t/** 2-4 numbers-only facts backing the issue. Only the five new detectors\n\t * and the list-summary circuit issue carry this — the older flag checks\n\t * keep their plain `detail` string. */\n\tevidence?: DoctorEvidence[];\n\tfix?: DoctorFix;\n}\n\nexport interface DoctorReport {\n\twebhook: WebhookDetail;\n\tdeliverySummary: {\n\t\ttotal: number;\n\t\tsuccessful: number;\n\t\tfailed: number;\n\t\tlast: DeliveryRow | null;\n\t};\n\tdeadCount: number;\n\tsubgraph: {\n\t\tname: string;\n\t\tstatus: string;\n\t\tsyncStatus: string;\n\t\tlastProcessedBlock: number;\n\t\tchainTip: number;\n\t\tgapCount: number;\n\t\tintegrity: string;\n\t} | null;\n\t/** CLI-facing next steps, one per issue, in the same order as `issues`. */\n\thints: string[];\n\tissues: DoctorIssue[];\n\t/** The single most severe issue (bad > warn > calm, then rule priority),\n\t * or `null` when there are none. The UI leads with this and collapses\n\t * the rest underneath. */\n\tprimary: DoctorIssue | null;\n}\n\n// ── Detector thresholds ─────────────────────────────────────────────\n// Named constants, not config (plan 068). Tune from real data at the 054\n// spike — until then these stay conservative.\n\nconst RATE_LIMITED_MIN_ATTEMPTS = 10;\nconst RATE_LIMITED_MIN_RATIO = 0.5;\nconst DOWN_MIN_CONSECUTIVE = 5;\nconst REJECTS_MIN_CONSECUTIVE = 5;\nconst SLOW_WINDOW = 20;\nconst SLOW_MIN_SAMPLES = 5;\nconst SLOW_TIMEOUT_RATIO = 0.5;\nconst LAG_WINDOW = 20;\nconst LAG_MIN_SAMPLES = 5;\nconst LAG_THRESHOLD_MS = 60_000;\nconst ERROR_SNIPPET_MAX = 200;\n\nfunction median(values: number[]): number {\n\tif (values.length === 0) return 0;\n\tconst sorted = [...values].sort((a, b) => a - b);\n\treturn sorted[Math.floor(sorted.length / 2)] ?? 0;\n}\n\nfunction percentile(values: number[], p: number): number {\n\tif (values.length === 0) return 0;\n\tconst sorted = [...values].sort((a, b) => a - b);\n\tconst idx = Math.min(sorted.length - 1, Math.floor(p * sorted.length));\n\treturn sorted[idx] ?? 0;\n}\n\nfunction formatMs(ms: number): string {\n\treturn ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${Math.round(ms)}ms`;\n}\n\n/** \"2026-04-23 00:00 UTC\" — date, hour:minute, and an explicit zone so an\n * evidence value never reads as local time. */\nfunction formatShortDate(iso: string): string {\n\treturn `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;\n}\n\n/** The longest run from the start of `rows` (newest-first) matching\n * `predicate` — the current losing (or rejecting) streak. */\nfunction consecutiveFromStart(\n\trows: DeliveryRow[],\n\tpredicate: (row: DeliveryRow) => boolean,\n): DeliveryRow[] {\n\tconst out: DeliveryRow[] = [];\n\tfor (const row of rows) {\n\t\tif (!predicate(row)) break;\n\t\tout.push(row);\n\t}\n\treturn out;\n}\n\nfunction summarizeStatusBreakdown(rows: DeliveryRow[]): string {\n\tconst counts = new Map<string, number>();\n\tfor (const row of rows) {\n\t\tconst key =\n\t\t\trow.statusCode === null ? \"timeout/no response\" : String(row.statusCode);\n\t\tcounts.set(key, (counts.get(key) ?? 0) + 1);\n\t}\n\treturn [...counts.entries()].map(([key, n]) => `${n}× ${key}`).join(\", \");\n}\n\nconst RETRY_AFTER_RE = /retry-after:?\\s*(\\d+)/i;\n\n/** The most recent 429's `Retry-After`, if the receiver's error text carried\n * one — we never invent a value the rows didn't report. */\nfunction extractRetryAfterSeconds(rows: DeliveryRow[]): string | null {\n\tfor (const row of rows) {\n\t\tif (row.statusCode !== 429 || !row.errorMessage) continue;\n\t\tconst match = row.errorMessage.match(RETRY_AFTER_RE);\n\t\tif (match?.[1]) return match[1];\n\t}\n\treturn null;\n}\n\n/** ≥10 attempts in the window, ≥50% of them 429 — the receiver is\n * rate-limiting us, not failing. */\nfunction detectReceiverRateLimited(\n\twebhook: WebhookDetail,\n\tdeliveries: DeliveryRow[],\n): DoctorIssue | null {\n\tif (deliveries.length < RATE_LIMITED_MIN_ATTEMPTS) return null;\n\tconst count429 = deliveries.filter((d) => d.statusCode === 429).length;\n\tif (count429 / deliveries.length < RATE_LIMITED_MIN_RATIO) return null;\n\n\tconst oldest = deliveries[deliveries.length - 1];\n\tconst newest = deliveries[0];\n\tconst evidence: DoctorEvidence[] = [\n\t\t{\n\t\t\tlabel: \"429 responses\",\n\t\t\tvalue: `${count429} of ${deliveries.length} attempts`,\n\t\t},\n\t\t{\n\t\t\tlabel: \"window\",\n\t\t\tvalue:\n\t\t\t\toldest && newest\n\t\t\t\t\t? `${formatShortDate(oldest.dispatchedAt)} → ${formatShortDate(newest.dispatchedAt)}`\n\t\t\t\t\t: \"unknown\",\n\t\t},\n\t];\n\tconst retryAfter = extractRetryAfterSeconds(deliveries);\n\tif (retryAfter) {\n\t\tevidence.push({ label: \"latest Retry-After\", value: `${retryAfter}s` });\n\t}\n\n\tconst suggestedConcurrency = Math.max(1, Math.floor(webhook.concurrency / 2));\n\treturn {\n\t\tcode: \"receiver_rate_limited\",\n\t\tseverity: \"warn\",\n\t\tevidence,\n\t\tfix: {\n\t\t\ttext: \"Lower concurrency, or honor the Retry-After header on your receiver.\",\n\t\t\tcommand: `secondlayer webhooks update ${webhook.id} --concurrency ${suggestedConcurrency}`,\n\t\t\tdocsPath: \"/docs/webhooks#receiver-rate-limited\",\n\t\t},\n\t};\n}\n\n/** ≥5 most recent attempts are 5xx or got no response at all (timeout /\n * connection failure) — the receiver looks down, not just slow. */\nfunction detectReceiverDown(\n\twebhook: WebhookDetail,\n\tdeliveries: DeliveryRow[],\n): DoctorIssue | null {\n\tconst streak = consecutiveFromStart(\n\t\tdeliveries,\n\t\t(d) => d.statusCode === null || d.statusCode >= 500,\n\t);\n\tif (streak.length < DOWN_MIN_CONSECUTIVE) return null;\n\n\treturn {\n\t\tcode: \"receiver_down\",\n\t\tseverity: \"bad\",\n\t\tevidence: [\n\t\t\t{ label: \"consecutive failures\", value: String(streak.length) },\n\t\t\t{ label: \"breakdown\", value: summarizeStatusBreakdown(streak) },\n\t\t\t{\n\t\t\t\tlabel: \"last success\",\n\t\t\t\tvalue: webhook.lastSuccessAt\n\t\t\t\t\t? formatShortDate(webhook.lastSuccessAt)\n\t\t\t\t\t: \"never\",\n\t\t\t},\n\t\t],\n\t\tfix: {\n\t\t\ttext: \"Check your receiver's health and logs, then send a test event.\",\n\t\t\tcommand: `secondlayer webhooks test ${webhook.id}`,\n\t\t\tdocsPath: \"/docs/webhooks#receiver-down\",\n\t\t},\n\t};\n}\n\n/** ≥5 most recent attempts are 4xx other than 429 — the receiver is\n * reachable but actively rejecting us (auth, signature, bad request). */\nfunction detectReceiverRejects(\n\twebhook: WebhookDetail,\n\tdeliveries: DeliveryRow[],\n): DoctorIssue | null {\n\tconst streak = consecutiveFromStart(\n\t\tdeliveries,\n\t\t(d) =>\n\t\t\td.statusCode !== null &&\n\t\t\td.statusCode >= 400 &&\n\t\t\td.statusCode < 500 &&\n\t\t\td.statusCode !== 429,\n\t);\n\tif (streak.length < REJECTS_MIN_CONSECUTIVE) return null;\n\n\tconst evidence: DoctorEvidence[] = [\n\t\t{ label: \"consecutive rejects\", value: String(streak.length) },\n\t\t{ label: \"breakdown\", value: summarizeStatusBreakdown(streak) },\n\t];\n\tconst withText = streak.find((d) => d.errorMessage || d.responseBody);\n\tconst snippet = withText\n\t\t? (withText.errorMessage ?? withText.responseBody ?? \"\").slice(\n\t\t\t\t0,\n\t\t\t\tERROR_SNIPPET_MAX,\n\t\t\t)\n\t\t: null;\n\tif (snippet) evidence.push({ label: \"first error\", value: snippet });\n\n\treturn {\n\t\tcode: \"receiver_rejects\",\n\t\tseverity: \"bad\",\n\t\tevidence,\n\t\tfix: {\n\t\t\ttext: \"Check your receiver's auth config or signature verification.\",\n\t\t\tcommand: `secondlayer webhooks test ${webhook.id}`,\n\t\t\tdocsPath: \"/docs/webhooks#receiver-rejects\",\n\t\t},\n\t};\n}\n\n/** Median response time of the newest 20 attempts is at least half the\n * webhook's timeout — deliveries are slow, not failing. */\nfunction detectReceiverSlow(\n\twebhook: WebhookDetail,\n\tdeliveries: DeliveryRow[],\n): DoctorIssue | null {\n\tconst durations = deliveries\n\t\t.slice(0, SLOW_WINDOW)\n\t\t.map((d) => d.durationMs)\n\t\t.filter((d): d is number => d !== null);\n\tif (durations.length < SLOW_MIN_SAMPLES) return null;\n\n\tconst med = median(durations);\n\tif (med < webhook.timeoutMs * SLOW_TIMEOUT_RATIO) return null;\n\n\treturn {\n\t\tcode: \"receiver_slow\",\n\t\tseverity: \"warn\",\n\t\tevidence: [\n\t\t\t{ label: \"median response time\", value: formatMs(med) },\n\t\t\t{\n\t\t\t\tlabel: \"p95 response time\",\n\t\t\t\tvalue: formatMs(percentile(durations, 0.95)),\n\t\t\t},\n\t\t\t{ label: \"timeout\", value: formatMs(webhook.timeoutMs) },\n\t\t],\n\t\tfix: {\n\t\t\ttext: \"Speed up your receiver, or raise the timeout (up to 30s hosted).\",\n\t\t\tcommand: `secondlayer webhooks update ${webhook.id} --timeout-ms 30000`,\n\t\t\tdocsPath: \"/docs/webhooks#receiver-slow\",\n\t\t},\n\t};\n}\n\n/** Median (dispatch time − block time) of the newest 20 attempts with a\n * known block time exceeds a minute — we're behind on delivery. This is\n * our-side latency, not something the receiver can fix, so it carries no\n * actionable command. */\nfunction detectDeliveryLag(deliveries: DeliveryRow[]): DoctorIssue | null {\n\tconst window = deliveries\n\t\t.slice(0, LAG_WINDOW)\n\t\t.filter(\n\t\t\t(d): d is DeliveryRow & { blockTime: string } => d.blockTime !== null,\n\t\t);\n\tif (window.length < LAG_MIN_SAMPLES) return null;\n\n\tconst lags = window.map(\n\t\t(d) => new Date(d.dispatchedAt).getTime() - new Date(d.blockTime).getTime(),\n\t);\n\tconst med = median(lags);\n\tif (med <= LAG_THRESHOLD_MS) return null;\n\n\tconst blockTimes = window.map((d) => d.blockTime).sort();\n\tconst oldest = blockTimes[0];\n\tconst newest = blockTimes[blockTimes.length - 1];\n\n\treturn {\n\t\tcode: \"delivery_lag\",\n\t\tseverity: \"warn\",\n\t\tevidence: [\n\t\t\t{ label: \"median lag\", value: formatMs(med) },\n\t\t\t{\n\t\t\t\tlabel: \"block time range\",\n\t\t\t\tvalue:\n\t\t\t\t\toldest && newest\n\t\t\t\t\t\t? `${formatShortDate(oldest)} → ${formatShortDate(newest)}`\n\t\t\t\t\t\t: \"unknown\",\n\t\t\t},\n\t\t],\n\t\tfix: {\n\t\t\ttext: \"We're behind on delivery; no action needed on your end.\",\n\t\t},\n\t};\n}\n\nconst SEVERITY_RANK: Record<DoctorSeverity, number> = {\n\tbad: 0,\n\twarn: 1,\n\tcalm: 2,\n};\n\n/** Tie-break within a severity tier when more than one issue fires at\n * once — most specific / most actionable first. */\nconst CODE_PRIORITY: DoctorIssueCode[] = [\n\t\"circuit\",\n\t\"receiver_down\",\n\t\"receiver_rejects\",\n\t\"dead_letters\",\n\t\"paused\",\n\t\"last_error\",\n\t\"warning\",\n\t\"receiver_rate_limited\",\n\t\"receiver_slow\",\n\t\"delivery_lag\",\n\t\"no_deliveries\",\n\t\"subgraph_gaps\",\n\t\"subgraph_catching_up\",\n];\n\nfunction pickPrimary(issues: DoctorIssue[]): DoctorIssue | null {\n\tif (issues.length === 0) return null;\n\treturn (\n\t\t[...issues].sort((a, b) => {\n\t\t\tconst sevDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];\n\t\t\tif (sevDiff !== 0) return sevDiff;\n\t\t\treturn CODE_PRIORITY.indexOf(a.code) - CODE_PRIORITY.indexOf(b.code);\n\t\t})[0] ?? null\n\t);\n}\n\nfunction hintFor(issue: DoctorIssue): string {\n\tif (!issue.fix) return issue.detail ?? issue.code;\n\treturn issue.fix.command\n\t\t? `${issue.fix.text} ${issue.fix.command}`\n\t\t: issue.fix.text;\n}\n\nexport function buildDoctorReport(input: {\n\twebhook: WebhookDetail;\n\tdeliveries: DeliveryRow[];\n\tdead: DeadRow[];\n\tsubgraph?: SubgraphDetail | null;\n}): DoctorReport {\n\tconst successful = input.deliveries.filter(isSuccessDelivery).length;\n\tconst failed = input.deliveries.length - successful;\n\tconst subgraph = input.subgraph\n\t\t? {\n\t\t\t\tname: input.subgraph.name,\n\t\t\t\tstatus: input.subgraph.status,\n\t\t\t\tsyncStatus: input.subgraph.sync.status,\n\t\t\t\tlastProcessedBlock: input.subgraph.sync.lastProcessedBlock,\n\t\t\t\tchainTip: input.subgraph.sync.chainTip,\n\t\t\t\tgapCount: input.subgraph.sync.gaps.count,\n\t\t\t\tintegrity: input.subgraph.sync.integrity,\n\t\t\t}\n\t\t: null;\n\n\tconst hints: string[] = [];\n\tconst issues: DoctorIssue[] = [];\n\tif (input.webhook.warning) {\n\t\thints.push(input.webhook.warning);\n\t\tissues.push({\n\t\t\tcode: \"warning\",\n\t\t\tdetail: input.webhook.warning,\n\t\t\tseverity: \"warn\",\n\t\t});\n\t}\n\tif (input.webhook.status === \"paused\") {\n\t\thints.push(\n\t\t\t`Resume when the receiver is healthy: secondlayer webhooks resume ${input.webhook.id}`,\n\t\t);\n\t\tissues.push({ code: \"paused\", severity: \"warn\" });\n\t}\n\tif (input.webhook.lastError) {\n\t\thints.push(\n\t\t\t\"Run secondlayer webhooks test to reproduce the receiver request.\",\n\t\t);\n\t\tissues.push({\n\t\t\tcode: \"last_error\",\n\t\t\tdetail: input.webhook.lastError,\n\t\t\tseverity: \"warn\",\n\t\t});\n\t}\n\tif (input.webhook.circuitOpenedAt || input.webhook.circuitFailures > 0) {\n\t\thints.push(\n\t\t\t\"Circuit breaker has failures; inspect receiver logs and delivery status codes.\",\n\t\t);\n\t\tissues.push({ code: \"circuit\", severity: \"bad\" });\n\t}\n\tif (input.dead.length > 0) {\n\t\thints.push(\n\t\t\t`Dead-letter rows exist; inspect with secondlayer webhooks dead ${input.webhook.id} and requeue selected rows.`,\n\t\t);\n\t\tissues.push({ code: \"dead_letters\", severity: \"bad\" });\n\t}\n\tif (subgraph?.gapCount && subgraph.gapCount > 0) {\n\t\thints.push(\n\t\t\t`Linked subgraph has gaps; run secondlayer subgraphs gaps ${input.webhook.subgraphName}.`,\n\t\t);\n\t\tissues.push({ code: \"subgraph_gaps\", severity: \"calm\" });\n\t}\n\tif (subgraph?.syncStatus === \"catching_up\") {\n\t\thints.push(\n\t\t\t\"Linked subgraph is still catching up; new matching rows may arrive later.\",\n\t\t);\n\t\tissues.push({ code: \"subgraph_catching_up\", severity: \"calm\" });\n\t}\n\n\tfor (const detector of [\n\t\tdetectReceiverRateLimited,\n\t\tdetectReceiverDown,\n\t\tdetectReceiverRejects,\n\t\tdetectReceiverSlow,\n\t]) {\n\t\tconst issue = detector(input.webhook, input.deliveries);\n\t\tif (issue) {\n\t\t\tissues.push(issue);\n\t\t\thints.push(hintFor(issue));\n\t\t}\n\t}\n\tconst lagIssue = detectDeliveryLag(input.deliveries);\n\tif (lagIssue) {\n\t\tissues.push(lagIssue);\n\t\thints.push(hintFor(lagIssue));\n\t}\n\n\tif (input.deliveries.length === 0) {\n\t\thints.push(\n\t\t\t\"No deliveries yet; confirm the table is receiving inserted rows that match the filter.\",\n\t\t);\n\t\tissues.push({ code: \"no_deliveries\", severity: \"calm\" });\n\t}\n\tif (hints.length === 0) {\n\t\thints.push(\"No immediate action needed.\");\n\t}\n\n\treturn {\n\t\twebhook: input.webhook,\n\t\tdeliverySummary: {\n\t\t\ttotal: input.deliveries.length,\n\t\t\tsuccessful,\n\t\t\tfailed,\n\t\t\tlast: input.deliveries[0] ?? null,\n\t\t},\n\t\tdeadCount: input.dead.length,\n\t\tsubgraph,\n\t\thints,\n\t\tissues,\n\t\tprimary: pickPrimary(issues),\n\t};\n}\n\n/**\n * The list page's own reduced detector: computed from `WebhookSummary` alone\n * (no deliveries, no dead-letter rows — the list endpoint returns neither,\n * and fetching per-row detail or deliveries here would fan out one request\n * per row). Only `status` and `circuitOpenedAt` distinguish a real problem\n * from a manual pause at this level; deliveries-based rules (rate limits,\n * downtime, rejects, slowness, lag) only ever show on the detail page.\n */\nexport function buildListIssue(\n\twebhook: Pick<WebhookSummary, \"status\" | \"circuitOpenedAt\" | \"lastSuccessAt\">,\n): DoctorIssue | null {\n\tif (webhook.status !== \"paused\") return null;\n\tif (webhook.circuitOpenedAt) {\n\t\treturn {\n\t\t\tcode: \"circuit\",\n\t\t\tseverity: \"bad\",\n\t\t\tevidence: [\n\t\t\t\t{\n\t\t\t\t\tlabel: \"circuit opened\",\n\t\t\t\t\tvalue: formatShortDate(webhook.circuitOpenedAt),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: \"last success\",\n\t\t\t\t\tvalue: webhook.lastSuccessAt\n\t\t\t\t\t\t? formatShortDate(webhook.lastSuccessAt)\n\t\t\t\t\t\t: \"never\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tfix: {\n\t\t\t\ttext: \"Check your receiver's health and logs, then send a test event.\",\n\t\t\t\tdocsPath: \"/docs/webhooks#receiver-down\",\n\t\t\t},\n\t\t};\n\t}\n\treturn { code: \"paused\", severity: \"warn\" };\n}\n"
6
6
  ],
7
- "mappings": ";AAaO,SAAS,iBAAiB,CAAC,KAA2B;AAAA,EAC5D,OACC,IAAI,eAAe,QAAQ,IAAI,cAAc,OAAO,IAAI,aAAa;AAAA;AA0ChE,SAAS,iBAAiB,CAAC,OAKjB;AAAA,EAChB,MAAM,aAAa,MAAM,WAAW,OAAO,iBAAiB,EAAE;AAAA,EAC9D,MAAM,SAAS,MAAM,WAAW,SAAS;AAAA,EACzC,MAAM,WAAW,MAAM,WACpB;AAAA,IACA,MAAM,MAAM,SAAS;AAAA,IACrB,QAAQ,MAAM,SAAS;AAAA,IACvB,YAAY,MAAM,SAAS,KAAK;AAAA,IAChC,oBAAoB,MAAM,SAAS,KAAK;AAAA,IACxC,UAAU,MAAM,SAAS,KAAK;AAAA,IAC9B,UAAU,MAAM,SAAS,KAAK,KAAK;AAAA,IACnC,WAAW,MAAM,SAAS,KAAK;AAAA,EAChC,IACC;AAAA,EAEH,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,SAAwB,CAAC;AAAA,EAC/B,IAAI,MAAM,QAAQ,SAAS;AAAA,IAC1B,MAAM,KAAK,MAAM,QAAQ,OAAO;AAAA,IAChC,OAAO,KAAK,EAAE,MAAM,WAAW,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AAAA,EAC/D;AAAA,EACA,IAAI,MAAM,QAAQ,WAAW,UAAU;AAAA,IACtC,MAAM,KACL,oEAAoE,MAAM,QAAQ,IACnF;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAAA,EAC/B;AAAA,EACA,IAAI,MAAM,QAAQ,WAAW;AAAA,IAC5B,MAAM,KACL,kEACD;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,cAAc,QAAQ,MAAM,QAAQ,UAAU,CAAC;AAAA,EACpE;AAAA,EACA,IAAI,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,kBAAkB,GAAG;AAAA,IACvE,MAAM,KACL,gFACD;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAAA,EAChC;AAAA,EACA,IAAI,MAAM,KAAK,SAAS,GAAG;AAAA,IAC1B,MAAM,KACL,kEAAkE,MAAM,QAAQ,+BACjF;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,eAAe,CAAC;AAAA,EACrC;AAAA,EACA,IAAI,UAAU,YAAY,SAAS,WAAW,GAAG;AAAA,IAChD,MAAM,KACL,4DAA4D,MAAM,QAAQ,eAC3E;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAAA,EACtC;AAAA,EACA,IAAI,UAAU,eAAe,eAAe;AAAA,IAC3C,MAAM,KACL,2EACD;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAAA,EAC7C;AAAA,EACA,IAAI,MAAM,WAAW,WAAW,GAAG;AAAA,IAClC,MAAM,KACL,wFACD;AAAA,IACA,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAAA,EACtC;AAAA,EACA,IAAI,MAAM,WAAW,GAAG;AAAA,IACvB,MAAM,KAAK,6BAA6B;AAAA,EACzC;AAAA,EAEA,OAAO;AAAA,IACN,SAAS,MAAM;AAAA,IACf,iBAAiB;AAAA,MAChB,OAAO,MAAM,WAAW;AAAA,MACxB;AAAA,MACA;AAAA,MACA,MAAM,MAAM,WAAW,MAAM;AAAA,IAC9B;AAAA,IACA,WAAW,MAAM,KAAK;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAAA;",
8
- "debugId": "2B12BAB34B7CE6B164756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "1D1950A610FFBD1864756E2164756E21",
9
9
  "names": []
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/sdk",
3
- "version": "12.2.0",
3
+ "version": "12.3.0",
4
4
  "description": "Decoded Stacks data in your own database — stream rows into your schema, or query the tables you defined.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -64,7 +64,7 @@
64
64
  "prepublishOnly": "bun run build"
65
65
  },
66
66
  "dependencies": {
67
- "@secondlayer/shared": "^11.11.0",
67
+ "@secondlayer/shared": "^11.12.4",
68
68
  "@secondlayer/stacks": "^6.1.0",
69
69
  "@secondlayer/subgraphs": "^6.2.0"
70
70
  },