@secondlayer/shared 13.12.0 → 13.13.1
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/src/archive/range-digest.d.ts +53 -0
- package/dist/src/archive/semantic-digest-builder.d.ts +53 -0
- package/dist/src/coverage/index.d.ts +53 -0
- package/dist/src/db/index.d.ts +66 -2
- package/dist/src/db/index.js +5 -1
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/queries/chain-reorgs.d.ts +53 -0
- package/dist/src/db/queries/chain-reorgs.js +5 -1
- package/dist/src/db/queries/chain-reorgs.js.map +3 -3
- package/dist/src/db/queries/contracts.d.ts +53 -0
- package/dist/src/db/queries/instance.d.ts +53 -0
- package/dist/src/db/queries/integrity.d.ts +53 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +53 -0
- package/dist/src/db/queries/subgraph-operations.d.ts +53 -0
- package/dist/src/db/queries/subgraphs.d.ts +53 -0
- package/dist/src/db/queries/sync-scope.d.ts +53 -0
- package/dist/src/db/queries/webhook-replays.d.ts +1431 -0
- package/dist/src/db/queries/webhook-replays.js +156 -0
- package/dist/src/db/queries/webhook-replays.js.map +10 -0
- package/dist/src/db/queries/webhooks.d.ts +53 -0
- package/dist/src/db/schema.d.ts +62 -2
- package/dist/src/email/index.d.ts +27 -6
- package/dist/src/email/index.js +135 -29
- package/dist/src/email/index.js.map +3 -3
- package/dist/src/index.d.ts +66 -2
- package/dist/src/index.js +9 -1
- package/dist/src/index.js.map +4 -4
- package/dist/src/insights/index.d.ts +70 -3
- package/dist/src/insights/index.js +40 -3
- package/dist/src/insights/index.js.map +3 -3
- package/dist/src/node/local-client.d.ts +53 -0
- package/dist/src/queue/listener.js +5 -1
- package/dist/src/queue/listener.js.map +3 -3
- package/dist/src/schemas/index.js +5 -1
- package/dist/src/schemas/index.js.map +3 -3
- package/dist/src/schemas/webhooks.d.ts +45 -1
- package/dist/src/schemas/webhooks.js +6 -1
- package/dist/src/schemas/webhooks.js.map +3 -3
- package/migrations/0178_insight_snooze.ts +22 -0
- package/migrations/0179_webhook_replays.ts +56 -0
- package/migrations/0180_push.ts +54 -0
- package/migrations/0181_push_sent.ts +30 -0
- package/package.json +5 -1
package/dist/src/email/index.js
CHANGED
|
@@ -1,60 +1,166 @@
|
|
|
1
1
|
// src/email/render.ts
|
|
2
|
+
var LOGO_URL = "https://www.secondlayer.tools/email/logo.png";
|
|
3
|
+
var TAGLINE = "Instant data for apps on Bitcoin";
|
|
4
|
+
var SANS = "Geist, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
5
|
+
var MONO = "'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
|
|
6
|
+
var LIGHT = {
|
|
7
|
+
paper: "#fbfaf7",
|
|
8
|
+
ink: "#14110d",
|
|
9
|
+
sub: "#63605c",
|
|
10
|
+
faint: "#9d9b97",
|
|
11
|
+
line: "#e8e6e3",
|
|
12
|
+
pri: "#1f45e0",
|
|
13
|
+
onPri: "#fbfaf7",
|
|
14
|
+
ok: "#128a43",
|
|
15
|
+
alarm: "#e3301e"
|
|
16
|
+
};
|
|
17
|
+
var DARK_STYLE = `
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
.sl-paper { background: #1e1b17 !important; }
|
|
20
|
+
.sl-ink { color: #e8e6e3 !important; }
|
|
21
|
+
.sl-sub { color: #b3b1ad !important; }
|
|
22
|
+
.sl-faint { color: #7c7a76 !important; }
|
|
23
|
+
.sl-line { border-color: #393632 !important; }
|
|
24
|
+
.sl-pri { background: #3a5bff !important; }
|
|
25
|
+
.sl-ok { color: #5cc28f !important; }
|
|
26
|
+
.sl-alarm { color: #ff5a47 !important; }
|
|
27
|
+
.sl-dot-neutral { background: #7c7a76 !important; }
|
|
28
|
+
.sl-dot-ok { background: #5cc28f !important; }
|
|
29
|
+
.sl-dot-alarm { background: #ff5a47 !important; }
|
|
30
|
+
}`.trim();
|
|
2
31
|
function escapeHtml(value) {
|
|
3
32
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4
33
|
}
|
|
34
|
+
var TONE_COLOR = {
|
|
35
|
+
neutral: LIGHT.sub,
|
|
36
|
+
ok: LIGHT.ok,
|
|
37
|
+
alarm: LIGHT.alarm
|
|
38
|
+
};
|
|
39
|
+
var DOT_COLOR = {
|
|
40
|
+
neutral: LIGHT.faint,
|
|
41
|
+
ok: LIGHT.ok,
|
|
42
|
+
alarm: LIGHT.alarm
|
|
43
|
+
};
|
|
44
|
+
var TONE_CLASS = {
|
|
45
|
+
neutral: "sl-sub",
|
|
46
|
+
ok: "sl-ok",
|
|
47
|
+
alarm: "sl-alarm"
|
|
48
|
+
};
|
|
49
|
+
function dot(tone) {
|
|
50
|
+
return `<span class="sl-dot-${tone}" style="display: inline-block; width: 7px; height: 7px; border-radius: 999px; background: ${DOT_COLOR[tone]}; margin-right: 6px; vertical-align: 1px;"></span>`;
|
|
51
|
+
}
|
|
52
|
+
function statusChip(status) {
|
|
53
|
+
return `<span class="sl-line ${TONE_CLASS[status.tone]}" style="display: inline-block; border: 1px solid ${LIGHT.line}; border-radius: 999px; padding: 2px 8px; font-family: ${MONO}; font-size: 12px; line-height: 16px; color: ${TONE_COLOR[status.tone]}; white-space: nowrap;">${dot(status.tone)}${escapeHtml(status.text)}</span>`;
|
|
54
|
+
}
|
|
55
|
+
function preheaderHtml(preheader) {
|
|
56
|
+
if (!preheader)
|
|
57
|
+
return "";
|
|
58
|
+
const filler = "͏‌ ".repeat(60);
|
|
59
|
+
return `<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all; opacity: 0; color: transparent;">${escapeHtml(preheader)}${filler}</div>`;
|
|
60
|
+
}
|
|
61
|
+
function figureHtml(input) {
|
|
62
|
+
if (!input.code && !input.facts?.length)
|
|
63
|
+
return "";
|
|
64
|
+
const title = input.figure ?? (input.code ? "sign-in code" : "details");
|
|
65
|
+
const line = `border-color: ${LIGHT.line}; border-style: solid; border-width: 0`;
|
|
66
|
+
const bar = `<tr>
|
|
67
|
+
<td class="sl-faint sl-line" style="padding: 8px 14px; ${line}; border-bottom-width: 1px; font-family: ${MONO}; font-size: 12px; line-height: 18px; color: ${LIGHT.faint};">${escapeHtml(title)}</td>
|
|
68
|
+
<td class="sl-line" align="right" style="padding: 8px 14px; ${line}; border-bottom-width: 1px;">${input.status ? statusChip(input.status) : ""}</td>
|
|
69
|
+
</tr>`;
|
|
70
|
+
let body;
|
|
71
|
+
if (input.code) {
|
|
72
|
+
body = `<tr><td colspan="2" align="center" class="sl-ink" style="padding: 24px 14px 8px; font-family: ${MONO}; font-size: 36px; line-height: 40px; font-weight: 500; letter-spacing: 0.3em; color: ${LIGHT.ink}; -webkit-user-select: all; user-select: all;">${escapeHtml(input.code)}</td></tr>
|
|
73
|
+
<tr><td colspan="2" align="center" class="sl-faint" style="padding: 0 14px 16px; font-family: ${MONO}; font-size: 12px; line-height: 18px; color: ${LIGHT.faint};">tap or double-click to select</td></tr>`;
|
|
74
|
+
} else {
|
|
75
|
+
const facts = input.facts ?? [];
|
|
76
|
+
body = facts.map((fact, i) => {
|
|
77
|
+
const top = i === 0 ? 12 : 2;
|
|
78
|
+
const bottom = i === facts.length - 1 ? 12 : 2;
|
|
79
|
+
const cell = `padding: ${top}px 14px ${bottom}px; font-family: ${MONO}; font-size: 13px; line-height: 20px; vertical-align: top;`;
|
|
80
|
+
return `<tr>
|
|
81
|
+
<td class="sl-faint" style="${cell} padding-right: 24px; width: 1%; color: ${LIGHT.faint}; white-space: nowrap;">${escapeHtml(fact.label.toLowerCase())}</td>
|
|
82
|
+
<td class="sl-ink" style="${cell} color: ${LIGHT.ink}; word-break: break-word;">${escapeHtml(fact.value)}</td>
|
|
83
|
+
</tr>`;
|
|
84
|
+
}).join(`
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" class="sl-line" style="width: 100%; border: 1px solid ${LIGHT.line}; border-radius: 8px; border-collapse: separate; border-spacing: 0; margin: 8px 0 24px;">
|
|
88
|
+
${bar}
|
|
89
|
+
${body}
|
|
90
|
+
</table>`;
|
|
91
|
+
}
|
|
5
92
|
function renderHtml(input) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
93
|
+
const hasFigure = Boolean(input.code || input.facts?.length);
|
|
94
|
+
const statusLine = input.status && !hasFigure ? `<p class="${TONE_CLASS[input.status.tone]}" style="margin: 0 0 12px; font-size: 13px; line-height: 20px; color: ${TONE_COLOR[input.status.tone]};">${dot(input.status.tone)}${escapeHtml(input.status.text)}</p>` : "";
|
|
95
|
+
const paragraphsHtml = input.paragraphs.map((p) => `<p class="sl-sub" style="margin: 0 0 14px; font-size: 15px; line-height: 24px; color: ${LIGHT.sub};">${escapeHtml(p)}</p>`).join(`
|
|
96
|
+
`);
|
|
97
|
+
const quoteHtml = input.quote ? `<p class="sl-ink" style="margin: 0 0 14px; font-size: 15px; line-height: 24px; color: ${LIGHT.ink};">“${escapeHtml(input.quote)}”</p>` : "";
|
|
98
|
+
const ctaHtml = input.cta ? `<table role="presentation" cellpadding="0" cellspacing="0" style="margin: 10px 0 0;"><tr><td class="sl-pri" style="background: ${LIGHT.pri}; border-radius: 4px;"><a href="${escapeHtml(input.cta.url)}" style="display: inline-block; padding: 0 18px; height: 42px; line-height: 42px; font-family: ${SANS}; font-size: 15px; font-weight: 500; color: ${LIGHT.onPri}; text-decoration: none;">${escapeHtml(input.cta.label)}</a></td></tr></table>` : "";
|
|
99
|
+
const footnoteHtml = input.footnote ? `<p class="sl-faint" style="margin: 0 0 6px;">${escapeHtml(input.footnote)}</p>` : "";
|
|
100
|
+
return `<!doctype html>
|
|
101
|
+
<html lang="en">
|
|
102
|
+
<head>
|
|
103
|
+
<meta charset="utf-8">
|
|
104
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
105
|
+
<meta name="color-scheme" content="light dark">
|
|
106
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
107
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500&family=Geist+Mono:wght@400;500&display=swap">
|
|
108
|
+
<style>
|
|
109
|
+
${DARK_STYLE}
|
|
110
|
+
</style>
|
|
111
|
+
</head>
|
|
112
|
+
<body class="sl-paper" style="margin: 0; padding: 0; background: ${LIGHT.paper};">
|
|
113
|
+
${preheaderHtml(input.preheader)}
|
|
114
|
+
<table role="presentation" cellpadding="0" cellspacing="0" class="sl-paper" style="width: 100%; background: ${LIGHT.paper};">
|
|
19
115
|
<tr>
|
|
20
116
|
<td align="center">
|
|
21
|
-
<table role="presentation" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 560px; font-family:
|
|
117
|
+
<table role="presentation" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 560px; font-family: ${SANS}; padding: 40px 20px;">
|
|
22
118
|
<tr>
|
|
23
119
|
<td>
|
|
24
|
-
<
|
|
25
|
-
|
|
120
|
+
<table role="presentation" cellpadding="0" cellspacing="0" style="margin: 0 0 32px;"><tr>
|
|
121
|
+
<td style="padding-right: 8px; vertical-align: middle;"><img src="${LOGO_URL}" width="30" height="20" alt="" style="display: block; border: 0;"></td>
|
|
122
|
+
<td class="sl-ink" style="vertical-align: middle; font-size: 15px; font-weight: 500; letter-spacing: -0.01em; color: ${LIGHT.ink};">secondlayer</td>
|
|
123
|
+
</tr></table>
|
|
124
|
+
${statusLine}
|
|
125
|
+
<h1 class="sl-ink" style="margin: 0 0 14px; font-size: 24px; line-height: 29px; font-weight: 500; letter-spacing: -0.025em; color: ${LIGHT.ink};">${escapeHtml(input.heading)}</h1>
|
|
26
126
|
${paragraphsHtml}
|
|
27
|
-
${
|
|
28
|
-
${
|
|
127
|
+
${quoteHtml}
|
|
128
|
+
${figureHtml(input)}
|
|
29
129
|
${ctaHtml}
|
|
30
|
-
${
|
|
130
|
+
<div class="sl-line sl-faint" style="margin: 32px 0 0; padding: 16px 0 0; border-top: 1px solid ${LIGHT.line}; font-size: 12px; line-height: 18px; color: ${LIGHT.faint};">
|
|
131
|
+
${footnoteHtml}
|
|
132
|
+
<p class="sl-faint" style="margin: 0;"><span class="sl-sub" style="color: ${LIGHT.sub}; font-weight: 500;">Secondlayer</span> · ${TAGLINE}</p>
|
|
133
|
+
</div>
|
|
31
134
|
</td>
|
|
32
135
|
</tr>
|
|
33
136
|
</table>
|
|
34
137
|
</td>
|
|
35
138
|
</tr>
|
|
36
|
-
</table
|
|
139
|
+
</table>
|
|
140
|
+
</body>
|
|
141
|
+
</html>`;
|
|
37
142
|
}
|
|
38
143
|
function renderText(input) {
|
|
39
144
|
const lines = [input.heading, ""];
|
|
40
145
|
for (const p of input.paragraphs)
|
|
41
146
|
lines.push(p, "");
|
|
42
|
-
if (input.
|
|
147
|
+
if (input.quote)
|
|
148
|
+
lines.push(`"${input.quote}"`, "");
|
|
149
|
+
if (input.status)
|
|
150
|
+
lines.push(input.status.text, "");
|
|
151
|
+
if (input.code)
|
|
43
152
|
lines.push(`Code: ${input.code}`, "");
|
|
44
|
-
}
|
|
45
153
|
if (input.facts?.length) {
|
|
46
154
|
for (const fact of input.facts)
|
|
47
155
|
lines.push(`${fact.label}: ${fact.value}`);
|
|
48
156
|
lines.push("");
|
|
49
157
|
}
|
|
50
|
-
if (input.cta)
|
|
158
|
+
if (input.cta)
|
|
51
159
|
lines.push(`${input.cta.label}: ${input.cta.url}`, "");
|
|
52
|
-
|
|
53
|
-
if (input.footnote)
|
|
160
|
+
lines.push("--");
|
|
161
|
+
if (input.footnote)
|
|
54
162
|
lines.push(input.footnote);
|
|
55
|
-
}
|
|
56
|
-
while (lines.length && lines[lines.length - 1] === "")
|
|
57
|
-
lines.pop();
|
|
163
|
+
lines.push(`Secondlayer · ${TAGLINE}`);
|
|
58
164
|
return lines.join(`
|
|
59
165
|
`);
|
|
60
166
|
}
|
|
@@ -227,5 +333,5 @@ export {
|
|
|
227
333
|
sendEmail
|
|
228
334
|
};
|
|
229
335
|
|
|
230
|
-
//# debugId=
|
|
336
|
+
//# debugId=36F0F48E4EBB77FF64756E2164756E21
|
|
231
337
|
//# sourceMappingURL=index.js.map
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/email/render.ts", "../src/env.ts", "../src/logger.ts", "../src/email/send.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * The one HTML/text layout every customer email renders through. Table-based\n * HTML, inline styles only (email clients strip <style>), 560px max width,\n * white background, #111 text, one black button — same look as the login\n * email this was lifted from. Text is the same content, plainer.\n */\n\nexport interface FactRow {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface EmailCta {\n\tlabel: string;\n\turl: string;\n}\n\nexport interface RenderEmailInput {\n\theading: string;\n\tparagraphs: string[];\n\t/** A code shown in a large, letter-spaced box (login codes). */\n\tcode?: string;\n\tfacts?: FactRow[];\n\tcta?: EmailCta;\n\tfootnote?: string;\n}\n\nexport interface RenderedEmail {\n\thtml: string;\n\ttext: string;\n}\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nfunction renderHtml(input: RenderEmailInput): string {\n\tconst paragraphsHtml = input.paragraphs\n\t\t.map(\n\t\t\t(p) =>\n\t\t\t\t`<p style=\"color: #555; font-size: 14px; line-height: 1.6; margin: 0 0 16px;\">${escapeHtml(p)}</p>`,\n\t\t)\n\t\t.join(\"\\n \");\n\n\tconst codeHtml = input.code\n\t\t? `<div style=\"background: #f5f5f5; border: 1px solid #e5e5e5; border-radius: 8px; padding: 20px; text-align: center; margin: 0 0 24px;\">\n <span style=\"font-size: 32px; font-weight: 600; letter-spacing: 8px; color: #111;\">${escapeHtml(input.code)}</span>\n </div>`\n\t\t: \"\";\n\n\tconst factsHtml = input.facts?.length\n\t\t? `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 100%; margin: 0 0 24px; border-collapse: collapse;\">\n ${input.facts\n\t\t\t.map(\n\t\t\t\t(fact) =>\n\t\t\t\t\t`<tr><td style=\"padding: 6px 0; color: #888; font-size: 13px;\">${escapeHtml(fact.label)}</td><td style=\"padding: 6px 0; color: #111; font-size: 13px; text-align: right; font-weight: 600;\">${escapeHtml(fact.value)}</td></tr>`,\n\t\t\t)\n\t\t\t.join(\"\\n \")}\n </table>`\n\t\t: \"\";\n\n\tconst ctaHtml = input.cta\n\t\t? `<a href=\"${escapeHtml(input.cta.url)}\" style=\"display: inline-block; background: #111; color: #fff; text-decoration: none; padding: 10px 20px; border-radius: 6px; font-size: 14px; margin: 0 0 24px;\">${escapeHtml(input.cta.label)}</a>`\n\t\t: \"\";\n\n\tconst footnoteHtml = input.footnote\n\t\t? `<p style=\"color: #aaa; font-size: 12px; margin: 24px 0 0;\">${escapeHtml(input.footnote)}</p>`\n\t\t: \"\";\n\n\treturn `\n<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 100%; background: #fff;\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 100%; max-width: 560px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 40px 20px;\">\n <tr>\n <td>\n <p style=\"color: #888; font-size: 14px; margin: 0 0 24px;\">secondlayer</p>\n <p style=\"color: #111; font-size: 18px; font-weight: 600; margin: 0 0 16px;\">${escapeHtml(input.heading)}</p>\n ${paragraphsHtml}\n ${codeHtml}\n ${factsHtml}\n ${ctaHtml}\n ${footnoteHtml}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n</table>`.trim();\n}\n\nfunction renderText(input: RenderEmailInput): string {\n\tconst lines: string[] = [input.heading, \"\"];\n\tfor (const p of input.paragraphs) lines.push(p, \"\");\n\tif (input.code) {\n\t\tlines.push(`Code: ${input.code}`, \"\");\n\t}\n\tif (input.facts?.length) {\n\t\tfor (const fact of input.facts) lines.push(`${fact.label}: ${fact.value}`);\n\t\tlines.push(\"\");\n\t}\n\tif (input.cta) {\n\t\tlines.push(`${input.cta.label}: ${input.cta.url}`, \"\");\n\t}\n\tif (input.footnote) {\n\t\tlines.push(input.footnote);\n\t}\n\t// Trim trailing blank lines.\n\twhile (lines.length && lines[lines.length - 1] === \"\") lines.pop();\n\treturn lines.join(\"\\n\");\n}\n\nexport function renderEmail(input: RenderEmailInput): RenderedEmail {\n\treturn { html: renderHtml(input), text: renderText(input) };\n}\n",
|
|
5
|
+
"/**\n * The one HTML/text layout every customer email renders through, in the\n * brand's \"Figure\" look: logo + wordmark, a 24px heading, muted prose, the\n * code or facts inside a hairline code figure whose bar names it and carries\n * a status chip, one blue button, and a hairline footer.\n *\n * Table-based HTML with inline styles (most clients strip <style>). The head\n * <style> only adds dark mode for clients that honor `prefers-color-scheme`\n * (Apple Mail, iOS); everyone else gets the light palette, which Gmail\n * inverts on its own. Geist loads where web fonts work, the system stack\n * elsewhere. Callers pass content; every color lives here.\n */\n\nexport interface FactRow {\n\tlabel: string;\n\tvalue: string;\n}\n\nexport interface EmailCta {\n\tlabel: string;\n\turl: string;\n}\n\n/** `alarm` only for failures and stops, `ok` only for success. */\nexport type EmailTone = \"neutral\" | \"ok\" | \"alarm\";\n\nexport interface EmailStatus {\n\ttone: EmailTone;\n\ttext: string;\n}\n\nexport interface RenderEmailInput {\n\theading: string;\n\t/** The inbox preview line after the subject. Not shown in the body. */\n\tpreheader?: string;\n\tparagraphs: string[];\n\t/** Someone else's words, quoted (a contact form message). */\n\tquote?: string;\n\t/** A sign-in code, shown as one selectable run of digits in the figure. */\n\tcode?: string;\n\tfacts?: FactRow[];\n\t/** The figure's title bar. Defaults to \"sign-in code\" or \"details\". */\n\tfigure?: string;\n\t/** A chip in the figure bar, or a status line when there is no figure. */\n\tstatus?: EmailStatus;\n\tcta?: EmailCta;\n\t/** Why the reader got this email and how to stop it. */\n\tfootnote?: string;\n}\n\nexport interface RenderedEmail {\n\thtml: string;\n\ttext: string;\n}\n\n// www directly: the apex 307s there, and every image fetch would pay for it.\nconst LOGO_URL = \"https://www.secondlayer.tools/email/logo.png\";\nconst TAGLINE = \"Instant data for apps on Bitcoin\";\n\nconst SANS =\n\t\"Geist, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\";\nconst MONO =\n\t\"'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace\";\n\n// DESIGN.md roles. Light inline; dark via the head <style> classes below.\nconst LIGHT = {\n\tpaper: \"#fbfaf7\",\n\tink: \"#14110d\",\n\tsub: \"#63605c\",\n\tfaint: \"#9d9b97\",\n\tline: \"#e8e6e3\",\n\tpri: \"#1f45e0\",\n\tonPri: \"#fbfaf7\",\n\tok: \"#128a43\",\n\talarm: \"#e3301e\",\n};\n\nconst DARK_STYLE = `\n@media (prefers-color-scheme: dark) {\n .sl-paper { background: #1e1b17 !important; }\n .sl-ink { color: #e8e6e3 !important; }\n .sl-sub { color: #b3b1ad !important; }\n .sl-faint { color: #7c7a76 !important; }\n .sl-line { border-color: #393632 !important; }\n .sl-pri { background: #3a5bff !important; }\n .sl-ok { color: #5cc28f !important; }\n .sl-alarm { color: #ff5a47 !important; }\n .sl-dot-neutral { background: #7c7a76 !important; }\n .sl-dot-ok { background: #5cc28f !important; }\n .sl-dot-alarm { background: #ff5a47 !important; }\n}`.trim();\n\nfunction escapeHtml(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\nconst TONE_COLOR: Record<EmailTone, string> = {\n\tneutral: LIGHT.sub,\n\tok: LIGHT.ok,\n\talarm: LIGHT.alarm,\n};\nconst DOT_COLOR: Record<EmailTone, string> = {\n\tneutral: LIGHT.faint,\n\tok: LIGHT.ok,\n\talarm: LIGHT.alarm,\n};\nconst TONE_CLASS: Record<EmailTone, string> = {\n\tneutral: \"sl-sub\",\n\tok: \"sl-ok\",\n\talarm: \"sl-alarm\",\n};\n\nfunction dot(tone: EmailTone): string {\n\treturn `<span class=\"sl-dot-${tone}\" style=\"display: inline-block; width: 7px; height: 7px; border-radius: 999px; background: ${DOT_COLOR[tone]}; margin-right: 6px; vertical-align: 1px;\"></span>`;\n}\n\nfunction statusChip(status: EmailStatus): string {\n\treturn `<span class=\"sl-line ${TONE_CLASS[status.tone]}\" style=\"display: inline-block; border: 1px solid ${LIGHT.line}; border-radius: 999px; padding: 2px 8px; font-family: ${MONO}; font-size: 12px; line-height: 16px; color: ${TONE_COLOR[status.tone]}; white-space: nowrap;\">${dot(status.tone)}${escapeHtml(status.text)}</span>`;\n}\n\nfunction preheaderHtml(preheader: string | undefined): string {\n\tif (!preheader) return \"\";\n\t// Trailing joiners keep body text from leaking into the preview line.\n\tconst filler = \"͏‌ \".repeat(60);\n\treturn `<div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all; opacity: 0; color: transparent;\">${escapeHtml(preheader)}${filler}</div>`;\n}\n\nfunction figureHtml(input: RenderEmailInput): string {\n\tif (!input.code && !input.facts?.length) return \"\";\n\tconst title = input.figure ?? (input.code ? \"sign-in code\" : \"details\");\n\tconst line = `border-color: ${LIGHT.line}; border-style: solid; border-width: 0`;\n\n\tconst bar = `<tr>\n <td class=\"sl-faint sl-line\" style=\"padding: 8px 14px; ${line}; border-bottom-width: 1px; font-family: ${MONO}; font-size: 12px; line-height: 18px; color: ${LIGHT.faint};\">${escapeHtml(title)}</td>\n <td class=\"sl-line\" align=\"right\" style=\"padding: 8px 14px; ${line}; border-bottom-width: 1px;\">${input.status ? statusChip(input.status) : \"\"}</td>\n </tr>`;\n\n\tlet body: string;\n\tif (input.code) {\n\t\tbody = `<tr><td colspan=\"2\" align=\"center\" class=\"sl-ink\" style=\"padding: 24px 14px 8px; font-family: ${MONO}; font-size: 36px; line-height: 40px; font-weight: 500; letter-spacing: 0.3em; color: ${LIGHT.ink}; -webkit-user-select: all; user-select: all;\">${escapeHtml(input.code)}</td></tr>\n <tr><td colspan=\"2\" align=\"center\" class=\"sl-faint\" style=\"padding: 0 14px 16px; font-family: ${MONO}; font-size: 12px; line-height: 18px; color: ${LIGHT.faint};\">tap or double-click to select</td></tr>`;\n\t} else {\n\t\tconst facts = input.facts ?? [];\n\t\tbody = facts\n\t\t\t.map((fact, i) => {\n\t\t\t\tconst top = i === 0 ? 12 : 2;\n\t\t\t\tconst bottom = i === facts.length - 1 ? 12 : 2;\n\t\t\t\tconst cell = `padding: ${top}px 14px ${bottom}px; font-family: ${MONO}; font-size: 13px; line-height: 20px; vertical-align: top;`;\n\t\t\t\treturn `<tr>\n <td class=\"sl-faint\" style=\"${cell} padding-right: 24px; width: 1%; color: ${LIGHT.faint}; white-space: nowrap;\">${escapeHtml(fact.label.toLowerCase())}</td>\n <td class=\"sl-ink\" style=\"${cell} color: ${LIGHT.ink}; word-break: break-word;\">${escapeHtml(fact.value)}</td>\n </tr>`;\n\t\t\t})\n\t\t\t.join(\"\\n \");\n\t}\n\n\treturn `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" class=\"sl-line\" style=\"width: 100%; border: 1px solid ${LIGHT.line}; border-radius: 8px; border-collapse: separate; border-spacing: 0; margin: 8px 0 24px;\">\n ${bar}\n ${body}\n </table>`;\n}\n\nfunction renderHtml(input: RenderEmailInput): string {\n\tconst hasFigure = Boolean(input.code || input.facts?.length);\n\n\tconst statusLine =\n\t\tinput.status && !hasFigure\n\t\t\t? `<p class=\"${TONE_CLASS[input.status.tone]}\" style=\"margin: 0 0 12px; font-size: 13px; line-height: 20px; color: ${TONE_COLOR[input.status.tone]};\">${dot(input.status.tone)}${escapeHtml(input.status.text)}</p>`\n\t\t\t: \"\";\n\n\tconst paragraphsHtml = input.paragraphs\n\t\t.map(\n\t\t\t(p) =>\n\t\t\t\t`<p class=\"sl-sub\" style=\"margin: 0 0 14px; font-size: 15px; line-height: 24px; color: ${LIGHT.sub};\">${escapeHtml(p)}</p>`,\n\t\t)\n\t\t.join(\"\\n \");\n\n\tconst quoteHtml = input.quote\n\t\t? `<p class=\"sl-ink\" style=\"margin: 0 0 14px; font-size: 15px; line-height: 24px; color: ${LIGHT.ink};\">“${escapeHtml(input.quote)}”</p>`\n\t\t: \"\";\n\n\tconst ctaHtml = input.cta\n\t\t? `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin: 10px 0 0;\"><tr><td class=\"sl-pri\" style=\"background: ${LIGHT.pri}; border-radius: 4px;\"><a href=\"${escapeHtml(input.cta.url)}\" style=\"display: inline-block; padding: 0 18px; height: 42px; line-height: 42px; font-family: ${SANS}; font-size: 15px; font-weight: 500; color: ${LIGHT.onPri}; text-decoration: none;\">${escapeHtml(input.cta.label)}</a></td></tr></table>`\n\t\t: \"\";\n\n\tconst footnoteHtml = input.footnote\n\t\t? `<p class=\"sl-faint\" style=\"margin: 0 0 6px;\">${escapeHtml(input.footnote)}</p>`\n\t\t: \"\";\n\n\treturn `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"color-scheme\" content=\"light dark\">\n<meta name=\"supported-color-schemes\" content=\"light dark\">\n<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Geist:wght@400;500&family=Geist+Mono:wght@400;500&display=swap\">\n<style>\n${DARK_STYLE}\n</style>\n</head>\n<body class=\"sl-paper\" style=\"margin: 0; padding: 0; background: ${LIGHT.paper};\">\n${preheaderHtml(input.preheader)}\n<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" class=\"sl-paper\" style=\"width: 100%; background: ${LIGHT.paper};\">\n <tr>\n <td align=\"center\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"width: 100%; max-width: 560px; font-family: ${SANS}; padding: 40px 20px;\">\n <tr>\n <td>\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin: 0 0 32px;\"><tr>\n <td style=\"padding-right: 8px; vertical-align: middle;\"><img src=\"${LOGO_URL}\" width=\"30\" height=\"20\" alt=\"\" style=\"display: block; border: 0;\"></td>\n <td class=\"sl-ink\" style=\"vertical-align: middle; font-size: 15px; font-weight: 500; letter-spacing: -0.01em; color: ${LIGHT.ink};\">secondlayer</td>\n </tr></table>\n ${statusLine}\n <h1 class=\"sl-ink\" style=\"margin: 0 0 14px; font-size: 24px; line-height: 29px; font-weight: 500; letter-spacing: -0.025em; color: ${LIGHT.ink};\">${escapeHtml(input.heading)}</h1>\n ${paragraphsHtml}\n ${quoteHtml}\n ${figureHtml(input)}\n ${ctaHtml}\n <div class=\"sl-line sl-faint\" style=\"margin: 32px 0 0; padding: 16px 0 0; border-top: 1px solid ${LIGHT.line}; font-size: 12px; line-height: 18px; color: ${LIGHT.faint};\">\n ${footnoteHtml}\n <p class=\"sl-faint\" style=\"margin: 0;\"><span class=\"sl-sub\" style=\"color: ${LIGHT.sub}; font-weight: 500;\">Secondlayer</span> · ${TAGLINE}</p>\n </div>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n</table>\n</body>\n</html>`;\n}\n\nfunction renderText(input: RenderEmailInput): string {\n\tconst lines: string[] = [input.heading, \"\"];\n\tfor (const p of input.paragraphs) lines.push(p, \"\");\n\tif (input.quote) lines.push(`\"${input.quote}\"`, \"\");\n\tif (input.status) lines.push(input.status.text, \"\");\n\tif (input.code) lines.push(`Code: ${input.code}`, \"\");\n\tif (input.facts?.length) {\n\t\tfor (const fact of input.facts) lines.push(`${fact.label}: ${fact.value}`);\n\t\tlines.push(\"\");\n\t}\n\tif (input.cta) lines.push(`${input.cta.label}: ${input.cta.url}`, \"\");\n\tlines.push(\"--\");\n\tif (input.footnote) lines.push(input.footnote);\n\tlines.push(`Secondlayer · ${TAGLINE}`);\n\treturn lines.join(\"\\n\");\n}\n\nexport function renderEmail(input: RenderEmailInput): RenderedEmail {\n\treturn { html: renderHtml(input), text: renderText(input) };\n}\n",
|
|
6
6
|
"import { z } from \"zod\";\n\n// Parse comma-separated networks\nconst networksSchema = z.string().transform((val) => {\n\tconst networks = val\n\t\t.split(\",\")\n\t\t.map((n) => n.trim())\n\t\t.filter(Boolean);\n\tconst valid = [\"mainnet\", \"testnet\"];\n\tfor (const n of networks) {\n\t\tif (!valid.includes(n)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid network: ${n}. Must be one of: ${valid.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn networks as (\"mainnet\" | \"testnet\")[];\n});\n\ninterface EnvSchemaOutput {\n\tDATABASE_URL?: string;\n\t/**\n\t * Shared indexer DB (blocks/txs/events). Falls back to DATABASE_URL.\n\t * Set this alongside TARGET_DATABASE_URL to enable dual-DB mode.\n\t */\n\tSOURCE_DATABASE_URL?: string;\n\t/**\n\t * Tenant DB (subgraph schemas + subgraphs table). Falls back to DATABASE_URL.\n\t * Set this alongside SOURCE_DATABASE_URL to enable dual-DB mode.\n\t */\n\tTARGET_DATABASE_URL?: string;\n\tNETWORK?: \"mainnet\" | \"testnet\";\n\tNETWORKS?: (\"mainnet\" | \"testnet\")[];\n\tLOG_LEVEL: \"debug\" | \"info\" | \"warn\" | \"error\";\n\tNODE_ENV: \"development\" | \"production\" | \"test\";\n}\n\n// Cast needed: z.preprocess / z.default create different _input vs _output types\n// that z.ZodType<T> can't represent without explicit input type param\nconst envSchema: z.ZodType<EnvSchemaOutput> = z.object({\n\tDATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tSOURCE_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tTARGET_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tNETWORK: z.enum([\"mainnet\", \"testnet\"]).optional(),\n\tNETWORKS: networksSchema.optional(),\n\tLOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).default(\"info\"),\n\tNODE_ENV: z\n\t\t.enum([\"development\", \"production\", \"test\"])\n\t\t.default(\"development\"),\n}) as unknown as z.ZodType<EnvSchemaOutput>;\n\nexport type Env = EnvSchemaOutput & {\n\tenabledNetworks: (\"mainnet\" | \"testnet\")[];\n};\n\nlet cachedEnv: Env | null = null;\n\nexport function getEnv(): Env {\n\tif (cachedEnv) {\n\t\treturn cachedEnv;\n\t}\n\n\tconst result = envSchema.safeParse(process.env);\n\n\tif (!result.success) {\n\t\tconsole.error(\"❌ Invalid environment configuration:\");\n\t\tconsole.error(z.treeifyError(result.error));\n\t\tthrow new Error(\"Invalid environment configuration\");\n\t}\n\n\t// Compute enabled networks from NETWORKS or NETWORK\n\tlet enabledNetworks: (\"mainnet\" | \"testnet\")[];\n\tif (result.data.NETWORKS && result.data.NETWORKS.length > 0) {\n\t\tenabledNetworks = result.data.NETWORKS;\n\t} else if (result.data.NETWORK) {\n\t\tenabledNetworks = [result.data.NETWORK];\n\t} else {\n\t\tenabledNetworks = [\"mainnet\"]; // Default\n\t}\n\n\tcachedEnv = { ...result.data, enabledNetworks };\n\treturn cachedEnv;\n}\n\n/**\n * True when `NODE_ENV=production`, read at RUNTIME — the single source of truth\n * for prod-vs-dev branching in this package.\n *\n * Why a helper instead of inlining `process.env.NODE_ENV === \"production\"`:\n * bunup/esbuild constant-folds the dot-access `process.env.NODE_ENV` to its\n * BUILD-time value. `@secondlayer/shared` is consumed by other services as its\n * built `dist`, so an inlined check freezes to a literal in the shipped bundle\n * (e.g. `const isProd = false`) and silently ignores the container's real\n * NODE_ENV. The bracket access below is NOT folded, so it stays a runtime read.\n * Route every prod check through here so the footgun lives in exactly one place.\n */\nexport function isProductionEnv(): boolean {\n\t// biome-ignore lint/complexity/useLiteralKeys: bracket access is deliberate — dot-access gets constant-folded by the bundler, freezing this to a build-time literal in the shipped dist.\n\treturn process.env[\"NODE_ENV\"] === \"production\";\n}\n\ntype EnvMap = NodeJS.ProcessEnv;\n\n/**\n * PoX-4 stacking decoder is ON by default — `/v1/index/stacking` is part of the\n * public surface, so the decoder that fills `pox4_calls` runs unless explicitly\n * opted out with `POX4_DECODER_ENABLED=false` (mirrors the sBTC decoder policy).\n */\nexport function isPox4DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX4_DECODER_ENABLED !== \"false\";\n}\n\n/**\n * PoX-5 print-event decoder is ON by default (mirrors pox-4/sBTC policy).\n * No activation gating needed: pox-5 events cannot exist before the Epoch 4.0\n * hard fork (Bitcoin block 960,230), so pre-activation the decoder idles at\n * tip. Opt out with `POX5_DECODER_ENABLED=false`.\n */\nexport function isPox5DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX5_DECODER_ENABLED !== \"false\";\n}\n\n/** Default ON. Opt out with `SBTC_DECODER_ENABLED=false`. */\nexport function isSbtcDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.SBTC_DECODER_ENABLED !== \"false\";\n}\n\n/** Default OFF. Opt in with `BNS_DECODER_ENABLED=true`. */\nexport function isBnsDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.BNS_DECODER_ENABLED === \"true\";\n}\n\n// Export for testing\nexport { envSchema };\n",
|
|
7
7
|
"import { getEnv } from \"./env.ts\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n};\n\nclass Logger {\n\tprivate _level?: LogLevel;\n\tprivate _isProduction?: boolean;\n\tprivate _initialized = false;\n\n\tprivate init() {\n\t\tif (this._initialized) return;\n\t\tthis._initialized = true;\n\t\ttry {\n\t\t\tconst env = getEnv();\n\t\t\tthis._level = env.LOG_LEVEL;\n\t\t\tthis._isProduction = env.NODE_ENV === \"production\";\n\t\t} catch {\n\t\t\t// Fallback when env is unavailable (e.g. tests without DATABASE_URL)\n\t\t\tthis._level = \"info\";\n\t\t\tthis._isProduction = false;\n\t\t}\n\t}\n\n\tprivate get level(): LogLevel {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._level!;\n\t}\n\n\tprivate get isProduction(): boolean {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._isProduction!;\n\t}\n\n\tprivate shouldLog(level: LogLevel): boolean {\n\t\treturn LOG_LEVELS[level] >= LOG_LEVELS[this.level];\n\t}\n\n\tprivate formatMessage(\n\t\tlevel: LogLevel,\n\t\tmessage: string,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tmeta?: Record<string, any>,\n\t) {\n\t\tconst timestamp = new Date().toISOString();\n\n\t\tif (this.isProduction) {\n\t\t\t// JSON output for production\n\t\t\treturn JSON.stringify({\n\t\t\t\ttimestamp,\n\t\t\t\tlevel,\n\t\t\t\tmessage,\n\t\t\t\t...meta,\n\t\t\t});\n\t\t}\n\n\t\t// Human-readable output for development\n\t\tconst metaStr = meta ? ` ${JSON.stringify(meta)}` : \"\";\n\t\treturn `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tdebug(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"debug\")) {\n\t\t\tconsole.debug(this.formatMessage(\"debug\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tinfo(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"info\")) {\n\t\t\tconsole.info(this.formatMessage(\"info\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\twarn(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"warn\")) {\n\t\t\tconsole.warn(this.formatMessage(\"warn\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\terror(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"error\")) {\n\t\t\tconsole.error(this.formatMessage(\"error\", message, meta));\n\t\t}\n\t}\n}\n\n// Export singleton instance\nexport const logger: Logger = new Logger();\n",
|
|
8
8
|
"import { logger } from \"../logger.ts\";\n\n/**\n * The one Resend call every customer email goes through. Never throws when\n * `RESEND_API_KEY` is unset — that's a dev/CI/self-host state, not a send\n * failure — it logs a warning and reports `{ skipped: true }` so a caller\n * can still record its own \"notification sent\" bookkeeping accurately.\n */\n\nexport interface SendEmailInput {\n\tto: string;\n\tsubject: string;\n\thtml: string;\n\ttext: string;\n\t/** Where a reply goes; omitted, replies go to the From address. */\n\treplyTo?: string;\n}\n\nexport type SendEmailResult = { skipped: true } | { skipped: false };\n\nexport async function sendEmail(\n\tinput: SendEmailInput,\n): Promise<SendEmailResult> {\n\tconst apiKey = process.env.RESEND_API_KEY;\n\tif (!apiKey) {\n\t\tlogger.warn(\"RESEND_API_KEY unset — skipping email\", {\n\t\t\tsubject: input.subject,\n\t\t});\n\t\treturn { skipped: true };\n\t}\n\n\tconst from =\n\t\tprocess.env.EMAIL_FROM ?? \"secondlayer <noreply@secondlayer.tools>\";\n\n\tconst response = await fetch(\"https://api.resend.com/emails\", {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tfrom,\n\t\t\tto: [input.to],\n\t\t\tsubject: input.subject,\n\t\t\thtml: input.html,\n\t\t\ttext: input.text,\n\t\t\t...(input.replyTo ? { reply_to: [input.replyTo] } : {}),\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst body = await response.text().catch(() => \"\");\n\t\tthrow new Error(\n\t\t\t`Resend API error (${response.status}): ${body.slice(0, 200)}`,\n\t\t);\n\t}\n\n\treturn { skipped: false };\n}\n"
|
|
9
9
|
],
|
|
10
|
-
"mappings": ";
|
|
11
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "36F0F48E4EBB77FF64756E2164756E21",
|
|
12
12
|
"names": []
|
|
13
13
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -620,6 +620,7 @@ interface InsightsTable {
|
|
|
620
620
|
clear_streak: Generated<number>;
|
|
621
621
|
resolved_at: Date | null;
|
|
622
622
|
dismissed_at: Date | null;
|
|
623
|
+
snoozed_until: Date | null;
|
|
623
624
|
}
|
|
624
625
|
/** "Expected": mutes one rule for one resource until removed. */
|
|
625
626
|
interface InsightMutesTable {
|
|
@@ -1113,6 +1114,7 @@ interface Database {
|
|
|
1113
1114
|
webhooks: WebhooksTable;
|
|
1114
1115
|
webhook_outbox: WebhookOutboxTable;
|
|
1115
1116
|
webhook_deliveries: WebhookDeliveriesTable;
|
|
1117
|
+
webhook_replays: WebhookReplaysTable;
|
|
1116
1118
|
trigger_evaluator_state: TriggerEvaluatorStateTable;
|
|
1117
1119
|
decoded_events: DecodedEventsTable;
|
|
1118
1120
|
decoder_checkpoints: DecoderCheckpointsTable;
|
|
@@ -1161,6 +1163,9 @@ interface Database {
|
|
|
1161
1163
|
insights: InsightsTable;
|
|
1162
1164
|
insight_mutes: InsightMutesTable;
|
|
1163
1165
|
feedback_tickets: FeedbackTicketsTable;
|
|
1166
|
+
push_subscriptions: PushSubscriptionsTable;
|
|
1167
|
+
push_prefs: PushPrefsTable;
|
|
1168
|
+
push_sent: PushSentTable;
|
|
1164
1169
|
}
|
|
1165
1170
|
interface ServiceHeartbeatsTable {
|
|
1166
1171
|
name: string;
|
|
@@ -1266,6 +1271,38 @@ interface WebhookIncidentAlertsTable {
|
|
|
1266
1271
|
resolved_at: Date | null;
|
|
1267
1272
|
}
|
|
1268
1273
|
type WebhookIncidentAlert = Selectable<WebhookIncidentAlertsTable>;
|
|
1274
|
+
/** One browser/device push subscription; the endpoint is unique. */
|
|
1275
|
+
interface PushSubscriptionsTable {
|
|
1276
|
+
id: Generated<string>;
|
|
1277
|
+
account_id: string;
|
|
1278
|
+
endpoint: string;
|
|
1279
|
+
p256dh: string;
|
|
1280
|
+
auth: string;
|
|
1281
|
+
user_agent: string | null;
|
|
1282
|
+
created_at: Generated<Date>;
|
|
1283
|
+
last_success_at: Date | null;
|
|
1284
|
+
failed_count: Generated<number>;
|
|
1285
|
+
}
|
|
1286
|
+
type PushSubscription = Selectable<PushSubscriptionsTable>;
|
|
1287
|
+
/** Per-account push toggles. A missing row means the column defaults. */
|
|
1288
|
+
interface PushPrefsTable {
|
|
1289
|
+
account_id: string;
|
|
1290
|
+
webhook_failing: Generated<boolean>;
|
|
1291
|
+
subgraph_stopped: Generated<boolean>;
|
|
1292
|
+
subgraph_not_serving: Generated<boolean>;
|
|
1293
|
+
replay_done: Generated<boolean>;
|
|
1294
|
+
spend_cap: Generated<boolean>;
|
|
1295
|
+
key_repeat: Generated<boolean>;
|
|
1296
|
+
updated_at: Generated<Date>;
|
|
1297
|
+
}
|
|
1298
|
+
type PushPrefs = Selectable<PushPrefsTable>;
|
|
1299
|
+
/** A push already sent, keyed by what it was about; debounces repeat signals. */
|
|
1300
|
+
interface PushSentTable {
|
|
1301
|
+
account_id: string;
|
|
1302
|
+
key: string;
|
|
1303
|
+
kind: string;
|
|
1304
|
+
sent_at: Generated<Date>;
|
|
1305
|
+
}
|
|
1269
1306
|
type CliDeviceLoginStatus = "pending" | "approved" | "denied" | "consumed";
|
|
1270
1307
|
/** The product that started a CLI sign-in; null is `sl login`. */
|
|
1271
1308
|
type CliDeviceProduct = "sentinel";
|
|
@@ -1460,6 +1497,8 @@ interface WebhookOutboxTable {
|
|
|
1460
1497
|
next_attempt_at: Generated<Date>;
|
|
1461
1498
|
status: ColumnType<OutboxStatus, OutboxStatus | undefined, OutboxStatus>;
|
|
1462
1499
|
is_replay: Generated<boolean>;
|
|
1500
|
+
/** The replay that created this row; null for live rows. */
|
|
1501
|
+
replay_id: string | null;
|
|
1463
1502
|
/** The chain block_time of the matched event/tx. Null for rows written
|
|
1464
1503
|
* before this column existed, and for the settlement scan path (not yet
|
|
1465
1504
|
* wired). Lets `delivered_at - block_time` measure end-to-end webhook
|
|
@@ -1475,7 +1514,28 @@ interface WebhookOutboxTable {
|
|
|
1475
1514
|
last_error: string | null;
|
|
1476
1515
|
created_at: Generated<Date>;
|
|
1477
1516
|
}
|
|
1478
|
-
type
|
|
1517
|
+
type WebhookReplayStatus = "scanning" | "running" | "done" | "failed";
|
|
1518
|
+
interface WebhookReplaysTable {
|
|
1519
|
+
id: string;
|
|
1520
|
+
webhook_id: string;
|
|
1521
|
+
from_block: number | bigint;
|
|
1522
|
+
to_block: number | bigint;
|
|
1523
|
+
status: WebhookReplayStatus;
|
|
1524
|
+
scanned_count: Generated<number>;
|
|
1525
|
+
enqueued_count: Generated<number>;
|
|
1526
|
+
total: number | null;
|
|
1527
|
+
delivered: number | null;
|
|
1528
|
+
gave_up: number | null;
|
|
1529
|
+
error: string | null;
|
|
1530
|
+
started_at: Generated<Date>;
|
|
1531
|
+
finished_at: Date | null;
|
|
1532
|
+
}
|
|
1533
|
+
type WebhookReplay = Selectable<WebhookReplaysTable>;
|
|
1534
|
+
/** `replay_id` is optional on the row type so hand-built rows (the test
|
|
1535
|
+
* delivery, fixtures) need not carry it; reads still return it. */
|
|
1536
|
+
type WebhookOutbox = Omit<Selectable<WebhookOutboxTable>, "replay_id"> & {
|
|
1537
|
+
replay_id?: string | null
|
|
1538
|
+
};
|
|
1479
1539
|
type InsertWebhookOutbox = Insertable<WebhookOutboxTable>;
|
|
1480
1540
|
type UpdateWebhookOutbox = Updateable<WebhookOutboxTable>;
|
|
1481
1541
|
interface WebhookDeliveriesTable {
|
|
@@ -2642,6 +2702,7 @@ declare const TABLE_TO_DB: {
|
|
|
2642
2702
|
webhooks: string
|
|
2643
2703
|
webhook_outbox: string
|
|
2644
2704
|
webhook_deliveries: string
|
|
2705
|
+
webhook_replays: string
|
|
2645
2706
|
trigger_evaluator_state: string
|
|
2646
2707
|
subgraphs: string
|
|
2647
2708
|
subgraph_operations: string
|
|
@@ -2664,6 +2725,9 @@ declare const TABLE_TO_DB: {
|
|
|
2664
2725
|
insights: string
|
|
2665
2726
|
insight_mutes: string
|
|
2666
2727
|
feedback_tickets: string
|
|
2728
|
+
push_subscriptions: string
|
|
2729
|
+
push_prefs: string
|
|
2730
|
+
push_sent: string
|
|
2667
2731
|
service_heartbeats: string
|
|
2668
2732
|
};
|
|
2669
2733
|
/** OSS: one application URL. SOURCE_/TARGET_ are hosted split only. */
|
|
@@ -3762,4 +3826,4 @@ declare function publicKeyPemFromPrivate(privateKeyPem: string): string;
|
|
|
3762
3826
|
declare function ed25519KeyId(publicKeyPem: string): string;
|
|
3763
3827
|
declare function signEd25519(payload: string, privateKey: KeyObject): string;
|
|
3764
3828
|
declare function verifyEd25519(payload: string, signatureBase64: string, publicKey: KeyObject): boolean;
|
|
3765
|
-
export { Account, AccountBalanceAlerts, AccountBalanceAlertsTable, AccountCredits, AccountCreditsTable, AccountSpendCap, AccountSpendCapsTable, AccountsTable, ApiFailedRequest, ApiFailedRequestsTable, ApiKey, ApiKeyRevokedError, ApiKeysTable, ApiRequestLogRow, ApiRequestLogTable, ArchiveFetch, ArchiveFetchesTable, AuthenticationError, AuthorizationError, BILLING_PAUSED_PREFIX, Block, BlocksTable, BnsMarketplaceAction, BnsMarketplaceEventsTable, BnsNameEventTopic, BnsNameEventsTable, BnsNamesTable, BnsNamespaceEventStatus, BnsNamespaceEventsTable, BnsNamespacesTable, BurnBlockRewardSlotsTable, BurnBlockRewardsTable, CHAIN_TRIGGER_FIELDS, CHAIN_TRIGGER_TYPES, CODE_TO_STATUS, ChainApplyDeliveryOf, ChainApplyEnvelope, ChainApplyEnvelopeOf, ChainEventEnvelope, ChainFtBurnData, ChainFtMintData, ChainFtTransferData, ChainNftBurnData, ChainNftMintData, ChainNftTransferData, ChainPrintEventData, ChainReadCacheTable, ChainReorgOrphanedEntry, ChainReorgRollbackDelivery, ChainReorgRollbackEnvelope, ChainReorgsTable, ChainStxBurnData, ChainStxLockData, ChainStxMintData, ChainStxTransferData, ChainTestDelivery, ChainTxLevelEvent, ChainWebhookDelivery, ChainWebhookEnvelope, CliDeviceLogin, CliDeviceLoginStatus, CliDeviceLoginsTable, CliDeviceProduct, ContactRequest, ContactRequestsTable, Contract, ContractsTable, CoverageSegmentsTable, CreateWebhookRequest, CreateWebhookRequestSchema, CreateWebhookResponse, DB_TO_STREAMS_EVENT_TYPE, DECODED_EVENT_TYPES2 as DECODED_EVENT_TYPES, DEFAULT_BTC_CONFIRMATIONS, Database, DatabaseError, DbPlane, DbReadRow, DbSplitStatus, DeadLetterEventsTable, DeadRow, DecodedEventType2 as DecodedEventType, DecodedEventsTable, DecoderCheckpointsTable, DeliveryRow, DeploySubgraphRequest, DeploySubgraphRequestSchema, DeploySubgraphResponse, EMPTY_RANGE_EVENT_INDEX_SENTINEL, Env, ErrorCode, ErrorCodes, Event, EventsArchiveTable, EventsTable, FailureClass, FailureUnit, FeedbackTicket, FeedbackTicketsTable, ForbiddenError, HealthHour, HealthSnapshotPoint, INSIGHT_SEVERITIES, IndexColumn, IndexColumnType, IndexPrimaryKey, IndexProgress, IndexProgressTable, InsertAccount, InsertAccountBalanceAlerts, InsertAccountCredits, InsertAccountSpendCap, InsertApiFailedRequest, InsertApiKey, InsertApiRequestLogRow, InsertArchiveFetch, InsertBlock, InsertContactRequest, InsertContract, InsertEvent, InsertFeedbackTicket, InsertIndexProgress, InsertMagicLink, InsertMempoolTransaction, InsertSession, InsertStateWrite, InsertSubgraph, InsertSubgraphGap, InsertSubgraphHealthSnapshot, InsertSubgraphOperation, InsertTransaction, InsertUsageLedgerRow, InsertVmEvent, InsertWaitlistSignup, InsertWebhook, InsertWebhookDelivery, InsertWebhookOutbox, Insight, InsightEvidence, InsightEvidenceSchema, InsightListResponse, InsightListResponseSchema, InsightMutesTable, InsightResourceKind, InsightResourceKindSchema, InsightSchema, InsightSeverity, InsightSeveritySchema, InsightsTable, InstancesTable, KeyRotatedError, MagicLink, MagicLinksTable, MempoolTransaction, MempoolTransactionsTable, MigrationRole, NativeClock, NotFoundError, NumericAsText, OauthClient, OauthClientsTable, OauthCodesTable, OauthGrant, OauthGrantsTable, OauthRefreshRetiredTable, OauthRequestStatus, OauthRequestsTable, OauthTokenKind, OauthTokensTable, ObserverJournalStatus, ObserverJournalTable, OutboxStatus, ParsedCreateWebhookRequest, ParsedReplayWebhookRequest, ParsedUpdateWebhookRequest, PaymentRequiredError, PendingForkBlocksTable, Pox4CallsTable, Pox4FunctionName, Pox5CycleSignersTable, Pox5CyclesTable, Pox5EventTopic, Pox5EventsTable, ProcessedStripeEventsTable, RUNE_EVENT_TYPES, RateLimitError, ReindexResponse, RepairMode, ReplayResult, ReplayWebhookRequest, ReplayWebhookRequestSchema, RetryState, RotateSecretResponse, RunStatus, RuneApplyDeliveryOf, RuneApplyEnvelope, RuneEventType, SEVERITY_ORDER, SOURCE_READ_COLUMNS, SOURCE_READ_PKS, SOURCE_READ_TYPES, STREAMS_DB_EVENT_TYPES, STREAMS_EVENT_TYPES, STREAMS_TO_DB_EVENT_TYPES, SbtcDepositEvent, SbtcEventTopic, SbtcEventsTable, SbtcSettlementsTable, SbtcTokenEventType, SbtcTokenEventsTable, SbtcWithdrawalEvent, SbtcWithdrawalSweptConfirmedEvent, SecondLayerError, SentinelAccount, SentinelAccountsTable, ServiceHeartbeatsTable, Session, SessionsTable, StageBlockReceiptsTable, StageFailuresTable, StageKind, StageRegistryTable, StageRunsTable, StateWrite, StateWritesArchiveTable, StateWritesTable, StreamsCursor, StreamsDbEventType, StreamsEventType, Subgraph, SubgraphAgentSchema, SubgraphAggregateParams, SubgraphAggregateResponse, SubgraphDetail, SubgraphErrorGroupsTable, SubgraphGap, SubgraphGapEntry, SubgraphGapRange, SubgraphGapsResponse, SubgraphGapsTable, SubgraphHealthSnapshot, SubgraphHealthSnapshotsTable, SubgraphOperation, SubgraphOperationKind, SubgraphOperationStatus, SubgraphOperationsTable, SubgraphProcessingStatsTable, SubgraphQueryParams, SubgraphSpecFormat, SubgraphSpecOptions, SubgraphSummary, SubgraphSyncInfo, SubgraphVerification, SubgraphViolationsTable, SubgraphsTable, SyncScopesTable, TABLE_TO_DB, TenantSuspendedError, Transaction, TransactionsArchiveTable, TransactionsTable, TriggerEvaluatorState, TriggerEvaluatorStateTable, UpdateAccountBalanceAlerts, UpdateAccountCredits, UpdateAccountSpendCap, UpdateApiKey, UpdateArchiveFetch, UpdateBlock, UpdateContract, UpdateEvent, UpdateIndexProgress, UpdateSubgraph, UpdateSubgraphOperation, UpdateTransaction, UpdateUsageLedgerRow, UpdateVmEvent, UpdateWebhook, UpdateWebhookOutbox, UpdateWebhookRequest, UpdateWebhookRequestSchema, UsageLedgerRow, UsageLedgerTable, VM_EVENT_TYPES, VM_NODE_TO_STORED_TYPE2 as VM_NODE_TO_STORED_TYPE, VM_STORED_TO_NODE_TYPE, ValidationError, VersionConflictError, VmEvent, VmEventType, VmEventsArchiveTable, VmEventsTable, VmNodeEventType, WEBHOOK_FORMATS, WEBHOOK_STATUSES, Waitlist, WaitlistSignup, WaitlistSignupsTable, WaitlistsTable, Webhook, WebhookDeliveriesTable, WebhookDelivery, WebhookDetail, WebhookFormat, WebhookFormatSchema, WebhookIncidentAlert, WebhookIncidentAlertsTable, WebhookKind, WebhookOutbox, WebhookOutboxTable, WebhookStatus, WebhookStatusSchema, WebhookSummary, WebhooksTable, applicationDatabaseUrl, assertDbSplit, billingPausedCode, blockEndCursor, bucketHourly, closeDb, closeDbOrTimeout, committedHeight, compareStreamsCursor, exports_hmac as crypto, decodeStreamsCursor, describeDbUrl, exports_ed25519 as ed25519, encodeStreamsCursor, finalizedBurnHeight, formatWebhookSchemaErrors, generateSubgraphAgentSchema, generateSubgraphMarkdown, generateSubgraphOpenApi, generateSubgraphSpec, getDb, getDbSplitStatus, getEnv, getErrorMessage, getMigrationRole, getRawClient, getSourceDb, getTargetDb, isBnsDecoderEnabled, isEmptyRangeCursor, isPox4DecoderEnabled, isPox5DecoderEnabled, isSbtcDecoderEnabled, jsonb, logger, onChainPlane, onControlPlane, parseJsonb, resolveSourceUrl, resolveTargetUrl, setMigrationRole, sql };
|
|
3829
|
+
export { Account, AccountBalanceAlerts, AccountBalanceAlertsTable, AccountCredits, AccountCreditsTable, AccountSpendCap, AccountSpendCapsTable, AccountsTable, ApiFailedRequest, ApiFailedRequestsTable, ApiKey, ApiKeyRevokedError, ApiKeysTable, ApiRequestLogRow, ApiRequestLogTable, ArchiveFetch, ArchiveFetchesTable, AuthenticationError, AuthorizationError, BILLING_PAUSED_PREFIX, Block, BlocksTable, BnsMarketplaceAction, BnsMarketplaceEventsTable, BnsNameEventTopic, BnsNameEventsTable, BnsNamesTable, BnsNamespaceEventStatus, BnsNamespaceEventsTable, BnsNamespacesTable, BurnBlockRewardSlotsTable, BurnBlockRewardsTable, CHAIN_TRIGGER_FIELDS, CHAIN_TRIGGER_TYPES, CODE_TO_STATUS, ChainApplyDeliveryOf, ChainApplyEnvelope, ChainApplyEnvelopeOf, ChainEventEnvelope, ChainFtBurnData, ChainFtMintData, ChainFtTransferData, ChainNftBurnData, ChainNftMintData, ChainNftTransferData, ChainPrintEventData, ChainReadCacheTable, ChainReorgOrphanedEntry, ChainReorgRollbackDelivery, ChainReorgRollbackEnvelope, ChainReorgsTable, ChainStxBurnData, ChainStxLockData, ChainStxMintData, ChainStxTransferData, ChainTestDelivery, ChainTxLevelEvent, ChainWebhookDelivery, ChainWebhookEnvelope, CliDeviceLogin, CliDeviceLoginStatus, CliDeviceLoginsTable, CliDeviceProduct, ContactRequest, ContactRequestsTable, Contract, ContractsTable, CoverageSegmentsTable, CreateWebhookRequest, CreateWebhookRequestSchema, CreateWebhookResponse, DB_TO_STREAMS_EVENT_TYPE, DECODED_EVENT_TYPES2 as DECODED_EVENT_TYPES, DEFAULT_BTC_CONFIRMATIONS, Database, DatabaseError, DbPlane, DbReadRow, DbSplitStatus, DeadLetterEventsTable, DeadRow, DecodedEventType2 as DecodedEventType, DecodedEventsTable, DecoderCheckpointsTable, DeliveryRow, DeploySubgraphRequest, DeploySubgraphRequestSchema, DeploySubgraphResponse, EMPTY_RANGE_EVENT_INDEX_SENTINEL, Env, ErrorCode, ErrorCodes, Event, EventsArchiveTable, EventsTable, FailureClass, FailureUnit, FeedbackTicket, FeedbackTicketsTable, ForbiddenError, HealthHour, HealthSnapshotPoint, INSIGHT_SEVERITIES, IndexColumn, IndexColumnType, IndexPrimaryKey, IndexProgress, IndexProgressTable, InsertAccount, InsertAccountBalanceAlerts, InsertAccountCredits, InsertAccountSpendCap, InsertApiFailedRequest, InsertApiKey, InsertApiRequestLogRow, InsertArchiveFetch, InsertBlock, InsertContactRequest, InsertContract, InsertEvent, InsertFeedbackTicket, InsertIndexProgress, InsertMagicLink, InsertMempoolTransaction, InsertSession, InsertStateWrite, InsertSubgraph, InsertSubgraphGap, InsertSubgraphHealthSnapshot, InsertSubgraphOperation, InsertTransaction, InsertUsageLedgerRow, InsertVmEvent, InsertWaitlistSignup, InsertWebhook, InsertWebhookDelivery, InsertWebhookOutbox, Insight, InsightEvidence, InsightEvidenceSchema, InsightListResponse, InsightListResponseSchema, InsightMutesTable, InsightResourceKind, InsightResourceKindSchema, InsightSchema, InsightSeverity, InsightSeveritySchema, InsightsTable, InstancesTable, KeyRotatedError, MagicLink, MagicLinksTable, MempoolTransaction, MempoolTransactionsTable, MigrationRole, NativeClock, NotFoundError, NumericAsText, OauthClient, OauthClientsTable, OauthCodesTable, OauthGrant, OauthGrantsTable, OauthRefreshRetiredTable, OauthRequestStatus, OauthRequestsTable, OauthTokenKind, OauthTokensTable, ObserverJournalStatus, ObserverJournalTable, OutboxStatus, ParsedCreateWebhookRequest, ParsedReplayWebhookRequest, ParsedUpdateWebhookRequest, PaymentRequiredError, PendingForkBlocksTable, Pox4CallsTable, Pox4FunctionName, Pox5CycleSignersTable, Pox5CyclesTable, Pox5EventTopic, Pox5EventsTable, ProcessedStripeEventsTable, PushPrefs, PushPrefsTable, PushSentTable, PushSubscription, PushSubscriptionsTable, RUNE_EVENT_TYPES, RateLimitError, ReindexResponse, RepairMode, ReplayResult, ReplayWebhookRequest, ReplayWebhookRequestSchema, RetryState, RotateSecretResponse, RunStatus, RuneApplyDeliveryOf, RuneApplyEnvelope, RuneEventType, SEVERITY_ORDER, SOURCE_READ_COLUMNS, SOURCE_READ_PKS, SOURCE_READ_TYPES, STREAMS_DB_EVENT_TYPES, STREAMS_EVENT_TYPES, STREAMS_TO_DB_EVENT_TYPES, SbtcDepositEvent, SbtcEventTopic, SbtcEventsTable, SbtcSettlementsTable, SbtcTokenEventType, SbtcTokenEventsTable, SbtcWithdrawalEvent, SbtcWithdrawalSweptConfirmedEvent, SecondLayerError, SentinelAccount, SentinelAccountsTable, ServiceHeartbeatsTable, Session, SessionsTable, StageBlockReceiptsTable, StageFailuresTable, StageKind, StageRegistryTable, StageRunsTable, StateWrite, StateWritesArchiveTable, StateWritesTable, StreamsCursor, StreamsDbEventType, StreamsEventType, Subgraph, SubgraphAgentSchema, SubgraphAggregateParams, SubgraphAggregateResponse, SubgraphDetail, SubgraphErrorGroupsTable, SubgraphGap, SubgraphGapEntry, SubgraphGapRange, SubgraphGapsResponse, SubgraphGapsTable, SubgraphHealthSnapshot, SubgraphHealthSnapshotsTable, SubgraphOperation, SubgraphOperationKind, SubgraphOperationStatus, SubgraphOperationsTable, SubgraphProcessingStatsTable, SubgraphQueryParams, SubgraphSpecFormat, SubgraphSpecOptions, SubgraphSummary, SubgraphSyncInfo, SubgraphVerification, SubgraphViolationsTable, SubgraphsTable, SyncScopesTable, TABLE_TO_DB, TenantSuspendedError, Transaction, TransactionsArchiveTable, TransactionsTable, TriggerEvaluatorState, TriggerEvaluatorStateTable, UpdateAccountBalanceAlerts, UpdateAccountCredits, UpdateAccountSpendCap, UpdateApiKey, UpdateArchiveFetch, UpdateBlock, UpdateContract, UpdateEvent, UpdateIndexProgress, UpdateSubgraph, UpdateSubgraphOperation, UpdateTransaction, UpdateUsageLedgerRow, UpdateVmEvent, UpdateWebhook, UpdateWebhookOutbox, UpdateWebhookRequest, UpdateWebhookRequestSchema, UsageLedgerRow, UsageLedgerTable, VM_EVENT_TYPES, VM_NODE_TO_STORED_TYPE2 as VM_NODE_TO_STORED_TYPE, VM_STORED_TO_NODE_TYPE, ValidationError, VersionConflictError, VmEvent, VmEventType, VmEventsArchiveTable, VmEventsTable, VmNodeEventType, WEBHOOK_FORMATS, WEBHOOK_STATUSES, Waitlist, WaitlistSignup, WaitlistSignupsTable, WaitlistsTable, Webhook, WebhookDeliveriesTable, WebhookDelivery, WebhookDetail, WebhookFormat, WebhookFormatSchema, WebhookIncidentAlert, WebhookIncidentAlertsTable, WebhookKind, WebhookOutbox, WebhookOutboxTable, WebhookReplay, WebhookReplayStatus, WebhookReplaysTable, WebhookStatus, WebhookStatusSchema, WebhookSummary, WebhooksTable, applicationDatabaseUrl, assertDbSplit, billingPausedCode, blockEndCursor, bucketHourly, closeDb, closeDbOrTimeout, committedHeight, compareStreamsCursor, exports_hmac as crypto, decodeStreamsCursor, describeDbUrl, exports_ed25519 as ed25519, encodeStreamsCursor, finalizedBurnHeight, formatWebhookSchemaErrors, generateSubgraphAgentSchema, generateSubgraphMarkdown, generateSubgraphOpenApi, generateSubgraphSpec, getDb, getDbSplitStatus, getEnv, getErrorMessage, getMigrationRole, getRawClient, getSourceDb, getTargetDb, isBnsDecoderEnabled, isEmptyRangeCursor, isPox4DecoderEnabled, isPox5DecoderEnabled, isSbtcDecoderEnabled, jsonb, logger, onChainPlane, onControlPlane, parseJsonb, resolveSourceUrl, resolveTargetUrl, setMigrationRole, sql };
|
package/dist/src/index.js
CHANGED
|
@@ -855,6 +855,7 @@ var TABLE_TO_DB = {
|
|
|
855
855
|
webhooks: "target",
|
|
856
856
|
webhook_outbox: "target",
|
|
857
857
|
webhook_deliveries: "target",
|
|
858
|
+
webhook_replays: "target",
|
|
858
859
|
trigger_evaluator_state: "target",
|
|
859
860
|
subgraphs: "target",
|
|
860
861
|
subgraph_operations: "target",
|
|
@@ -877,6 +878,9 @@ var TABLE_TO_DB = {
|
|
|
877
878
|
insights: "target",
|
|
878
879
|
insight_mutes: "target",
|
|
879
880
|
feedback_tickets: "target",
|
|
881
|
+
push_subscriptions: "target",
|
|
882
|
+
push_prefs: "target",
|
|
883
|
+
push_sent: "target",
|
|
880
884
|
service_heartbeats: "both"
|
|
881
885
|
};
|
|
882
886
|
|
|
@@ -1718,6 +1722,10 @@ var ReplayWebhookRequestSchema = z4.object({
|
|
|
1718
1722
|
message: "fromBlock must be less than or equal to toBlock",
|
|
1719
1723
|
path: ["toBlock"]
|
|
1720
1724
|
});
|
|
1725
|
+
var RequeueDeadRequestSchema = z4.object({
|
|
1726
|
+
replayId: z4.string().regex(/^[0-9a-f]{16}$/, "replayId must be 16 hex characters").optional(),
|
|
1727
|
+
since: z4.iso.datetime({ offset: true }).optional()
|
|
1728
|
+
});
|
|
1721
1729
|
var trigger = {
|
|
1722
1730
|
stxTransfer: (f = {}) => ({
|
|
1723
1731
|
type: "stx_transfer",
|
|
@@ -2408,5 +2416,5 @@ export {
|
|
|
2408
2416
|
sql2 as sql
|
|
2409
2417
|
};
|
|
2410
2418
|
|
|
2411
|
-
//# debugId=
|
|
2419
|
+
//# debugId=4C61DE1D3131B7BB64756E2164756E21
|
|
2412
2420
|
//# sourceMappingURL=index.js.map
|