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,351 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { ACCOUNT_BILLING_COLUMNS, ACCOUNT_COLUMNS, DASHBOARD_SESSION_COLUMNS, LOGIN_TOKEN_COLUMNS, DELIVERY_COLUMNS, DOMAIN_COLUMNS, DuplicateError, EVENT_COLUMNS, fromColumns, jsonFallback, SESSION_COLUMNS, toColumns, toUsageRow, USAGE_COLUMNS, usageSql, } from './store.js';
|
|
3
|
+
const SCHEMA = `
|
|
4
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
name TEXT NOT NULL,
|
|
7
|
+
api_key_hash TEXT NOT NULL UNIQUE,
|
|
8
|
+
sendgrid_api_key_enc TEXT,
|
|
9
|
+
webhook_url TEXT,
|
|
10
|
+
webhook_secret TEXT NOT NULL,
|
|
11
|
+
widget_origins TEXT NOT NULL DEFAULT '[]',
|
|
12
|
+
domain_limit INTEGER,
|
|
13
|
+
created_at INTEGER NOT NULL
|
|
14
|
+
);
|
|
15
|
+
CREATE TABLE IF NOT EXISTS domains (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
18
|
+
domain TEXT NOT NULL,
|
|
19
|
+
subdomain TEXT NOT NULL DEFAULT '',
|
|
20
|
+
tenant_id TEXT,
|
|
21
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
22
|
+
status TEXT NOT NULL,
|
|
23
|
+
sendgrid_domain_id TEXT NOT NULL,
|
|
24
|
+
sendgrid_subuser TEXT,
|
|
25
|
+
dns_provider TEXT NOT NULL,
|
|
26
|
+
dns_credentials_enc TEXT,
|
|
27
|
+
inbound TEXT,
|
|
28
|
+
overwrite INTEGER NOT NULL DEFAULT 0,
|
|
29
|
+
records TEXT NOT NULL DEFAULT '[]',
|
|
30
|
+
pending TEXT NOT NULL DEFAULT '[]',
|
|
31
|
+
validation TEXT,
|
|
32
|
+
conflicts TEXT,
|
|
33
|
+
last_error TEXT,
|
|
34
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
next_run_at INTEGER,
|
|
36
|
+
locked_until INTEGER,
|
|
37
|
+
verified_at INTEGER,
|
|
38
|
+
expires_at INTEGER,
|
|
39
|
+
created_at INTEGER NOT NULL,
|
|
40
|
+
updated_at INTEGER NOT NULL,
|
|
41
|
+
UNIQUE (account_id, domain, subdomain)
|
|
42
|
+
);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS domains_due ON domains (next_run_at) WHERE next_run_at IS NOT NULL;
|
|
44
|
+
CREATE INDEX IF NOT EXISTS domains_by_account ON domains (account_id, created_at, id);
|
|
45
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
domain_id TEXT NOT NULL,
|
|
48
|
+
account_id TEXT NOT NULL,
|
|
49
|
+
type TEXT NOT NULL,
|
|
50
|
+
data TEXT NOT NULL,
|
|
51
|
+
created_at INTEGER NOT NULL
|
|
52
|
+
);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS events_by_domain ON events (domain_id, created_at);
|
|
54
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
57
|
+
event_id TEXT NOT NULL,
|
|
58
|
+
url TEXT NOT NULL,
|
|
59
|
+
payload TEXT NOT NULL,
|
|
60
|
+
status TEXT NOT NULL,
|
|
61
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
next_attempt_at INTEGER,
|
|
63
|
+
locked_until INTEGER,
|
|
64
|
+
last_error TEXT,
|
|
65
|
+
response_status INTEGER,
|
|
66
|
+
created_at INTEGER NOT NULL
|
|
67
|
+
);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS deliveries_due ON webhook_deliveries (next_attempt_at) WHERE status = 'pending';
|
|
69
|
+
CREATE TABLE IF NOT EXISTS usage_records (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
account_id TEXT NOT NULL,
|
|
72
|
+
tenant_id TEXT,
|
|
73
|
+
domain_id TEXT NOT NULL,
|
|
74
|
+
domain TEXT NOT NULL,
|
|
75
|
+
metric TEXT NOT NULL,
|
|
76
|
+
quantity INTEGER NOT NULL,
|
|
77
|
+
day TEXT NOT NULL,
|
|
78
|
+
occurred_at INTEGER NOT NULL
|
|
79
|
+
);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS usage_by_account_day ON usage_records (account_id, day);
|
|
81
|
+
CREATE TABLE IF NOT EXISTS widget_sessions (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
84
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
85
|
+
domain_id TEXT,
|
|
86
|
+
defaults TEXT,
|
|
87
|
+
expires_at INTEGER NOT NULL,
|
|
88
|
+
created_at INTEGER NOT NULL
|
|
89
|
+
);
|
|
90
|
+
CREATE TABLE IF NOT EXISTS login_tokens (
|
|
91
|
+
token_hash TEXT PRIMARY KEY,
|
|
92
|
+
email TEXT NOT NULL,
|
|
93
|
+
name TEXT,
|
|
94
|
+
expires_at INTEGER NOT NULL,
|
|
95
|
+
used_at INTEGER,
|
|
96
|
+
created_at INTEGER NOT NULL
|
|
97
|
+
);
|
|
98
|
+
CREATE TABLE IF NOT EXISTS dashboard_sessions (
|
|
99
|
+
token_hash TEXT PRIMARY KEY,
|
|
100
|
+
account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
|
101
|
+
expires_at INTEGER NOT NULL,
|
|
102
|
+
created_at INTEGER NOT NULL
|
|
103
|
+
);
|
|
104
|
+
CREATE TABLE IF NOT EXISTS billing_events (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
processed_at INTEGER NOT NULL
|
|
107
|
+
);
|
|
108
|
+
`;
|
|
109
|
+
/** Columns added after the first release, for databases created before them. */
|
|
110
|
+
const MIGRATIONS = [
|
|
111
|
+
['accounts', 'widget_origins', "TEXT NOT NULL DEFAULT '[]'"],
|
|
112
|
+
['accounts', 'domain_limit', 'INTEGER'],
|
|
113
|
+
...ACCOUNT_BILLING_COLUMNS.map(([column, sqlite]) => ['accounts', column, sqlite]),
|
|
114
|
+
];
|
|
115
|
+
/** Run after MIGRATIONS, since they use migrated columns. */
|
|
116
|
+
const POST_MIGRATION = `
|
|
117
|
+
UPDATE accounts SET email_credentials_enc = sendgrid_api_key_enc
|
|
118
|
+
WHERE email_credentials_enc IS NULL AND sendgrid_api_key_enc IS NOT NULL;
|
|
119
|
+
CREATE UNIQUE INDEX IF NOT EXISTS accounts_by_email ON accounts (email);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS accounts_by_billing_customer ON accounts (billing_customer_id);
|
|
121
|
+
`;
|
|
122
|
+
function encode(kind, v) {
|
|
123
|
+
if (kind === 'bool')
|
|
124
|
+
return v ? 1 : 0;
|
|
125
|
+
if (kind === 'emptyAsNull')
|
|
126
|
+
return v ?? '';
|
|
127
|
+
if (typeof kind === 'object')
|
|
128
|
+
return v === null ? null : JSON.stringify(v);
|
|
129
|
+
return v ?? null;
|
|
130
|
+
}
|
|
131
|
+
function decode(kind, v) {
|
|
132
|
+
if (kind === 'bool')
|
|
133
|
+
return v === 1;
|
|
134
|
+
if (kind === 'emptyAsNull')
|
|
135
|
+
return v === '' ? null : v;
|
|
136
|
+
if (typeof kind === 'object')
|
|
137
|
+
return v === null || v === undefined ? jsonFallback(kind) : JSON.parse(v);
|
|
138
|
+
return v ?? null;
|
|
139
|
+
}
|
|
140
|
+
const toRow = (map, value) => toColumns(map, value, encode);
|
|
141
|
+
const fromRow = (map, row) => fromColumns(map, row, decode);
|
|
142
|
+
export class SqliteStore {
|
|
143
|
+
db;
|
|
144
|
+
/** `path` is a file path, or ':memory:' for tests. */
|
|
145
|
+
constructor(path) {
|
|
146
|
+
this.db = new DatabaseSync(path);
|
|
147
|
+
this.db.exec('PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 5000;');
|
|
148
|
+
this.db.exec(SCHEMA);
|
|
149
|
+
for (const [table, column, definition] of MIGRATIONS) {
|
|
150
|
+
const columns = this.db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name);
|
|
151
|
+
if (!columns.includes(column))
|
|
152
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
153
|
+
}
|
|
154
|
+
this.db.exec(POST_MIGRATION);
|
|
155
|
+
}
|
|
156
|
+
async createAccount(account) {
|
|
157
|
+
this.insert('accounts', toRow(ACCOUNT_COLUMNS, account));
|
|
158
|
+
}
|
|
159
|
+
async getAccount(id) {
|
|
160
|
+
return fromRow(ACCOUNT_COLUMNS, this.db.prepare('SELECT * FROM accounts WHERE id = ?').get(id));
|
|
161
|
+
}
|
|
162
|
+
async getAccountByKeyHash(hash) {
|
|
163
|
+
return fromRow(ACCOUNT_COLUMNS, this.db.prepare('SELECT * FROM accounts WHERE api_key_hash = ?').get(hash));
|
|
164
|
+
}
|
|
165
|
+
async updateAccount(id, patch) {
|
|
166
|
+
this.update('accounts', id, toRow(ACCOUNT_COLUMNS, patch));
|
|
167
|
+
}
|
|
168
|
+
async insertDomain(domain) {
|
|
169
|
+
try {
|
|
170
|
+
this.insert('domains', toRow(DOMAIN_COLUMNS, domain));
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
if (/UNIQUE constraint failed/.test(String(err.message)))
|
|
174
|
+
throw new DuplicateError('Domain already registered');
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async getDomain(accountId, id) {
|
|
179
|
+
return fromRow(DOMAIN_COLUMNS, this.db.prepare('SELECT * FROM domains WHERE account_id = ? AND id = ?').get(accountId, id));
|
|
180
|
+
}
|
|
181
|
+
async findDomain(accountId, domain, subdomain) {
|
|
182
|
+
const row = this.db
|
|
183
|
+
.prepare('SELECT * FROM domains WHERE account_id = ? AND domain = ? AND subdomain = ?')
|
|
184
|
+
.get(accountId, domain, subdomain ?? '');
|
|
185
|
+
return fromRow(DOMAIN_COLUMNS, row);
|
|
186
|
+
}
|
|
187
|
+
async listDomains(accountId, query) {
|
|
188
|
+
const where = ['account_id = $account'];
|
|
189
|
+
const params = { $account: accountId, $limit: query.limit + 1 };
|
|
190
|
+
if (query.tenantId) {
|
|
191
|
+
where.push('tenant_id = $tenant');
|
|
192
|
+
params.$tenant = query.tenantId;
|
|
193
|
+
}
|
|
194
|
+
if (query.status) {
|
|
195
|
+
where.push('status = $status');
|
|
196
|
+
params.$status = query.status;
|
|
197
|
+
}
|
|
198
|
+
if (query.after) {
|
|
199
|
+
const [createdAt, id] = Buffer.from(query.after, 'base64url').toString('utf8').split(':');
|
|
200
|
+
where.push('(created_at < $afterAt OR (created_at = $afterAt AND id < $afterId))');
|
|
201
|
+
params.$afterAt = Number(createdAt);
|
|
202
|
+
params.$afterId = id ?? '';
|
|
203
|
+
}
|
|
204
|
+
const rows = this.db
|
|
205
|
+
.prepare(`SELECT * FROM domains WHERE ${where.join(' AND ')} ORDER BY created_at DESC, id DESC LIMIT $limit`)
|
|
206
|
+
.all(params)
|
|
207
|
+
.map((r) => fromRow(DOMAIN_COLUMNS, r));
|
|
208
|
+
const page = rows.slice(0, query.limit);
|
|
209
|
+
const last = page.at(-1);
|
|
210
|
+
const next = rows.length > query.limit && last ? Buffer.from(`${last.createdAt}:${last.id}`).toString('base64url') : null;
|
|
211
|
+
return { domains: page, next };
|
|
212
|
+
}
|
|
213
|
+
async updateDomain(id, patch) {
|
|
214
|
+
this.update('domains', id, toRow(DOMAIN_COLUMNS, patch));
|
|
215
|
+
return fromRow(DOMAIN_COLUMNS, this.db.prepare('SELECT * FROM domains WHERE id = ?').get(id));
|
|
216
|
+
}
|
|
217
|
+
async deleteDomain(id) {
|
|
218
|
+
this.db.prepare('DELETE FROM events WHERE domain_id = ?').run(id);
|
|
219
|
+
this.db.prepare('DELETE FROM widget_sessions WHERE domain_id = ?').run(id);
|
|
220
|
+
this.db.prepare('DELETE FROM domains WHERE id = ?').run(id);
|
|
221
|
+
}
|
|
222
|
+
async claimDueDomains(now, limit, leaseMs) {
|
|
223
|
+
return this.db
|
|
224
|
+
.prepare(`UPDATE domains SET locked_until = $lease
|
|
225
|
+
WHERE id IN (
|
|
226
|
+
SELECT id FROM domains
|
|
227
|
+
WHERE next_run_at IS NOT NULL AND next_run_at <= $now AND (locked_until IS NULL OR locked_until < $now)
|
|
228
|
+
ORDER BY next_run_at LIMIT $limit
|
|
229
|
+
)
|
|
230
|
+
RETURNING *`)
|
|
231
|
+
.all({ $lease: now + leaseMs, $now: now, $limit: limit })
|
|
232
|
+
.map((r) => fromRow(DOMAIN_COLUMNS, r));
|
|
233
|
+
}
|
|
234
|
+
async addEvent(event) {
|
|
235
|
+
this.insert('events', toRow(EVENT_COLUMNS, event));
|
|
236
|
+
}
|
|
237
|
+
async listEvents(domainId, limit) {
|
|
238
|
+
return this.db
|
|
239
|
+
.prepare('SELECT * FROM events WHERE domain_id = ? ORDER BY created_at DESC, rowid DESC LIMIT ?')
|
|
240
|
+
.all(domainId, limit)
|
|
241
|
+
.map((r) => fromRow(EVENT_COLUMNS, r));
|
|
242
|
+
}
|
|
243
|
+
async addDelivery(delivery) {
|
|
244
|
+
this.insert('webhook_deliveries', toRow(DELIVERY_COLUMNS, delivery));
|
|
245
|
+
}
|
|
246
|
+
async claimDueDeliveries(now, limit, leaseMs) {
|
|
247
|
+
return this.db
|
|
248
|
+
.prepare(`UPDATE webhook_deliveries SET locked_until = $lease
|
|
249
|
+
WHERE id IN (
|
|
250
|
+
SELECT id FROM webhook_deliveries
|
|
251
|
+
WHERE status = 'pending' AND next_attempt_at <= $now AND (locked_until IS NULL OR locked_until < $now)
|
|
252
|
+
ORDER BY next_attempt_at LIMIT $limit
|
|
253
|
+
)
|
|
254
|
+
RETURNING *`)
|
|
255
|
+
.all({ $lease: now + leaseMs, $now: now, $limit: limit })
|
|
256
|
+
.map((r) => fromRow(DELIVERY_COLUMNS, r));
|
|
257
|
+
}
|
|
258
|
+
async updateDelivery(id, patch) {
|
|
259
|
+
this.update('webhook_deliveries', id, toRow(DELIVERY_COLUMNS, patch));
|
|
260
|
+
}
|
|
261
|
+
async createWidgetSession(session) {
|
|
262
|
+
this.insert('widget_sessions', toRow(SESSION_COLUMNS, session));
|
|
263
|
+
}
|
|
264
|
+
async getWidgetSessionByHash(hash) {
|
|
265
|
+
return fromRow(SESSION_COLUMNS, this.db.prepare('SELECT * FROM widget_sessions WHERE token_hash = ?').get(hash));
|
|
266
|
+
}
|
|
267
|
+
async updateWidgetSession(id, patch) {
|
|
268
|
+
this.update('widget_sessions', id, toRow(SESSION_COLUMNS, patch));
|
|
269
|
+
}
|
|
270
|
+
async listAccounts() {
|
|
271
|
+
return this.db.prepare('SELECT * FROM accounts ORDER BY created_at, id').all().map((r) => fromRow(ACCOUNT_COLUMNS, r));
|
|
272
|
+
}
|
|
273
|
+
async countDomains(accountId) {
|
|
274
|
+
return Number(this.db.prepare('SELECT COUNT(*) AS n FROM domains WHERE account_id = ?').get(accountId).n);
|
|
275
|
+
}
|
|
276
|
+
async recordUsage(records) {
|
|
277
|
+
for (const record of records) {
|
|
278
|
+
const row = toRow(USAGE_COLUMNS, record);
|
|
279
|
+
const columns = Object.keys(row);
|
|
280
|
+
this.db
|
|
281
|
+
.prepare(`INSERT OR IGNORE INTO usage_records (${columns.join(', ')}) VALUES (${columns.map((c) => `$${c}`).join(', ')})`)
|
|
282
|
+
.run(Object.fromEntries(columns.map((c) => [`$${c}`, row[c]])));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async recordActiveDomainDays(day, now) {
|
|
286
|
+
this.db
|
|
287
|
+
.prepare(`INSERT OR IGNORE INTO usage_records (id, account_id, tenant_id, domain_id, domain, metric, quantity, day, occurred_at)
|
|
288
|
+
SELECT 'active_domain_day:' || id || ':' || $day, account_id, tenant_id, id, domain, 'active_domain_day', 1, $day, $now
|
|
289
|
+
FROM domains WHERE status = 'verified'`)
|
|
290
|
+
.run({ $day: day, $now: now });
|
|
291
|
+
}
|
|
292
|
+
async summarizeUsage(query) {
|
|
293
|
+
const params = [query.from, query.to, ...(query.accountId ? [query.accountId] : [])];
|
|
294
|
+
return this.db
|
|
295
|
+
.prepare(usageSql(query, () => '?'))
|
|
296
|
+
.all(...params)
|
|
297
|
+
.map((r) => toUsageRow(r, Boolean(query.groupByTenant)));
|
|
298
|
+
}
|
|
299
|
+
async getAccountByEmail(email) {
|
|
300
|
+
return fromRow(ACCOUNT_COLUMNS, this.db.prepare('SELECT * FROM accounts WHERE email = ?').get(email));
|
|
301
|
+
}
|
|
302
|
+
async getAccountByBillingCustomer(customerId) {
|
|
303
|
+
return fromRow(ACCOUNT_COLUMNS, this.db.prepare('SELECT * FROM accounts WHERE billing_customer_id = ?').get(customerId));
|
|
304
|
+
}
|
|
305
|
+
async listBillableAccounts() {
|
|
306
|
+
return this.db
|
|
307
|
+
.prepare("SELECT * FROM accounts WHERE billing_status IN ('active', 'past_due') AND billing_customer_id IS NOT NULL ORDER BY id")
|
|
308
|
+
.all()
|
|
309
|
+
.map((r) => fromRow(ACCOUNT_COLUMNS, r));
|
|
310
|
+
}
|
|
311
|
+
async createLoginToken(token) {
|
|
312
|
+
this.insert('login_tokens', toRow(LOGIN_TOKEN_COLUMNS, token));
|
|
313
|
+
}
|
|
314
|
+
async consumeLoginToken(tokenHash, now) {
|
|
315
|
+
const row = this.db
|
|
316
|
+
.prepare('UPDATE login_tokens SET used_at = $now WHERE token_hash = $hash AND used_at IS NULL AND expires_at > $now RETURNING *')
|
|
317
|
+
.get({ $now: now, $hash: tokenHash });
|
|
318
|
+
return fromRow(LOGIN_TOKEN_COLUMNS, row);
|
|
319
|
+
}
|
|
320
|
+
async createDashboardSession(session) {
|
|
321
|
+
this.insert('dashboard_sessions', toRow(DASHBOARD_SESSION_COLUMNS, session));
|
|
322
|
+
}
|
|
323
|
+
async getDashboardSession(tokenHash) {
|
|
324
|
+
return fromRow(DASHBOARD_SESSION_COLUMNS, this.db.prepare('SELECT * FROM dashboard_sessions WHERE token_hash = ?').get(tokenHash));
|
|
325
|
+
}
|
|
326
|
+
async deleteDashboardSession(tokenHash) {
|
|
327
|
+
this.db.prepare('DELETE FROM dashboard_sessions WHERE token_hash = ?').run(tokenHash);
|
|
328
|
+
}
|
|
329
|
+
async markBillingEventProcessed(eventId, now) {
|
|
330
|
+
// `changes` may be a bigint.
|
|
331
|
+
return Number(this.db.prepare('INSERT OR IGNORE INTO billing_events (id, processed_at) VALUES (?, ?)').run(eventId, now).changes) === 1;
|
|
332
|
+
}
|
|
333
|
+
async close() {
|
|
334
|
+
this.db.close();
|
|
335
|
+
}
|
|
336
|
+
insert(table, row) {
|
|
337
|
+
const columns = Object.keys(row);
|
|
338
|
+
this.db
|
|
339
|
+
.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map((c) => `$${c}`).join(', ')})`)
|
|
340
|
+
.run(Object.fromEntries(columns.map((c) => [`$${c}`, row[c]])));
|
|
341
|
+
}
|
|
342
|
+
update(table, id, row) {
|
|
343
|
+
const columns = Object.keys(row);
|
|
344
|
+
if (columns.length === 0)
|
|
345
|
+
return;
|
|
346
|
+
this.db
|
|
347
|
+
.prepare(`UPDATE ${table} SET ${columns.map((c) => `${c} = $${c}`).join(', ')} WHERE id = $__id`)
|
|
348
|
+
.run({ ...Object.fromEntries(columns.map((c) => [`$${c}`, row[c]])), $__id: id });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=sqlite-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../../../src/hosted/sqlite-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import type { DnsRecord, PlannedChange, ValidationResult } from '../types.js';
|
|
2
|
+
export type DomainStatus = 'pending' | 'awaiting_dns' | 'verifying' | 'verified' | 'conflict' | 'failed';
|
|
3
|
+
export interface Account {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
apiKeyHash: string;
|
|
7
|
+
/** sendgrid | postmark | mailgun | ses */
|
|
8
|
+
emailProvider: string;
|
|
9
|
+
/** Encrypted JSON of the provider's credentials (see EmailCredentials). */
|
|
10
|
+
emailCredentialsEnc: string | null;
|
|
11
|
+
webhookUrl: string | null;
|
|
12
|
+
webhookSecret: string;
|
|
13
|
+
/** Origins allowed to embed the widget. */
|
|
14
|
+
widgetOrigins: string[];
|
|
15
|
+
/** Max domains this account may have at once; null for unlimited. Set by the operator. */
|
|
16
|
+
domainLimit: number | null;
|
|
17
|
+
/** Sign-in email for self-serve accounts; null for operator-created ones. */
|
|
18
|
+
email: string | null;
|
|
19
|
+
billingCustomerId: string | null;
|
|
20
|
+
billingSubscriptionId: string | null;
|
|
21
|
+
billingStatus: BillingStatus;
|
|
22
|
+
/** Current billing period from the payment provider, ms. */
|
|
23
|
+
billingPeriodStart: number | null;
|
|
24
|
+
billingPeriodEnd: number | null;
|
|
25
|
+
/** Active domains already reported to the payment provider this period. */
|
|
26
|
+
reportedQuantity: number;
|
|
27
|
+
/** Time (seconds) of the last billing event applied, to ignore stale, out-of-order ones. */
|
|
28
|
+
billingEventAt: number | null;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
}
|
|
31
|
+
/** free: no card on file. active: paying. past_due: payment failing, the provider is retrying. */
|
|
32
|
+
export type BillingStatus = 'free' | 'active' | 'past_due';
|
|
33
|
+
/** A single-use email sign-in / sign-up link. */
|
|
34
|
+
export interface LoginToken {
|
|
35
|
+
tokenHash: string;
|
|
36
|
+
email: string;
|
|
37
|
+
name: string | null;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
usedAt: number | null;
|
|
40
|
+
createdAt: number;
|
|
41
|
+
}
|
|
42
|
+
export interface DashboardSession {
|
|
43
|
+
tokenHash: string;
|
|
44
|
+
accountId: string;
|
|
45
|
+
expiresAt: number;
|
|
46
|
+
createdAt: number;
|
|
47
|
+
}
|
|
48
|
+
export type UsageMetric = 'domain_created' | 'domain_verified' | 'active_domain_day';
|
|
49
|
+
/**
|
|
50
|
+
* One billable fact. Append-only, and the id is deterministic (e.g.
|
|
51
|
+
* "active_domain_day:dom_1:2026-10-25"), so recording the same fact twice is a no-op.
|
|
52
|
+
* Domain name and tenant are snapshots, so history survives domain deletion.
|
|
53
|
+
*/
|
|
54
|
+
export interface UsageRecord {
|
|
55
|
+
id: string;
|
|
56
|
+
accountId: string;
|
|
57
|
+
tenantId: string | null;
|
|
58
|
+
domainId: string;
|
|
59
|
+
domain: string;
|
|
60
|
+
metric: UsageMetric;
|
|
61
|
+
quantity: number;
|
|
62
|
+
/** UTC calendar day, YYYY-MM-DD. */
|
|
63
|
+
day: string;
|
|
64
|
+
occurredAt: number;
|
|
65
|
+
}
|
|
66
|
+
export interface UsageTotals {
|
|
67
|
+
domainsCreated: number;
|
|
68
|
+
verifications: number;
|
|
69
|
+
/** Distinct domains that were verified on at least one day of the period. */
|
|
70
|
+
activeDomains: number;
|
|
71
|
+
/** Sum over days of verified domains: for prorated billing. */
|
|
72
|
+
activeDomainDays: number;
|
|
73
|
+
}
|
|
74
|
+
export interface UsageQuery {
|
|
75
|
+
/** Inclusive UTC days, YYYY-MM-DD. */
|
|
76
|
+
from: string;
|
|
77
|
+
to: string;
|
|
78
|
+
/** Omit for every account (operator reports). */
|
|
79
|
+
accountId?: string;
|
|
80
|
+
groupByTenant?: boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface UsageRow extends UsageTotals {
|
|
83
|
+
accountId: string;
|
|
84
|
+
/** Present when grouped by tenant. */
|
|
85
|
+
tenantId?: string | null;
|
|
86
|
+
}
|
|
87
|
+
export interface WidgetDefaults {
|
|
88
|
+
tenantId: string;
|
|
89
|
+
metadata: Record<string, string>;
|
|
90
|
+
subdomain: string | null;
|
|
91
|
+
sendgridSubuser: string | null;
|
|
92
|
+
/** Inbound parse webhook; the hostname becomes parse.<domain>. */
|
|
93
|
+
inboundUrl: string | null;
|
|
94
|
+
}
|
|
95
|
+
/** A short-lived browser credential scoped to one domain (or to creating one). */
|
|
96
|
+
export interface WidgetSession {
|
|
97
|
+
id: string;
|
|
98
|
+
accountId: string;
|
|
99
|
+
tokenHash: string;
|
|
100
|
+
domainId: string | null;
|
|
101
|
+
/** Used when the end user enters the domain in the widget. */
|
|
102
|
+
defaults: WidgetDefaults | null;
|
|
103
|
+
expiresAt: number;
|
|
104
|
+
createdAt: number;
|
|
105
|
+
}
|
|
106
|
+
export interface InboundConfig {
|
|
107
|
+
url: string;
|
|
108
|
+
hostname: string;
|
|
109
|
+
spamCheck: boolean;
|
|
110
|
+
sendRaw: boolean;
|
|
111
|
+
}
|
|
112
|
+
export interface DomainRow {
|
|
113
|
+
id: string;
|
|
114
|
+
accountId: string;
|
|
115
|
+
domain: string;
|
|
116
|
+
subdomain: string | null;
|
|
117
|
+
tenantId: string | null;
|
|
118
|
+
metadata: Record<string, string>;
|
|
119
|
+
status: DomainStatus;
|
|
120
|
+
/** The domain's id at the email provider. */
|
|
121
|
+
providerDomainId: string;
|
|
122
|
+
sendgridSubuser: string | null;
|
|
123
|
+
dnsProvider: string;
|
|
124
|
+
dnsCredentialsEnc: string | null;
|
|
125
|
+
inbound: InboundConfig | null;
|
|
126
|
+
overwrite: boolean;
|
|
127
|
+
records: DnsRecord[];
|
|
128
|
+
/** Names of records not yet visible in DNS at the last check. */
|
|
129
|
+
pending: string[];
|
|
130
|
+
validation: ValidationResult | null;
|
|
131
|
+
conflicts: PlannedChange[] | null;
|
|
132
|
+
lastError: string | null;
|
|
133
|
+
attempts: number;
|
|
134
|
+
nextRunAt: number | null;
|
|
135
|
+
lockedUntil: number | null;
|
|
136
|
+
verifiedAt: number | null;
|
|
137
|
+
expiresAt: number | null;
|
|
138
|
+
createdAt: number;
|
|
139
|
+
updatedAt: number;
|
|
140
|
+
}
|
|
141
|
+
export interface DomainEvent {
|
|
142
|
+
id: string;
|
|
143
|
+
domainId: string;
|
|
144
|
+
accountId: string;
|
|
145
|
+
type: string;
|
|
146
|
+
data: Record<string, unknown>;
|
|
147
|
+
createdAt: number;
|
|
148
|
+
}
|
|
149
|
+
export interface WebhookDelivery {
|
|
150
|
+
id: string;
|
|
151
|
+
accountId: string;
|
|
152
|
+
eventId: string;
|
|
153
|
+
url: string;
|
|
154
|
+
payload: string;
|
|
155
|
+
status: 'pending' | 'delivered' | 'failed';
|
|
156
|
+
attempts: number;
|
|
157
|
+
nextAttemptAt: number | null;
|
|
158
|
+
lockedUntil: number | null;
|
|
159
|
+
lastError: string | null;
|
|
160
|
+
responseStatus: number | null;
|
|
161
|
+
createdAt: number;
|
|
162
|
+
}
|
|
163
|
+
export interface ListDomainsQuery {
|
|
164
|
+
tenantId?: string;
|
|
165
|
+
status?: DomainStatus;
|
|
166
|
+
limit: number;
|
|
167
|
+
/** Opaque cursor from a previous page. */
|
|
168
|
+
after?: string;
|
|
169
|
+
}
|
|
170
|
+
export declare class DuplicateError extends Error {
|
|
171
|
+
}
|
|
172
|
+
/** Persistence for the hosted API. Implemented by SqliteStore and PostgresStore. */
|
|
173
|
+
export interface Store {
|
|
174
|
+
createAccount(account: Account): Promise<void>;
|
|
175
|
+
getAccount(id: string): Promise<Account | undefined>;
|
|
176
|
+
getAccountByKeyHash(hash: string): Promise<Account | undefined>;
|
|
177
|
+
updateAccount(id: string, patch: Partial<Omit<Account, 'id' | 'createdAt'>>): Promise<void>;
|
|
178
|
+
/** Throws DuplicateError when the account already has this domain + subdomain. */
|
|
179
|
+
insertDomain(domain: DomainRow): Promise<void>;
|
|
180
|
+
getDomain(accountId: string, id: string): Promise<DomainRow | undefined>;
|
|
181
|
+
findDomain(accountId: string, domain: string, subdomain: string | null): Promise<DomainRow | undefined>;
|
|
182
|
+
listDomains(accountId: string, query: ListDomainsQuery): Promise<{
|
|
183
|
+
domains: DomainRow[];
|
|
184
|
+
next: string | null;
|
|
185
|
+
}>;
|
|
186
|
+
updateDomain(id: string, patch: Partial<Omit<DomainRow, 'id' | 'accountId' | 'createdAt'>>): Promise<DomainRow>;
|
|
187
|
+
deleteDomain(id: string): Promise<void>;
|
|
188
|
+
/** Atomically leases up to `limit` domains whose next run is due. */
|
|
189
|
+
claimDueDomains(now: number, limit: number, leaseMs: number): Promise<DomainRow[]>;
|
|
190
|
+
addEvent(event: DomainEvent): Promise<void>;
|
|
191
|
+
listEvents(domainId: string, limit: number): Promise<DomainEvent[]>;
|
|
192
|
+
addDelivery(delivery: WebhookDelivery): Promise<void>;
|
|
193
|
+
claimDueDeliveries(now: number, limit: number, leaseMs: number): Promise<WebhookDelivery[]>;
|
|
194
|
+
updateDelivery(id: string, patch: Partial<Omit<WebhookDelivery, 'id' | 'accountId' | 'createdAt'>>): Promise<void>;
|
|
195
|
+
createWidgetSession(session: WidgetSession): Promise<void>;
|
|
196
|
+
getWidgetSessionByHash(hash: string): Promise<WidgetSession | undefined>;
|
|
197
|
+
updateWidgetSession(id: string, patch: Partial<Pick<WidgetSession, 'domainId'>>): Promise<void>;
|
|
198
|
+
listAccounts(): Promise<Account[]>;
|
|
199
|
+
/** Current number of domains (any status) for plan limits. */
|
|
200
|
+
countDomains(accountId: string): Promise<number>;
|
|
201
|
+
/** Inserts records, silently skipping ids that already exist. */
|
|
202
|
+
recordUsage(records: UsageRecord[]): Promise<void>;
|
|
203
|
+
/** Records an active day for every currently verified domain. Idempotent per domain and day. */
|
|
204
|
+
recordActiveDomainDays(day: string, now: number): Promise<void>;
|
|
205
|
+
summarizeUsage(query: UsageQuery): Promise<UsageRow[]>;
|
|
206
|
+
getAccountByEmail(email: string): Promise<Account | undefined>;
|
|
207
|
+
getAccountByBillingCustomer(customerId: string): Promise<Account | undefined>;
|
|
208
|
+
/** Accounts with a card on file (active or past_due) and a billing customer. */
|
|
209
|
+
listBillableAccounts(): Promise<Account[]>;
|
|
210
|
+
createLoginToken(token: LoginToken): Promise<void>;
|
|
211
|
+
/** Marks the token used and returns it, or undefined if unknown, expired or already used. Atomic. */
|
|
212
|
+
consumeLoginToken(tokenHash: string, now: number): Promise<LoginToken | undefined>;
|
|
213
|
+
createDashboardSession(session: DashboardSession): Promise<void>;
|
|
214
|
+
getDashboardSession(tokenHash: string): Promise<DashboardSession | undefined>;
|
|
215
|
+
deleteDashboardSession(tokenHash: string): Promise<void>;
|
|
216
|
+
/** Records a billing webhook event id; false if it was already processed. */
|
|
217
|
+
markBillingEventProcessed(eventId: string, now: number): Promise<boolean>;
|
|
218
|
+
close(): Promise<void>;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* How a model field is stored. Each store turns these into its own encoding
|
|
222
|
+
* (e.g. JSON is TEXT in SQLite and JSONB in Postgres; booleans are 0/1 in SQLite).
|
|
223
|
+
*/
|
|
224
|
+
export type ColumnKind = 'text' | 'int' | 'bool'
|
|
225
|
+
/** '' in the column (so UNIQUE constraints work) ↔ null in the model. */
|
|
226
|
+
| 'emptyAsNull' | {
|
|
227
|
+
json: unknown;
|
|
228
|
+
};
|
|
229
|
+
export type ColumnMap<T> = Record<keyof T, readonly [column: string, kind: ColumnKind]>;
|
|
230
|
+
export declare const ACCOUNT_COLUMNS: ColumnMap<Account>;
|
|
231
|
+
export declare const LOGIN_TOKEN_COLUMNS: ColumnMap<LoginToken>;
|
|
232
|
+
export declare const DASHBOARD_SESSION_COLUMNS: ColumnMap<DashboardSession>;
|
|
233
|
+
/** Billing columns added to accounts after the first release: [column, SQLite type, Postgres type]. */
|
|
234
|
+
export declare const ACCOUNT_BILLING_COLUMNS: [string, string, string][];
|
|
235
|
+
export declare const USAGE_COLUMNS: ColumnMap<UsageRecord>;
|
|
236
|
+
/** UTC calendar day of a timestamp, YYYY-MM-DD. */
|
|
237
|
+
export declare const utcDay: (ms: number) => string;
|
|
238
|
+
/**
|
|
239
|
+
* The aggregate both stores run. Grouped by account (and tenant if asked).
|
|
240
|
+
* Parameters: $1 from, $2 to, then optionally $3 account id.
|
|
241
|
+
*/
|
|
242
|
+
export declare function usageSql(query: UsageQuery, placeholder: (n: number) => string): string;
|
|
243
|
+
/** Aggregate row → UsageRow. Counts may arrive as strings (Postgres bigint/numeric) or null. */
|
|
244
|
+
export declare function toUsageRow(row: Record<string, unknown>, groupByTenant: boolean): UsageRow;
|
|
245
|
+
export declare const DOMAIN_COLUMNS: ColumnMap<DomainRow>;
|
|
246
|
+
export declare const EVENT_COLUMNS: ColumnMap<DomainEvent>;
|
|
247
|
+
export declare const DELIVERY_COLUMNS: ColumnMap<WebhookDelivery>;
|
|
248
|
+
export declare const SESSION_COLUMNS: ColumnMap<WidgetSession>;
|
|
249
|
+
/** Model object → { column: encoded value }, skipping undefined fields (so it works for patches). */
|
|
250
|
+
export declare function toColumns<T, V>(map: ColumnMap<T>, value: Partial<T>, encode: (kind: ColumnKind, v: unknown) => V): Record<string, V>;
|
|
251
|
+
export declare function fromColumns<T>(map: ColumnMap<T>, row: Record<string, unknown> | undefined, decode: (kind: ColumnKind, v: unknown) => unknown): T | undefined;
|
|
252
|
+
/** A fresh copy of a JSON column's fallback, so rows never share one mutable object. */
|
|
253
|
+
export declare const jsonFallback: (kind: {
|
|
254
|
+
json: unknown;
|
|
255
|
+
}) => unknown;
|
|
256
|
+
/** Opens a store by URL: postgres://… or postgresql://… for Postgres, anything else is a SQLite path (or :memory:). */
|
|
257
|
+
export declare function openStore(url: string): Promise<Store>;
|