dkimkit 0.0.0-stage → 0.6.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/README.md +153 -2
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +398 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/dns-host.d.ts +10 -0
- package/dist/src/dns-host.js +31 -0
- package/dist/src/dns-host.js.map +1 -0
- package/dist/src/errors.d.ts +17 -0
- package/dist/src/errors.js +51 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/hosted/api.d.ts +95 -0
- package/dist/src/hosted/api.js +781 -0
- package/dist/src/hosted/api.js.map +1 -0
- package/dist/src/hosted/auth.d.ts +70 -0
- package/dist/src/hosted/auth.js +121 -0
- package/dist/src/hosted/auth.js.map +1 -0
- package/dist/src/hosted/billing-dodo.d.ts +58 -0
- package/dist/src/hosted/billing-dodo.js +139 -0
- package/dist/src/hosted/billing-dodo.js.map +1 -0
- package/dist/src/hosted/billing-stripe.d.ts +82 -0
- package/dist/src/hosted/billing-stripe.js +205 -0
- package/dist/src/hosted/billing-stripe.js.map +1 -0
- package/dist/src/hosted/billing.d.ts +114 -0
- package/dist/src/hosted/billing.js +173 -0
- package/dist/src/hosted/billing.js.map +1 -0
- package/dist/src/hosted/cli.d.ts +2 -0
- package/dist/src/hosted/cli.js +305 -0
- package/dist/src/hosted/cli.js.map +1 -0
- package/dist/src/hosted/crypto.d.ts +22 -0
- package/dist/src/hosted/crypto.js +55 -0
- package/dist/src/hosted/crypto.js.map +1 -0
- package/dist/src/hosted/index.d.ts +7 -0
- package/dist/src/hosted/index.js +11 -0
- package/dist/src/hosted/index.js.map +1 -0
- package/dist/src/hosted/markdown.d.ts +16 -0
- package/dist/src/hosted/markdown.js +168 -0
- package/dist/src/hosted/markdown.js.map +1 -0
- package/dist/src/hosted/net.d.ts +9 -0
- package/dist/src/hosted/net.js +60 -0
- package/dist/src/hosted/net.js.map +1 -0
- package/dist/src/hosted/postgres-store.d.ts +62 -0
- package/dist/src/hosted/postgres-store.js +387 -0
- package/dist/src/hosted/postgres-store.js.map +1 -0
- package/dist/src/hosted/serialize.d.ts +144 -0
- package/dist/src/hosted/serialize.js +126 -0
- package/dist/src/hosted/serialize.js.map +1 -0
- package/dist/src/hosted/service.d.ts +166 -0
- package/dist/src/hosted/service.js +582 -0
- package/dist/src/hosted/service.js.map +1 -0
- package/dist/src/hosted/site.d.ts +54 -0
- package/dist/src/hosted/site.js +472 -0
- package/dist/src/hosted/site.js.map +1 -0
- package/dist/src/hosted/sqlite-store.d.ts +45 -0
- package/dist/src/hosted/sqlite-store.js +351 -0
- package/dist/src/hosted/sqlite-store.js.map +1 -0
- package/dist/src/hosted/store.d.ts +257 -0
- package/dist/src/hosted/store.js +181 -0
- package/dist/src/hosted/store.js.map +1 -0
- package/dist/src/hosted/widget.d.ts +46 -0
- package/dist/src/hosted/widget.js +130 -0
- package/dist/src/hosted/widget.js.map +1 -0
- package/dist/src/hosted/worker.d.ts +45 -0
- package/dist/src/hosted/worker.js +142 -0
- package/dist/src/hosted/worker.js.map +1 -0
- package/dist/src/http.d.ts +14 -0
- package/dist/src/http.js +54 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/orchestrator.d.ts +70 -0
- package/dist/src/orchestrator.js +69 -0
- package/dist/src/orchestrator.js.map +1 -0
- package/dist/src/propagation.d.ts +30 -0
- package/dist/src/propagation.js +76 -0
- package/dist/src/propagation.js.map +1 -0
- package/dist/src/providers/dns/aws.d.ts +28 -0
- package/dist/src/providers/dns/aws.js +103 -0
- package/dist/src/providers/dns/aws.js.map +1 -0
- package/dist/src/providers/dns/cloudflare.d.ts +26 -0
- package/dist/src/providers/dns/cloudflare.js +97 -0
- package/dist/src/providers/dns/cloudflare.js.map +1 -0
- package/dist/src/providers/dns/manual.d.ts +12 -0
- package/dist/src/providers/dns/manual.js +18 -0
- package/dist/src/providers/dns/manual.js.map +1 -0
- package/dist/src/providers/dns/route53.d.ts +34 -0
- package/dist/src/providers/dns/route53.js +176 -0
- package/dist/src/providers/dns/route53.js.map +1 -0
- package/dist/src/providers/email/factory.d.ts +29 -0
- package/dist/src/providers/email/factory.js +67 -0
- package/dist/src/providers/email/factory.js.map +1 -0
- package/dist/src/providers/email/mailgun.d.ts +36 -0
- package/dist/src/providers/email/mailgun.js +141 -0
- package/dist/src/providers/email/mailgun.js.map +1 -0
- package/dist/src/providers/email/postmark.d.ts +37 -0
- package/dist/src/providers/email/postmark.js +121 -0
- package/dist/src/providers/email/postmark.js.map +1 -0
- package/dist/src/providers/email/sendgrid.d.ts +31 -0
- package/dist/src/providers/email/sendgrid.js +109 -0
- package/dist/src/providers/email/sendgrid.js.map +1 -0
- package/dist/src/providers/email/ses.d.ts +39 -0
- package/dist/src/providers/email/ses.js +123 -0
- package/dist/src/providers/email/ses.js.map +1 -0
- package/dist/src/providers/factory.d.ts +14 -0
- package/dist/src/providers/factory.js +32 -0
- package/dist/src/providers/factory.js.map +1 -0
- package/dist/src/records.d.ts +35 -0
- package/dist/src/records.js +109 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/server.d.ts +67 -0
- package/dist/src/server.js +301 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types.d.ts +75 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/xml.d.ts +6 -0
- package/dist/src/xml.js +23 -0
- package/dist/src/xml.js.map +1 -0
- package/docs/billing.md +43 -0
- package/docs/getting-started.md +57 -0
- package/docs/hosted-api.md +192 -0
- package/docs/self-hosting.md +148 -0
- package/docs/widget.md +121 -0
- package/package.json +63 -4
- package/web/dashboard.html +390 -0
- package/web/index.html +697 -0
- package/web/widget-frame.html +480 -0
- package/web/widget.js +91 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface SiteOptions {
|
|
2
|
+
appName?: string;
|
|
3
|
+
/** Who operates the service: a person's or business's legal name. */
|
|
4
|
+
legalName?: string;
|
|
5
|
+
contactEmail?: string;
|
|
6
|
+
/** Whose law governs the terms, e.g. "India". */
|
|
7
|
+
legalCountry?: string;
|
|
8
|
+
/** "Last updated" on the legal pages, YYYY-MM-DD. */
|
|
9
|
+
effectiveDate?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SiteContext extends SiteOptions {
|
|
12
|
+
publicUrl: string;
|
|
13
|
+
signupEnabled: boolean;
|
|
14
|
+
/** Present when billing is on; drives the pricing page. */
|
|
15
|
+
plan?: {
|
|
16
|
+
freeDomains: number;
|
|
17
|
+
unitAmount: number;
|
|
18
|
+
currency: string;
|
|
19
|
+
provider: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface Page {
|
|
23
|
+
status: number;
|
|
24
|
+
headers: Record<string, string>;
|
|
25
|
+
body: string;
|
|
26
|
+
}
|
|
27
|
+
export declare class Site {
|
|
28
|
+
private readonly ctx;
|
|
29
|
+
readonly appName: string;
|
|
30
|
+
private readonly docs;
|
|
31
|
+
constructor(ctx: SiteContext);
|
|
32
|
+
/** Missing settings show as a visible placeholder, so they're noticed before launch. */
|
|
33
|
+
missingSettings(): string[];
|
|
34
|
+
/** Routes a request path to a page, or null if it isn't a site page. */
|
|
35
|
+
render(path: string): Page | null;
|
|
36
|
+
private money;
|
|
37
|
+
private docLink;
|
|
38
|
+
/** A value the operator must configure; shown highlighted until it is. */
|
|
39
|
+
private setting;
|
|
40
|
+
private get operator();
|
|
41
|
+
private get email();
|
|
42
|
+
private get effective();
|
|
43
|
+
private cta;
|
|
44
|
+
private page;
|
|
45
|
+
private landing;
|
|
46
|
+
private pricing;
|
|
47
|
+
private about;
|
|
48
|
+
private contact;
|
|
49
|
+
private docPage;
|
|
50
|
+
private legal;
|
|
51
|
+
private terms;
|
|
52
|
+
private privacy;
|
|
53
|
+
private refunds;
|
|
54
|
+
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { renderMarkdown } from './markdown.js';
|
|
3
|
+
const DEFAULT_APP_NAME = 'DKIMkit';
|
|
4
|
+
const DEFAULT_EFFECTIVE_DATE = '2026-10-25';
|
|
5
|
+
const DOCS_DIR = new URL('../../../docs/', import.meta.url);
|
|
6
|
+
/** Public docs, in sidebar order. Other files in docs/ (e.g. self-hosting.md) are not published. */
|
|
7
|
+
const DOCS = [
|
|
8
|
+
{ slug: 'getting-started', file: 'getting-started.md', label: 'Getting started' },
|
|
9
|
+
{ slug: 'hosted-api', file: 'hosted-api.md', label: 'API reference' },
|
|
10
|
+
{ slug: 'widget', file: 'widget.md', label: 'Embeddable widget' },
|
|
11
|
+
{ slug: 'billing', file: 'billing.md', label: 'Plans and billing', needsBilling: true },
|
|
12
|
+
];
|
|
13
|
+
const esc = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
14
|
+
export class Site {
|
|
15
|
+
ctx;
|
|
16
|
+
appName;
|
|
17
|
+
docs = new Map();
|
|
18
|
+
constructor(ctx) {
|
|
19
|
+
this.ctx = ctx;
|
|
20
|
+
this.appName = ctx.appName || DEFAULT_APP_NAME;
|
|
21
|
+
const price = ctx.plan ? this.money(ctx.plan.unitAmount) : '';
|
|
22
|
+
for (const doc of DOCS) {
|
|
23
|
+
if ('needsBilling' in doc && doc.needsBilling && !ctx.plan)
|
|
24
|
+
continue;
|
|
25
|
+
const source = readFileSync(new URL(doc.file, DOCS_DIR), 'utf8')
|
|
26
|
+
// {{#billing}}…{{/billing}}: text that only applies when billing (and so the free plan) is on.
|
|
27
|
+
.replace(/\{\{#billing\}\}([\s\S]*?)\{\{\/billing\}\}/g, (_, inner) => (ctx.plan ? inner : ''))
|
|
28
|
+
.replaceAll('{{APP_NAME}}', this.appName)
|
|
29
|
+
.replaceAll('{{PUBLIC_URL}}', ctx.publicUrl)
|
|
30
|
+
.replaceAll('{{FREE_DOMAINS}}', String(ctx.plan?.freeDomains ?? ''))
|
|
31
|
+
.replaceAll('{{PRICE}}', price)
|
|
32
|
+
// Examples in the docs use a placeholder host; show this server's.
|
|
33
|
+
.replaceAll('https://dns.example.com', ctx.publicUrl);
|
|
34
|
+
const rendered = renderMarkdown(source, { resolveLink: (href) => this.docLink(href) });
|
|
35
|
+
this.docs.set(doc.slug, { ...rendered, label: doc.label });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Missing settings show as a visible placeholder, so they're noticed before launch. */
|
|
39
|
+
missingSettings() {
|
|
40
|
+
return [
|
|
41
|
+
!this.ctx.legalName && 'LEGAL_NAME',
|
|
42
|
+
!this.ctx.contactEmail && 'CONTACT_EMAIL',
|
|
43
|
+
!this.ctx.legalCountry && 'LEGAL_COUNTRY',
|
|
44
|
+
].filter((x) => Boolean(x));
|
|
45
|
+
}
|
|
46
|
+
/** Routes a request path to a page, or null if it isn't a site page. */
|
|
47
|
+
render(path) {
|
|
48
|
+
switch (path) {
|
|
49
|
+
case '/':
|
|
50
|
+
return this.page('', this.landing(), 'home');
|
|
51
|
+
case '/pricing':
|
|
52
|
+
return this.ctx.plan ? this.page('Pricing', this.pricing(), 'pricing') : null;
|
|
53
|
+
case '/about':
|
|
54
|
+
return this.page('About', this.about(), 'about');
|
|
55
|
+
case '/contact':
|
|
56
|
+
return this.page('Contact', this.contact(), 'contact');
|
|
57
|
+
case '/terms':
|
|
58
|
+
return this.page('Terms of Service', this.terms(), 'legal');
|
|
59
|
+
case '/privacy':
|
|
60
|
+
return this.page('Privacy Policy', this.privacy(), 'legal');
|
|
61
|
+
case '/refunds':
|
|
62
|
+
return this.page('Refund Policy', this.refunds(), 'legal');
|
|
63
|
+
case '/docs':
|
|
64
|
+
case '/docs/':
|
|
65
|
+
return { status: 302, headers: { location: '/docs/getting-started' }, body: '' };
|
|
66
|
+
}
|
|
67
|
+
const doc = /^\/docs\/([\w-]+)$/.exec(path);
|
|
68
|
+
if (doc && this.docs.has(doc[1]))
|
|
69
|
+
return this.page(this.docs.get(doc[1]).title, this.docPage(doc[1]), 'docs');
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// ---- helpers ----
|
|
73
|
+
money(cents) {
|
|
74
|
+
const currency = (this.ctx.plan?.currency ?? 'usd').toUpperCase();
|
|
75
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency, minimumFractionDigits: cents % 100 ? 2 : 0 }).format(cents / 100);
|
|
76
|
+
}
|
|
77
|
+
docLink(href) {
|
|
78
|
+
// Only links to published docs survive; others (e.g. self-hosting.md) render as plain text.
|
|
79
|
+
const [target, hash] = href.split('#');
|
|
80
|
+
const doc = DOCS.find((d) => d.file === target || `/docs/${d.slug}` === target);
|
|
81
|
+
if (!doc || ('needsBilling' in doc && doc.needsBilling && !this.ctx.plan))
|
|
82
|
+
return null;
|
|
83
|
+
return `/docs/${doc.slug}${hash ? `#${hash}` : ''}`;
|
|
84
|
+
}
|
|
85
|
+
/** A value the operator must configure; shown highlighted until it is. */
|
|
86
|
+
setting(value, name) {
|
|
87
|
+
return value ? esc(value) : `<mark title="Set ${name}">[${name}]</mark>`;
|
|
88
|
+
}
|
|
89
|
+
get operator() {
|
|
90
|
+
return this.setting(this.ctx.legalName, 'LEGAL_NAME');
|
|
91
|
+
}
|
|
92
|
+
get email() {
|
|
93
|
+
return this.ctx.contactEmail
|
|
94
|
+
? `<a href="mailto:${esc(this.ctx.contactEmail)}">${esc(this.ctx.contactEmail)}</a>`
|
|
95
|
+
: this.setting(undefined, 'CONTACT_EMAIL');
|
|
96
|
+
}
|
|
97
|
+
get effective() {
|
|
98
|
+
const date = this.ctx.effectiveDate || DEFAULT_EFFECTIVE_DATE;
|
|
99
|
+
return new Date(`${date}T00:00:00Z`).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' });
|
|
100
|
+
}
|
|
101
|
+
cta(primary = 'Start free') {
|
|
102
|
+
return this.ctx.signupEnabled ? `<a class="btn primary" href="/dashboard">${primary}</a>` : '';
|
|
103
|
+
}
|
|
104
|
+
// ---- layout ----
|
|
105
|
+
page(title, main, section) {
|
|
106
|
+
const app = esc(this.appName);
|
|
107
|
+
const nav = (href, label, key) => `<a href="${href}"${section === key ? ' aria-current="page"' : ''}>${label}</a>`;
|
|
108
|
+
const year = new Date().getUTCFullYear();
|
|
109
|
+
const body = `<!doctype html>
|
|
110
|
+
<html lang="en">
|
|
111
|
+
<head>
|
|
112
|
+
<meta charset="utf-8">
|
|
113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
114
|
+
<title>${title ? `${esc(title)} · ${app}` : `${app}: verified sending domains for your customers`}</title>
|
|
115
|
+
<meta name="description" content="${app} authenticates your customers' sending domains with SendGrid, Postmark, Mailgun or Amazon SES: DNS records written or shown, verification tracked, webhooks when ready.">
|
|
116
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%233b5bdb'/%3E%3Cpath d='M8 11h16v11H8z M8 12l8 6 8-6' fill='none' stroke='white' stroke-width='2.2' stroke-linejoin='round'/%3E%3C/svg%3E">
|
|
117
|
+
<style>${CSS}</style>
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
<header class="site">
|
|
121
|
+
<div class="wrap bar">
|
|
122
|
+
<a class="brand" href="/"><span class="mark" aria-hidden="true">${LOGO}</span>${app}</a>
|
|
123
|
+
<nav aria-label="Main">
|
|
124
|
+
${nav('/docs/getting-started', 'Docs', 'docs')}
|
|
125
|
+
${this.ctx.plan ? nav('/pricing', 'Pricing', 'pricing') : ''}
|
|
126
|
+
${nav('/about', 'About', 'about')}
|
|
127
|
+
${this.ctx.signupEnabled ? '<a class="btn small" href="/dashboard">Sign in</a>' : ''}
|
|
128
|
+
</nav>
|
|
129
|
+
</div>
|
|
130
|
+
</header>
|
|
131
|
+
<main>${main}</main>
|
|
132
|
+
<footer class="site">
|
|
133
|
+
<div class="wrap foot">
|
|
134
|
+
<div>© ${year} ${this.operator}</div>
|
|
135
|
+
<nav aria-label="Footer">
|
|
136
|
+
<a href="/docs/getting-started">Docs</a>
|
|
137
|
+
${this.ctx.plan ? '<a href="/pricing">Pricing</a>' : ''}
|
|
138
|
+
<a href="/about">About</a>
|
|
139
|
+
<a href="/contact">Contact</a>
|
|
140
|
+
<a href="/terms">Terms</a>
|
|
141
|
+
<a href="/privacy">Privacy</a>
|
|
142
|
+
${this.ctx.plan ? '<a href="/refunds">Refunds</a>' : ''}
|
|
143
|
+
</nav>
|
|
144
|
+
</div>
|
|
145
|
+
</footer>
|
|
146
|
+
</body>
|
|
147
|
+
</html>`;
|
|
148
|
+
return {
|
|
149
|
+
status: 200,
|
|
150
|
+
headers: {
|
|
151
|
+
'content-type': 'text/html; charset=utf-8',
|
|
152
|
+
'cache-control': 'public, max-age=300',
|
|
153
|
+
'x-content-type-options': 'nosniff',
|
|
154
|
+
'referrer-policy': 'strict-origin-when-cross-origin',
|
|
155
|
+
'content-security-policy': "default-src 'none'; script-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
|
|
156
|
+
},
|
|
157
|
+
body,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// ---- pages ----
|
|
161
|
+
landing() {
|
|
162
|
+
const plan = this.ctx.plan;
|
|
163
|
+
const features = [
|
|
164
|
+
['Writes the DNS records', 'Connect a customer’s Cloudflare or Route 53 and the records are added for them. On any other host, they get the exact records to paste.'],
|
|
165
|
+
['Watches until verified', 'Checks the authoritative nameservers, then your email provider, with backoff. Re-checks verified domains daily and tells you if one breaks.'],
|
|
166
|
+
['Embeddable widget', 'Drop a setup flow into your app with two lines of code. Customers’ DNS credentials never touch your servers or your frontend.'],
|
|
167
|
+
['Signed webhooks', 'Get domain.verified, domain.conflict and more as they happen, signed and retried, so your app updates itself.'],
|
|
168
|
+
['Inbound email too', 'Optionally route replies to your app: the MX record and inbound forwarding are configured in the same flow (SendGrid, Postmark and Mailgun).'],
|
|
169
|
+
['Built for multi-tenant SaaS', 'Tag domains with your own tenant ids and see usage per customer for your own billing.'],
|
|
170
|
+
];
|
|
171
|
+
return `
|
|
172
|
+
<section class="hero wrap">
|
|
173
|
+
<h1>Your customers’ sending domains, <span class="accent">verified automatically</span></h1>
|
|
174
|
+
<p class="lead">${esc(this.appName)} handles sending-domain authentication with SendGrid, Postmark, Mailgun or Amazon SES for every customer of your app: DNS records written or shown, verification tracked, and a webhook when each domain is ready to send.</p>
|
|
175
|
+
<div class="row">${this.cta()}<a class="btn" href="/docs/getting-started">Read the docs</a></div>
|
|
176
|
+
${plan ? `<p class="small muted">Free for up to ${plan.freeDomains} domains. No credit card required.</p>` : ''}
|
|
177
|
+
</section>
|
|
178
|
+
|
|
179
|
+
<section class="wrap band">
|
|
180
|
+
<h2>How it works</h2>
|
|
181
|
+
<ol class="steps">
|
|
182
|
+
<li><b>Register the domain</b><span>One API call from your backend, or let customers enter it in the widget.</span></li>
|
|
183
|
+
<li><b>DNS gets set up</b><span>We write the records through Cloudflare or Route 53, or show your customer exactly what to add.</span></li>
|
|
184
|
+
<li><b>Verified, and you’re told</b><span>We track propagation and your provider’s verification, then send you a signed webhook.</span></li>
|
|
185
|
+
</ol>
|
|
186
|
+
</section>
|
|
187
|
+
|
|
188
|
+
<section class="wrap">
|
|
189
|
+
<div class="grid3">${features.map(([t, d]) => `<div class="card"><h3>${t}</h3><p>${d}</p></div>`).join('')}</div>
|
|
190
|
+
</section>
|
|
191
|
+
|
|
192
|
+
<section class="wrap split">
|
|
193
|
+
<div>
|
|
194
|
+
<h2>One call to start</h2>
|
|
195
|
+
<p>Register a customer’s domain and get the DNS records back immediately. Everything after that happens in the background.</p>
|
|
196
|
+
<a href="/docs/hosted-api">API reference →</a>
|
|
197
|
+
</div>
|
|
198
|
+
<pre><code>curl -X POST ${esc(this.ctx.publicUrl)}/v1/domains \\
|
|
199
|
+
-H "Authorization: Bearer $API_KEY" \\
|
|
200
|
+
-d '{"domain": "customer.com",
|
|
201
|
+
"tenant_id": "cus_42",
|
|
202
|
+
"dns": {"provider": "cloudflare",
|
|
203
|
+
"cloudflare_api_token": "…"}}'</code></pre>
|
|
204
|
+
</section>
|
|
205
|
+
|
|
206
|
+
<section class="wrap band">
|
|
207
|
+
<h2>Credentials handled with care</h2>
|
|
208
|
+
<p class="muted">Email provider keys and DNS tokens are encrypted at rest (AES-256-GCM), API keys are stored only as hashes, and the widget runs on our domain so your customers’ DNS credentials never pass through your app. <a href="/privacy">How we handle data</a></p>
|
|
209
|
+
</section>
|
|
210
|
+
|
|
211
|
+
${plan ? `<section class="wrap center">
|
|
212
|
+
<h2>Start free, pay as you grow</h2>
|
|
213
|
+
<p class="muted">${plan.freeDomains} domains free. Then ${this.money(plan.unitAmount)} per active domain per month.</p>
|
|
214
|
+
<div class="row center">${this.cta('Create your free account')}<a class="btn" href="/pricing">See pricing</a></div>
|
|
215
|
+
</section>` : ''}`;
|
|
216
|
+
}
|
|
217
|
+
pricing() {
|
|
218
|
+
const p = this.ctx.plan;
|
|
219
|
+
const price = this.money(p.unitAmount);
|
|
220
|
+
const faq = [
|
|
221
|
+
['What is an active domain?', `A domain that was verified on at least one day of the billing period. Each domain counts once per period, however many days it was active. Domains that never verify aren’t counted.`],
|
|
222
|
+
['What happens when I reach the free limit?', `Creating more than ${p.freeDomains} domains is blocked until you add a payment method. Your existing domains keep working.`],
|
|
223
|
+
['How am I billed?', `Monthly, for the active domains in each billing period. The first ${p.freeDomains} are always free. Your dashboard shows the estimated amount as the month goes.`],
|
|
224
|
+
['Can I cancel?', 'Yes, any time from Manage billing in your dashboard. Your domains keep working and you return to the free plan’s limit.'],
|
|
225
|
+
['Do prices include tax?', 'Payments are handled by Dodo Payments, our merchant of record, which adds any VAT, GST or sales tax that applies where you are.'],
|
|
226
|
+
];
|
|
227
|
+
return `
|
|
228
|
+
<section class="wrap hero small-hero">
|
|
229
|
+
<h1>Simple, usage-based pricing</h1>
|
|
230
|
+
<p class="lead">Start free. Pay only for the domains your customers actually use.</p>
|
|
231
|
+
</section>
|
|
232
|
+
<section class="wrap">
|
|
233
|
+
<div class="plans">
|
|
234
|
+
<div class="plan">
|
|
235
|
+
<h2>Free</h2>
|
|
236
|
+
<div class="price">${this.money(0)}<small> forever</small></div>
|
|
237
|
+
<ul>
|
|
238
|
+
<li>Up to ${p.freeDomains} domains</li>
|
|
239
|
+
<li>API and embeddable widget</li>
|
|
240
|
+
<li>Cloudflare, Route 53 and manual DNS</li>
|
|
241
|
+
<li>Signed webhooks and usage reports</li>
|
|
242
|
+
<li>No credit card required</li>
|
|
243
|
+
</ul>
|
|
244
|
+
${this.cta('Start free')}
|
|
245
|
+
</div>
|
|
246
|
+
<div class="plan featured">
|
|
247
|
+
<h2>Pay as you go</h2>
|
|
248
|
+
<div class="price">${price}<small> / active domain / month</small></div>
|
|
249
|
+
<ul>
|
|
250
|
+
<li>First ${p.freeDomains} active domains free</li>
|
|
251
|
+
<li>Unlimited domains</li>
|
|
252
|
+
<li>Everything in Free</li>
|
|
253
|
+
<li>Billed monthly for what you use</li>
|
|
254
|
+
<li>Cancel any time</li>
|
|
255
|
+
</ul>
|
|
256
|
+
${this.cta('Get started')}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
<p class="small muted center">Example: 10 active domains in a month = (10 − ${p.freeDomains}) × ${price} = ${this.money((10 - p.freeDomains) * p.unitAmount)}.</p>
|
|
260
|
+
</section>
|
|
261
|
+
<section class="wrap narrow">
|
|
262
|
+
<h2>Questions</h2>
|
|
263
|
+
${faq.map(([q, a]) => `<details><summary>${q}</summary><p>${a}</p></details>`).join('')}
|
|
264
|
+
</section>`;
|
|
265
|
+
}
|
|
266
|
+
about() {
|
|
267
|
+
const country = this.ctx.legalCountry ? `, based in ${esc(this.ctx.legalCountry)}` : '';
|
|
268
|
+
return `
|
|
269
|
+
<article class="wrap narrow prose">
|
|
270
|
+
<h1>About ${esc(this.appName)}</h1>
|
|
271
|
+
<p>${esc(this.appName)} helps software companies let their customers send email from their own domains.</p>
|
|
272
|
+
<p>Authenticating a sending domain with an email provider like SendGrid, Postmark, Mailgun or Amazon SES means creating DNS records, getting them added at the right DNS host, waiting for them to propagate, and confirming with the provider. Each step can go wrong, and each one usually ends up as a support ticket. ${esc(this.appName)} turns that into one API call, or a widget your customers complete themselves, and lets your app know the moment a domain is ready.</p>
|
|
273
|
+
<h2>Who we are</h2>
|
|
274
|
+
<p>${esc(this.appName)} is built and operated by ${this.operator}${country}.</p>
|
|
275
|
+
<h2>Get in touch</h2>
|
|
276
|
+
<p>Questions, feedback or a feature you need? Email ${this.email} or see our <a href="/contact">contact page</a>.</p>
|
|
277
|
+
</article>`;
|
|
278
|
+
}
|
|
279
|
+
contact() {
|
|
280
|
+
return `
|
|
281
|
+
<article class="wrap narrow prose">
|
|
282
|
+
<h1>Contact</h1>
|
|
283
|
+
<p>Email ${this.email}. We read every message and will get back to you as soon as we can.</p>
|
|
284
|
+
<h2>Before you write</h2>
|
|
285
|
+
<ul>
|
|
286
|
+
<li><b>Setting up a domain?</b> The <a href="/docs/getting-started">getting started guide</a> and <a href="/docs/hosted-api">API reference</a> cover most questions.</li>
|
|
287
|
+
${this.ctx.plan ? '<li><b>Billing, invoices or cancelling?</b> Use <b>Manage billing</b> in your <a href="/dashboard">dashboard</a>. For anything else about a charge, email us with your account email.</li>' : ''}
|
|
288
|
+
<li><b>Found a security issue?</b> Email us with “Security” in the subject. Please don’t disclose it publicly until we’ve had a chance to fix it.</li>
|
|
289
|
+
</ul>
|
|
290
|
+
<h2>Operator</h2>
|
|
291
|
+
<p>${this.operator}${this.ctx.legalCountry ? `, ${esc(this.ctx.legalCountry)}` : ''}</p>
|
|
292
|
+
</article>`;
|
|
293
|
+
}
|
|
294
|
+
docPage(slug) {
|
|
295
|
+
const doc = this.docs.get(slug);
|
|
296
|
+
const sidebar = [...this.docs.entries()]
|
|
297
|
+
.map(([s, d]) => `<a href="/docs/${s}"${s === slug ? ' aria-current="page"' : ''}>${esc(d.label)}</a>`)
|
|
298
|
+
.join('');
|
|
299
|
+
const toc = doc.headings.filter((h) => h.level === 2);
|
|
300
|
+
return `
|
|
301
|
+
<div class="wrap docs">
|
|
302
|
+
<aside>
|
|
303
|
+
<nav aria-label="Documentation">${sidebar}</nav>
|
|
304
|
+
${toc.length > 1 ? `<div class="toc"><div class="small muted">On this page</div>${toc.map((h) => `<a href="#${esc(h.id)}">${h.text}</a>`).join('')}</div>` : ''}
|
|
305
|
+
</aside>
|
|
306
|
+
<article class="prose">${doc.html}</article>
|
|
307
|
+
</div>`;
|
|
308
|
+
}
|
|
309
|
+
// ---- legal (drafts: the operator should have these reviewed) ----
|
|
310
|
+
legal(title, sections) {
|
|
311
|
+
return `
|
|
312
|
+
<article class="wrap narrow prose legal">
|
|
313
|
+
<h1>${title}</h1>
|
|
314
|
+
<p class="muted">Last updated ${this.effective}</p>
|
|
315
|
+
${sections.map(([h, body]) => `<h2>${h}</h2>${body}`).join('\n')}
|
|
316
|
+
</article>`;
|
|
317
|
+
}
|
|
318
|
+
terms() {
|
|
319
|
+
const app = esc(this.appName);
|
|
320
|
+
const p = this.ctx.plan;
|
|
321
|
+
return this.legal('Terms of Service', [
|
|
322
|
+
['1. Agreement', `<p>These terms govern your use of ${app} (the “Service”), operated by ${this.operator} (“we”, “us”). By creating an account or using the Service you agree to them. If you use the Service for an organization, you accept them on its behalf.</p>`],
|
|
323
|
+
['2. The Service', `<p>The Service creates and verifies email sending domains with your chosen email provider (SendGrid, Postmark, Mailgun or Amazon SES) on your behalf. It can write DNS records through DNS providers you or your customers connect, check DNS and verification status, configure inbound email, and notify your systems by webhook. It relies on third-party services (your email provider, DNS providers and our payment provider), whose own terms also apply to your use of them.</p>`],
|
|
324
|
+
['3. Your account', `<p>You sign in with a link sent to your email address. Keep your email account and API keys secure: anyone with them can act on your account. You’re responsible for activity under your account, and you must tell us promptly about any unauthorized use.</p>`],
|
|
325
|
+
['4. Domains and authorization', `<p>Only set up domains, and only connect DNS providers or email provider accounts, that you or your customers own or are authorized to manage. If your customers connect their DNS through you, you’re responsible for having their permission and for giving them any notices required by law. We use the credentials you or your customers provide only to perform the operations you request.</p>`],
|
|
326
|
+
['5. Acceptable use', `<p>You must not use the Service to:</p><ul><li>send spam or unsolicited email, or break anti-spam laws or your email provider’s acceptable use policy;</li><li>configure domains you don’t control or impersonate others;</li><li>disrupt, overload or probe the Service or anyone else’s systems;</li><li>break any law that applies to you.</li></ul><p>We may suspend accounts that do.</p>`],
|
|
327
|
+
['6. Plans, fees and payment', p
|
|
328
|
+
? `<p>The free plan includes up to ${p.freeDomains} domains. The pay-as-you-go plan is billed monthly per active domain beyond the free allowance, at the prices shown on our <a href="/pricing">pricing page</a>. An active domain is one verified on at least one day of the billing period. Payments are processed by Dodo Payments as merchant of record, which may add applicable taxes and whose terms apply to the purchase. We’ll give at least 30 days’ notice of price increases.</p>`
|
|
329
|
+
: `<p>The Service is currently provided without charge. If we introduce fees, we’ll tell you in advance and you can stop using the Service before they apply.</p>`],
|
|
330
|
+
['7. Cancellation and termination', `<p>You can stop using the Service ${p ? 'and cancel your subscription ' : ''}at any time${p ? ' from your dashboard' : ''}. We may suspend or end your access if you breach these terms${p ? ' or don’t pay amounts due' : ''}, and we’ll tell you why where we reasonably can. After your account ends, we delete your data as described in our <a href="/privacy">Privacy Policy</a>.</p>`],
|
|
331
|
+
['8. Your data', `<p>You keep all rights to the data you provide. You allow us to use it only to provide, secure and improve the Service. Our <a href="/privacy">Privacy Policy</a> explains how we handle personal data.</p>`],
|
|
332
|
+
['9. Availability and changes', `<p>We work to keep the Service reliable but don’t guarantee it will be uninterrupted or error-free, and it depends on third-party services we don’t control. We may change or discontinue features; if we discontinue the Service, we’ll give reasonable notice.</p>`],
|
|
333
|
+
['10. Disclaimer', `<p>The Service is provided “as is” and “as available”. To the extent the law allows, we disclaim all warranties, including merchantability, fitness for a particular purpose and non-infringement. We don’t guarantee that email sent from authenticated domains will be delivered.</p>`],
|
|
334
|
+
['11. Limitation of liability', `<p>To the extent the law allows, we are not liable for indirect, incidental, special, consequential or punitive damages, or for lost profits, revenue or data. Our total liability for any claim relating to the Service is limited to the amount you paid us in the 12 months before the claim${p ? '' : ' (or zero, if you paid nothing)'}. Nothing in these terms limits liability that can’t be limited by law.</p>`],
|
|
335
|
+
['12. Indemnity', `<p>You’ll defend and indemnify us against claims arising from your use of the Service, the domains and content you or your customers send, or your breach of these terms.</p>`],
|
|
336
|
+
['13. Changes to these terms', `<p>We may update these terms. We’ll post the new version here and, for material changes, tell you by email at least 30 days before they take effect. Continuing to use the Service after that means you accept them.</p>`],
|
|
337
|
+
['14. Governing law', `<p>These terms are governed by the laws of ${this.setting(this.ctx.legalCountry, 'LEGAL_COUNTRY')}, and its courts have jurisdiction over disputes, except where the law where you live says otherwise.</p>`],
|
|
338
|
+
['15. Contact', `<p>Questions about these terms: ${this.email}.</p>`],
|
|
339
|
+
]);
|
|
340
|
+
}
|
|
341
|
+
privacy() {
|
|
342
|
+
const app = esc(this.appName);
|
|
343
|
+
const p = this.ctx.plan;
|
|
344
|
+
return this.legal('Privacy Policy', [
|
|
345
|
+
['Who we are', `<p>${app} is operated by ${this.operator}${this.ctx.legalCountry ? `, ${esc(this.ctx.legalCountry)}` : ''}, which is responsible for your personal data. Contact: ${this.email}.</p>`],
|
|
346
|
+
['What we collect', `<ul>
|
|
347
|
+
<li><b>Account details:</b> your email address and the name you give us.</li>
|
|
348
|
+
<li><b>Credentials you provide:</b> email provider API keys and DNS provider tokens or keys. They’re encrypted at rest and used only to perform the operations you request. Our own API keys are stored only as one-way hashes.</li>
|
|
349
|
+
<li><b>Domain data:</b> domain names, DNS records, verification status, and any tenant ids or metadata you send with them.</li>
|
|
350
|
+
<li><b>Usage records:</b> which domains were active on which days, used to calculate usage and bills.</li>
|
|
351
|
+
<li><b>Technical data:</b> IP addresses and request details in our server logs, used for security, rate limiting and troubleshooting.</li>
|
|
352
|
+
${p ? '<li><b>Billing data:</b> your payment provider customer id and subscription status. Card details are collected by Dodo Payments and never reach us.</li>' : ''}
|
|
353
|
+
</ul>`],
|
|
354
|
+
['Cookies', `<p>We use one cookie, a session cookie that keeps you signed in to the dashboard. It’s strictly necessary for the Service. We don’t use advertising or analytics cookies, and we don’t track you across other sites.</p>`],
|
|
355
|
+
['How we use it', `<p>To provide and secure the Service, send sign-in emails and service notices, calculate usage${p ? ' and bill you' : ''}, respond to you, and meet legal obligations. We don’t sell personal data, and we don’t use it for advertising.</p>`],
|
|
356
|
+
['Who we share it with', `<p>Only service providers that help us run the Service, under appropriate safeguards:</p><ul>
|
|
357
|
+
<li><b>The email provider you connect</b> (SendGrid, Postmark, Mailgun or Amazon SES): to authenticate your domains.</li>
|
|
358
|
+
<li><b>Twilio SendGrid:</b> sending our sign-in emails.</li>
|
|
359
|
+
<li><b>DNS providers you connect</b> (such as Cloudflare or Amazon Route 53): to manage the records you ask us to.</li>
|
|
360
|
+
${p ? '<li><b>Dodo Payments:</b> payment processing as merchant of record.</li>' : ''}
|
|
361
|
+
<li><b>Our hosting and database providers:</b> to store and run the Service.</li>
|
|
362
|
+
</ul><p>We may also disclose data when the law requires it.</p>`],
|
|
363
|
+
['International transfers', `<p>Our providers may process data in countries other than yours. Where the law requires it, we rely on appropriate safeguards for these transfers.</p>`],
|
|
364
|
+
['How long we keep it', `<p>Account data, credentials and domain data are kept while your account exists, and credentials are deleted as soon as you remove them or the domain. When you delete a domain, its usage records stay, because we need them for billing and accounting. When your account is closed, we delete your data within 30 days, except records we must keep by law. Server logs are kept for a limited time for security.</p>`],
|
|
365
|
+
['Security', `<p>Connections use HTTPS. Credentials are encrypted with AES-256-GCM and API keys are hashed. Access to production systems is restricted. No system is perfectly secure, but we work to protect your data and will notify you of a breach where the law requires it.</p>`],
|
|
366
|
+
['Your rights', `<p>Depending on where you live, including under the EU and UK GDPR and India’s Digital Personal Data Protection Act, 2023, you may have the right to access, correct, delete or export your personal data, to object to or restrict some processing, and to withdraw consent. To exercise them, email ${this.email}. You can also complain to your data protection authority.</p>`],
|
|
367
|
+
['Your customers’ data', `<p>If you use the Service for your own customers, you decide what data you send us about them, and we process it on your behalf to provide the Service.</p>`],
|
|
368
|
+
['Children', `<p>The Service is for businesses and isn’t intended for children.</p>`],
|
|
369
|
+
['Changes', `<p>We’ll post updates here and tell you by email about significant changes.</p>`],
|
|
370
|
+
]);
|
|
371
|
+
}
|
|
372
|
+
refunds() {
|
|
373
|
+
const p = this.ctx.plan;
|
|
374
|
+
return this.legal('Refund Policy', [
|
|
375
|
+
['Free plan', `<p>The free plan costs nothing, so there’s nothing to refund.</p>`],
|
|
376
|
+
['Pay as you go', `<p>You’re billed monthly in arrears for the active domains you actually used in each billing period${p ? `, after the first ${p.freeDomains}, which are free` : ''}. Because you only pay for usage that has already happened, charges are generally not refundable.</p><p>You can cancel at any time from <b>Manage billing</b> in your dashboard to stop future charges. You won’t be charged for periods after you cancel.</p>`],
|
|
377
|
+
['Billing mistakes', `<p>If you were charged incorrectly, for example for a domain that wasn’t active or because of a technical error on our side, email ${this.email} within 30 days of the charge with your account email and the invoice. We’ll correct it and refund the difference.</p>`],
|
|
378
|
+
['How refunds are paid', `<p>Payments are processed by Dodo Payments, our merchant of record. Approved refunds go back to your original payment method through Dodo Payments; how long it takes to appear depends on your bank or card issuer.</p>`],
|
|
379
|
+
['Questions', `<p>Contact ${this.email}.</p>`],
|
|
380
|
+
]);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const LOGO = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg>`;
|
|
384
|
+
const CSS = `
|
|
385
|
+
:root { --bg:#ffffff; --bg-2:#f6f7f9; --surface:#ffffff; --border:#e2e5ea; --text:#15181d; --muted:#5d6573; --accent:#3b5bdb; --accent-hover:#3249b8; --accent-soft:#e8edff; --on-accent:#fff; --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; color-scheme: light; }
|
|
386
|
+
@media (prefers-color-scheme: dark) { :root { --bg:#0f1115; --bg-2:#141821; --surface:#171a20; --border:#2a2f39; --text:#e8eaee; --muted:#9aa3b2; --accent:#7b93ff; --accent-hover:#93a7ff; --accent-soft:#1f2645; --on-accent:#0b0f1f; color-scheme: dark; } }
|
|
387
|
+
* { box-sizing: border-box; }
|
|
388
|
+
body { margin:0; background:var(--bg); color:var(--text); font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif; }
|
|
389
|
+
a { color:var(--accent); }
|
|
390
|
+
/* Horizontal padding only: sections set their own vertical spacing. */
|
|
391
|
+
.wrap { max-width:1080px; margin:0 auto; padding-left:20px; padding-right:20px; }
|
|
392
|
+
.narrow { max-width:760px; }
|
|
393
|
+
header.site { border-bottom:1px solid var(--border); background:var(--surface); position:sticky; top:0; z-index:1; }
|
|
394
|
+
.bar { display:flex; align-items:center; gap:16px; min-height:60px; flex-wrap:wrap; }
|
|
395
|
+
.brand { display:flex; align-items:center; gap:10px; font-weight:650; color:var(--text); text-decoration:none; }
|
|
396
|
+
.mark { width:28px; height:28px; border-radius:8px; background:var(--accent); color:var(--on-accent); display:grid; place-items:center; }
|
|
397
|
+
header nav { margin-left:auto; display:flex; gap:18px; align-items:center; flex-wrap:wrap; }
|
|
398
|
+
header nav a { color:var(--muted); text-decoration:none; font-size:15px; }
|
|
399
|
+
header nav a:hover, header nav a[aria-current] { color:var(--text); }
|
|
400
|
+
.btn { display:inline-block; font-weight:550; font-size:15px; border-radius:8px; padding:10px 18px; border:1px solid var(--border); background:var(--surface); color:var(--text) !important; text-decoration:none; }
|
|
401
|
+
.btn:hover { background:var(--bg-2); }
|
|
402
|
+
.btn.primary { background:var(--accent); border-color:var(--accent); color:var(--on-accent) !important; }
|
|
403
|
+
.btn.primary:hover { background:var(--accent-hover); }
|
|
404
|
+
.btn.small { padding:6px 14px; font-size:14px; }
|
|
405
|
+
.row { display:flex; gap:12px; flex-wrap:wrap; align-items:center; }
|
|
406
|
+
.center { text-align:center; justify-content:center; }
|
|
407
|
+
.muted { color:var(--muted); }
|
|
408
|
+
.small { font-size:14px; }
|
|
409
|
+
section { padding:56px 0 0; }
|
|
410
|
+
.hero { padding-top:80px; max-width:820px; }
|
|
411
|
+
.small-hero { padding-top:56px; }
|
|
412
|
+
.hero h1 { font-size:clamp(32px,5vw,52px); line-height:1.1; letter-spacing:-.02em; margin:0 0 18px; }
|
|
413
|
+
.accent { color:var(--accent); }
|
|
414
|
+
.lead { font-size:19px; color:var(--muted); margin:0 0 26px; }
|
|
415
|
+
h2 { font-size:26px; letter-spacing:-.01em; margin:0 0 14px; }
|
|
416
|
+
.band h2 { margin-bottom:22px; }
|
|
417
|
+
.steps { list-style:none; counter-reset:s; padding:0; margin:0; display:grid; grid-template-columns:repeat(3,1fr); gap:16px; }
|
|
418
|
+
.steps li { counter-increment:s; background:var(--bg-2); border:1px solid var(--border); border-radius:12px; padding:20px; display:grid; gap:6px; align-content:start; }
|
|
419
|
+
.steps li::before { content:counter(s); width:28px; height:28px; border-radius:50%; background:var(--accent-soft); color:var(--accent); display:grid; place-items:center; font-weight:700; font-size:14px; }
|
|
420
|
+
.steps span { color:var(--muted); font-size:15px; }
|
|
421
|
+
.grid3 { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; }
|
|
422
|
+
.card { border:1px solid var(--border); border-radius:12px; padding:20px; background:var(--surface); }
|
|
423
|
+
.card h3 { margin:0 0 6px; font-size:17px; }
|
|
424
|
+
.card p { margin:0; color:var(--muted); font-size:15px; }
|
|
425
|
+
.split { display:grid; grid-template-columns:1fr 1.2fr; gap:32px; align-items:center; }
|
|
426
|
+
pre { margin:0; font-family:var(--mono); font-size:13.5px; line-height:1.55; background:#0f1115; color:#e8eaee; border:1px solid var(--border); border-radius:12px; padding:18px; overflow-x:auto; }
|
|
427
|
+
code { font-family:var(--mono); font-size:.9em; }
|
|
428
|
+
:not(pre) > code { background:var(--bg-2); border:1px solid var(--border); border-radius:5px; padding:1px 5px; }
|
|
429
|
+
.plans { display:grid; grid-template-columns:1fr 1fr; gap:20px; max-width:820px; margin:0 auto 18px; }
|
|
430
|
+
.plan { border:1px solid var(--border); border-radius:14px; padding:26px; display:grid; gap:14px; align-content:start; background:var(--surface); }
|
|
431
|
+
.plan.featured { border-color:var(--accent); box-shadow:0 0 0 1px var(--accent); }
|
|
432
|
+
.plan h2 { margin:0; font-size:20px; }
|
|
433
|
+
.price { font-size:36px; font-weight:700; letter-spacing:-.02em; }
|
|
434
|
+
.price small { font-size:15px; font-weight:400; color:var(--muted); letter-spacing:0; }
|
|
435
|
+
.plan ul { margin:0; padding-left:20px; display:grid; gap:6px; }
|
|
436
|
+
details { border-bottom:1px solid var(--border); padding:14px 0; }
|
|
437
|
+
summary { cursor:pointer; font-weight:600; }
|
|
438
|
+
details p { color:var(--muted); margin:10px 0 0; }
|
|
439
|
+
.prose h1 { font-size:36px; letter-spacing:-.02em; line-height:1.15; margin:48px 0 8px; }
|
|
440
|
+
.prose h2 { font-size:22px; margin:36px 0 10px; }
|
|
441
|
+
.prose h3 { font-size:17px; margin:26px 0 8px; }
|
|
442
|
+
.prose p, .prose li { color:var(--text); }
|
|
443
|
+
.prose ul, .prose ol { padding-left:22px; }
|
|
444
|
+
.prose li { margin:4px 0; }
|
|
445
|
+
.prose pre { margin:14px 0; }
|
|
446
|
+
.legal h2 { font-size:19px; }
|
|
447
|
+
.table { overflow-x:auto; margin:14px 0; }
|
|
448
|
+
table { border-collapse:collapse; width:100%; font-size:15px; }
|
|
449
|
+
th, td { text-align:left; vertical-align:top; padding:8px 10px; border-bottom:1px solid var(--border); }
|
|
450
|
+
th { font-weight:600; background:var(--bg-2); }
|
|
451
|
+
mark { background:#fff3bf; color:#5c3c00; padding:0 4px; border-radius:4px; }
|
|
452
|
+
.docs { display:grid; grid-template-columns:220px 1fr; gap:40px; padding-bottom:40px; }
|
|
453
|
+
.docs aside { position:sticky; top:76px; align-self:start; padding-top:48px; display:grid; gap:22px; }
|
|
454
|
+
.docs aside nav, .toc { display:grid; gap:6px; }
|
|
455
|
+
.docs aside a { color:var(--muted); text-decoration:none; font-size:15px; }
|
|
456
|
+
.docs aside a[aria-current] { color:var(--accent); font-weight:600; }
|
|
457
|
+
.toc a { font-size:14px; }
|
|
458
|
+
.docs article { min-width:0; }
|
|
459
|
+
footer.site { margin-top:72px; border-top:1px solid var(--border); background:var(--bg-2); }
|
|
460
|
+
.foot { display:flex; gap:16px; justify-content:space-between; flex-wrap:wrap; padding:24px 20px; color:var(--muted); font-size:14px; }
|
|
461
|
+
.foot nav { display:flex; gap:16px; flex-wrap:wrap; }
|
|
462
|
+
.foot a { color:var(--muted); text-decoration:none; }
|
|
463
|
+
.foot a:hover { color:var(--text); }
|
|
464
|
+
@media (max-width: 820px) {
|
|
465
|
+
.steps, .grid3, .split, .plans { grid-template-columns:1fr; }
|
|
466
|
+
.docs { grid-template-columns:1fr; gap:0; }
|
|
467
|
+
.docs aside { position:static; padding-top:24px; }
|
|
468
|
+
.toc { display:none; }
|
|
469
|
+
.hero { padding-top:48px; }
|
|
470
|
+
}
|
|
471
|
+
`;
|
|
472
|
+
//# sourceMappingURL=site.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site.js","sourceRoot":"","sources":["../../../src/hosted/site.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,cAAc,EAAyB,MAAM,eAAe,CAAC;AA6BtE,MAAM,gBAAgB,GAAG,SAAS,CAAC;AACnC,MAAM,sBAAsB,GAAG,YAAY,CAAC;AAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE5D,oGAAoG;AACpG,MAAM,IAAI,GAAG;IACX,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,iBAAiB,EAAE;IACjF,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE;IACrE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE;IACjE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,IAAI,EAAE;CAC/E,CAAC;AAEX,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAE/I,MAAM,OAAO,IAAI;IAIc;IAHpB,OAAO,CAAS;IACR,IAAI,GAAG,IAAI,GAAG,EAAgD,CAAC;IAEhF,YAA6B,GAAgB;QAAhB,QAAG,GAAH,GAAG,CAAa;QAC3C,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,gBAAgB,CAAC;QAC/C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,cAAc,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,IAAI,CAAC,GAAG,CAAC,IAAI;gBAAE,SAAS;YACrE,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;gBAC9D,+FAA+F;iBAC9F,OAAO,CAAC,8CAA8C,EAAE,CAAC,CAAC,EAAE,KAAa,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;iBACtG,UAAU,CAAC,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC;iBACxC,UAAU,CAAC,gBAAgB,EAAE,GAAG,CAAC,SAAS,CAAC;iBAC3C,UAAU,CAAC,kBAAkB,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;iBACnE,UAAU,CAAC,WAAW,EAAE,KAAK,CAAC;gBAC/B,mEAAmE;iBAClE,UAAU,CAAC,yBAAyB,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,wFAAwF;IACxF,eAAe;QACb,OAAO;YACL,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,YAAY;YACnC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,eAAe;YACzC,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,eAAe;SAC1C,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,wEAAwE;IACxE,MAAM,CAAC,IAAY;QACjB,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,GAAG;gBACN,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;YAC/C,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAChF,KAAK,QAAQ;gBACX,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;YACnD,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;YACzD,KAAK,QAAQ;gBACX,OAAO,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;YAC9D,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;YAC9D,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;YAC7D,KAAK,OAAO,CAAC;YACb,KAAK,QAAQ;gBACX,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,uBAAuB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QACrF,CAAC;QACD,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAE,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAClH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,oBAAoB;IAEZ,KAAK,CAAC,KAAa;QACzB,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QAClE,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,qBAAqB,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;IACzI,CAAC;IAEO,OAAO,CAAC,IAAY;QAC1B,4FAA4F;QAC5F,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,SAAS,CAAC,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC,CAAC;QAChF,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc,IAAI,GAAG,IAAI,GAAG,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACvF,OAAO,SAAS,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACtD,CAAC;IAED,0EAA0E;IAClE,OAAO,CAAC,KAAyB,EAAE,IAAY;QACrD,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,IAAI,MAAM,IAAI,UAAU,CAAC;IAC3E,CAAC;IAED,IAAY,QAAQ;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACxD,CAAC;IAED,IAAY,KAAK;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY;YAC1B,CAAC,CAAC,mBAAmB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM;YACpF,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IAC/C,CAAC;IAED,IAAY,SAAS;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,sBAAsB,CAAC;QAC9D,OAAO,IAAI,IAAI,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACxI,CAAC;IAEO,GAAG,CAAC,OAAO,GAAG,YAAY;QAChC,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,4CAA4C,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACjG,CAAC;IAED,mBAAmB;IAEX,IAAI,CAAC,KAAa,EAAE,IAAY,EAAE,OAAe;QACvD,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,GAAW,EAAE,EAAE,CAAC,YAAY,IAAI,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC;QAC3I,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,cAAc,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG;;;;;SAKR,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,+CAA+C;oCAC7D,GAAG;;SAE9B,GAAG;;;;;sEAK0D,IAAI,UAAU,GAAG;;QAE/E,GAAG,CAAC,uBAAuB,EAAE,MAAM,EAAE,MAAM,CAAC;QAC5C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE;QAC1D,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,oDAAoD,CAAC,CAAC,CAAC,EAAE;;;;QAIlF,IAAI;;;aAGC,IAAI,IAAI,IAAI,CAAC,QAAQ;;;QAG1B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,EAAE;;;;;QAKrD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,EAAE;;;;;QAKrD,CAAC;QACL,OAAO;YACL,MAAM,EAAE,GAAG;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,0BAA0B;gBAC1C,eAAe,EAAE,qBAAqB;gBACtC,wBAAwB,EAAE,SAAS;gBACnC,iBAAiB,EAAE,iCAAiC;gBACpD,yBAAyB,EAAE,qJAAqJ;aACjL;YACD,IAAI;SACL,CAAC;IACJ,CAAC;IAED,kBAAkB;IAEV,OAAO;QACb,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QAC3B,MAAM,QAAQ,GAAG;YACf,CAAC,wBAAwB,EAAE,yIAAyI,CAAC;YACrK,CAAC,wBAAwB,EAAE,6IAA6I,CAAC;YACzK,CAAC,mBAAmB,EAAE,+HAA+H,CAAC;YACtJ,CAAC,iBAAiB,EAAE,+GAA+G,CAAC;YACpI,CAAC,mBAAmB,EAAE,8IAA8I,CAAC;YACrK,CAAC,6BAA6B,EAAE,uFAAuF,CAAC;SACzH,CAAC;QACF,OAAO;;;oBAGS,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;qBAChB,IAAI,CAAC,GAAG,EAAE;IAC3B,IAAI,CAAC,CAAC,CAAC,yCAAyC,IAAI,CAAC,WAAW,wCAAwC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;uBAa1F,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,yBAAyB,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;;;;;;;4BAShF,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;;;;;;;;;;;;;EAajD,IAAI,CAAC,CAAC,CAAC;;qBAEY,IAAI,CAAC,WAAW,uBAAuB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;4BAC3D,IAAI,CAAC,GAAG,CAAC,0BAA0B,CAAC;WACrD,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACjB,CAAC;IAEO,OAAO;QACb,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAK,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG;YACV,CAAC,2BAA2B,EAAE,sLAAsL,CAAC;YACrN,CAAC,2CAA2C,EAAE,sBAAsB,CAAC,CAAC,WAAW,yFAAyF,CAAC;YAC3K,CAAC,kBAAkB,EAAE,qEAAqE,CAAC,CAAC,WAAW,gFAAgF,CAAC;YACxL,CAAC,eAAe,EAAE,yHAAyH,CAAC;YAC5I,CAAC,wBAAwB,EAAE,iIAAiI,CAAC;SAC9J,CAAC;QACF,OAAO;;;;;;;;;2BASgB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;;oBAEpB,CAAC,CAAC,WAAW;;;;;;QAMzB,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;;;;2BAIH,KAAK;;oBAEZ,CAAC,CAAC,WAAW;;;;;;QAMzB,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC;;;gFAGiD,CAAC,CAAC,WAAW,OAAO,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC;;;;IAI1J,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;WAC9E,CAAC;IACV,CAAC;IAEO,KAAK;QACX,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,OAAO;;cAEG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;OACxB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;8TACsS,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;;OAExU,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,6BAA6B,IAAI,CAAC,QAAQ,GAAG,OAAO;;wDAEpB,IAAI,CAAC,KAAK;WACvD,CAAC;IACV,CAAC;IAEO,OAAO;QACb,OAAO;;;aAGE,IAAI,CAAC,KAAK;;;;MAIjB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,4LAA4L,CAAC,CAAC,CAAC,EAAE;;;;OAIhN,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;WAC1E,CAAC;IACV,CAAC;IAEO,OAAO,CAAC,IAAY;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC;QACjC,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;aACrC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;aACtG,IAAI,CAAC,EAAE,CAAC,CAAC;QACZ,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;QACtD,OAAO;;;sCAG2B,OAAO;MACvC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,+DAA+D,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;;2BAExI,GAAG,CAAC,IAAI;OAC5B,CAAC;IACN,CAAC;IAED,oEAAoE;IAE5D,KAAK,CAAC,KAAa,EAAE,QAA4B;QACvD,OAAO;;QAEH,KAAK;kCACqB,IAAI,CAAC,SAAS;IAC5C,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;WACvD,CAAC;IACV,CAAC;IAEO,KAAK;QACX,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,EAAE;YACpC,CAAC,cAAc,EAAE,qCAAqC,GAAG,iCAAiC,IAAI,CAAC,QAAQ,8JAA8J,CAAC;YACtQ,CAAC,gBAAgB,EAAE,0dAA0d,CAAC;YAC9e,CAAC,iBAAiB,EAAE,iQAAiQ,CAAC;YACtR,CAAC,8BAA8B,EAAE,sYAAsY,CAAC;YACxa,CAAC,mBAAmB,EAAE,gYAAgY,CAAC;YACvZ,CAAC,4BAA4B,EAAE,CAAC;oBAC9B,CAAC,CAAC,mCAAmC,CAAC,CAAC,WAAW,8aAA8a;oBAChe,CAAC,CAAC,gKAAgK,CAAC;YACrK,CAAC,iCAAiC,EAAE,qCAAqC,CAAC,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,EAAE,gEAAgE,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,EAAE,+JAA+J,CAAC;YACla,CAAC,cAAc,EAAE,6MAA6M,CAAC;YAC/N,CAAC,6BAA6B,EAAE,sQAAsQ,CAAC;YACvS,CAAC,gBAAgB,EAAE,yRAAyR,CAAC;YAC7S,CAAC,6BAA6B,EAAE,kSAAkS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iCAAiC,6EAA6E,CAAC;YAC1b,CAAC,eAAe,EAAE,+KAA+K,CAAC;YAClM,CAAC,4BAA4B,EAAE,0NAA0N,CAAC;YAC1P,CAAC,mBAAmB,EAAE,8CAA8C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,eAAe,CAAC,2GAA2G,CAAC;YACpO,CAAC,aAAa,EAAE,mCAAmC,IAAI,CAAC,KAAK,OAAO,CAAC;SACtE,CAAC,CAAC;IACL,CAAC;IAEO,OAAO;QACb,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE;YAClC,CAAC,YAAY,EAAE,MAAM,GAAG,mBAAmB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,2DAA2D,IAAI,CAAC,KAAK,OAAO,CAAC;YACtM,CAAC,iBAAiB,EAAE;;;;;;UAMhB,CAAC,CAAC,CAAC,CAAC,0JAA0J,CAAC,CAAC,CAAC,EAAE;YACjK,CAAC;YACP,CAAC,SAAS,EAAE,0NAA0N,CAAC;YACvO,CAAC,eAAe,EAAE,iGAAiG,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,qHAAqH,CAAC;YACjQ,CAAC,sBAAsB,EAAE;;;;UAIrB,CAAC,CAAC,CAAC,CAAC,0EAA0E,CAAC,CAAC,CAAC,EAAE;;sEAEvB,CAAC;YACjE,CAAC,yBAAyB,EAAE,wJAAwJ,CAAC;YACrL,CAAC,qBAAqB,EAAE,0ZAA0Z,CAAC;YACnb,CAAC,UAAU,EAAE,0QAA0Q,CAAC;YACxR,CAAC,aAAa,EAAE,ySAAyS,IAAI,CAAC,KAAK,gEAAgE,CAAC;YACpY,CAAC,sBAAsB,EAAE,6JAA6J,CAAC;YACvL,CAAC,UAAU,EAAE,uEAAuE,CAAC;YACrF,CAAC,SAAS,EAAE,iFAAiF,CAAC;SAC/F,CAAC,CAAC;IACL,CAAC;IAEO,OAAO;QACb,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE;YACjC,CAAC,WAAW,EAAE,mEAAmE,CAAC;YAClF,CAAC,eAAe,EAAE,sGAAsG,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,WAAW,kBAAkB,CAAC,CAAC,CAAC,EAAE,gQAAgQ,CAAC;YACtb,CAAC,kBAAkB,EAAE,sIAAsI,IAAI,CAAC,KAAK,wHAAwH,CAAC;YAC9R,CAAC,sBAAsB,EAAE,0NAA0N,CAAC;YACpP,CAAC,WAAW,EAAE,cAAc,IAAI,CAAC,KAAK,OAAO,CAAC;SAC/C,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,IAAI,GAAG,qOAAqO,CAAC;AAEnP,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuFX,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type DashboardSession, type LoginToken, type UsageQuery, type UsageRecord, type UsageRow, type Account, type DomainEvent, type DomainRow, type ListDomainsQuery, type Store, type WebhookDelivery, type WidgetSession } from './store.js';
|
|
2
|
+
export declare class SqliteStore implements Store {
|
|
3
|
+
private readonly db;
|
|
4
|
+
/** `path` is a file path, or ':memory:' for tests. */
|
|
5
|
+
constructor(path: string);
|
|
6
|
+
createAccount(account: Account): Promise<void>;
|
|
7
|
+
getAccount(id: string): Promise<Account | undefined>;
|
|
8
|
+
getAccountByKeyHash(hash: string): Promise<Account | undefined>;
|
|
9
|
+
updateAccount(id: string, patch: Partial<Account>): Promise<void>;
|
|
10
|
+
insertDomain(domain: DomainRow): Promise<void>;
|
|
11
|
+
getDomain(accountId: string, id: string): Promise<DomainRow | undefined>;
|
|
12
|
+
findDomain(accountId: string, domain: string, subdomain: string | null): Promise<DomainRow | undefined>;
|
|
13
|
+
listDomains(accountId: string, query: ListDomainsQuery): Promise<{
|
|
14
|
+
domains: DomainRow[];
|
|
15
|
+
next: string | null;
|
|
16
|
+
}>;
|
|
17
|
+
updateDomain(id: string, patch: Partial<DomainRow>): Promise<DomainRow>;
|
|
18
|
+
deleteDomain(id: string): Promise<void>;
|
|
19
|
+
claimDueDomains(now: number, limit: number, leaseMs: number): Promise<DomainRow[]>;
|
|
20
|
+
addEvent(event: DomainEvent): Promise<void>;
|
|
21
|
+
listEvents(domainId: string, limit: number): Promise<DomainEvent[]>;
|
|
22
|
+
addDelivery(delivery: WebhookDelivery): Promise<void>;
|
|
23
|
+
claimDueDeliveries(now: number, limit: number, leaseMs: number): Promise<WebhookDelivery[]>;
|
|
24
|
+
updateDelivery(id: string, patch: Partial<WebhookDelivery>): Promise<void>;
|
|
25
|
+
createWidgetSession(session: WidgetSession): Promise<void>;
|
|
26
|
+
getWidgetSessionByHash(hash: string): Promise<WidgetSession | undefined>;
|
|
27
|
+
updateWidgetSession(id: string, patch: Partial<WidgetSession>): Promise<void>;
|
|
28
|
+
listAccounts(): Promise<Account[]>;
|
|
29
|
+
countDomains(accountId: string): Promise<number>;
|
|
30
|
+
recordUsage(records: UsageRecord[]): Promise<void>;
|
|
31
|
+
recordActiveDomainDays(day: string, now: number): Promise<void>;
|
|
32
|
+
summarizeUsage(query: UsageQuery): Promise<UsageRow[]>;
|
|
33
|
+
getAccountByEmail(email: string): Promise<Account | undefined>;
|
|
34
|
+
getAccountByBillingCustomer(customerId: string): Promise<Account | undefined>;
|
|
35
|
+
listBillableAccounts(): Promise<Account[]>;
|
|
36
|
+
createLoginToken(token: LoginToken): Promise<void>;
|
|
37
|
+
consumeLoginToken(tokenHash: string, now: number): Promise<LoginToken | undefined>;
|
|
38
|
+
createDashboardSession(session: DashboardSession): Promise<void>;
|
|
39
|
+
getDashboardSession(tokenHash: string): Promise<DashboardSession | undefined>;
|
|
40
|
+
deleteDashboardSession(tokenHash: string): Promise<void>;
|
|
41
|
+
markBillingEventProcessed(eventId: string, now: number): Promise<boolean>;
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
private insert;
|
|
44
|
+
private update;
|
|
45
|
+
}
|