@autumnsgrove/groveengine 0.4.12 → 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 +5 -3
- package/dist/components/OnboardingChecklist.svelte +118 -0
- package/dist/components/OnboardingChecklist.svelte.d.ts +14 -0
- package/dist/components/quota/QuotaWarning.svelte +125 -0
- package/dist/components/quota/QuotaWarning.svelte.d.ts +16 -0
- package/dist/components/quota/QuotaWidget.svelte +120 -0
- package/dist/components/quota/QuotaWidget.svelte.d.ts +15 -0
- package/dist/components/quota/UpgradePrompt.svelte +288 -0
- package/dist/components/quota/UpgradePrompt.svelte.d.ts +13 -0
- package/dist/components/quota/index.d.ts +8 -0
- package/dist/components/quota/index.js +8 -0
- package/dist/groveauth/client.d.ts +143 -0
- package/dist/groveauth/client.js +502 -0
- package/dist/groveauth/colors.d.ts +35 -0
- package/dist/groveauth/colors.js +91 -0
- package/dist/groveauth/index.d.ts +34 -0
- package/dist/groveauth/index.js +35 -0
- package/dist/groveauth/limits.d.ts +70 -0
- package/dist/groveauth/limits.js +202 -0
- package/dist/groveauth/rate-limit.d.ts +95 -0
- package/dist/groveauth/rate-limit.js +172 -0
- package/dist/groveauth/types.d.ts +139 -0
- package/dist/groveauth/types.js +61 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/payments/types.d.ts +7 -2
- package/dist/server/services/__mocks__/cloudflare.d.ts +54 -0
- package/dist/server/services/__mocks__/cloudflare.js +470 -0
- package/dist/server/services/cache.d.ts +170 -0
- package/dist/server/services/cache.js +335 -0
- package/dist/server/services/database.d.ts +236 -0
- package/dist/server/services/database.js +450 -0
- package/dist/server/services/index.d.ts +34 -0
- package/dist/server/services/index.js +77 -0
- package/dist/server/services/storage.d.ts +221 -0
- package/dist/server/services/storage.js +485 -0
- package/package.json +11 -1
- package/static/fonts/Calistoga-Regular.ttf +1438 -0
- package/static/fonts/Caveat-Regular.ttf +0 -0
- package/static/fonts/EBGaramond-Regular.ttf +0 -0
- package/static/fonts/Fraunces-Regular.ttf +0 -0
- package/static/fonts/InstrumentSans-Regular.ttf +0 -0
- package/static/fonts/Lora-Regular.ttf +0 -0
- package/static/fonts/Luciole-Regular.ttf +1438 -0
- package/static/fonts/Manrope-Regular.ttf +0 -0
- package/static/fonts/Merriweather-Regular.ttf +1439 -0
- package/static/fonts/Nunito-Regular.ttf +0 -0
- package/static/fonts/PlusJakartaSans-Regular.ttf +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Service - KV Key-Value Store Abstraction
|
|
3
|
+
*
|
|
4
|
+
* Provides typed caching operations with:
|
|
5
|
+
* - Automatic JSON serialization/deserialization
|
|
6
|
+
* - TTL management with sensible defaults
|
|
7
|
+
* - Namespace prefixing to avoid key collisions
|
|
8
|
+
* - Compute-if-missing pattern (getOrSet)
|
|
9
|
+
* - Specific error types for debugging
|
|
10
|
+
*/
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Errors
|
|
13
|
+
// ============================================================================
|
|
14
|
+
export class CacheError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
cause;
|
|
17
|
+
constructor(message, code, cause) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
this.name = 'CacheError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Configuration
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/** Default TTL: 1 hour */
|
|
28
|
+
const DEFAULT_TTL_SECONDS = 3600;
|
|
29
|
+
/** Key prefix to avoid collisions with other KV users */
|
|
30
|
+
const KEY_PREFIX = 'grove';
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Key Management
|
|
33
|
+
// ============================================================================
|
|
34
|
+
/**
|
|
35
|
+
* Build a namespaced cache key
|
|
36
|
+
* @example buildKey('user', '123') => 'grove:user:123'
|
|
37
|
+
*/
|
|
38
|
+
function buildKey(namespace, key) {
|
|
39
|
+
const parts = [KEY_PREFIX];
|
|
40
|
+
if (namespace)
|
|
41
|
+
parts.push(namespace);
|
|
42
|
+
parts.push(key);
|
|
43
|
+
return parts.join(':');
|
|
44
|
+
}
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Cache Operations
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Get a value from the cache
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const user = await cache.get<User>(kv, 'user:123');
|
|
54
|
+
* if (user) {
|
|
55
|
+
* console.log(user.name);
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export async function get(kv, key, options) {
|
|
60
|
+
const fullKey = buildKey(options?.namespace, key);
|
|
61
|
+
try {
|
|
62
|
+
const value = await kv.get(fullKey, 'text');
|
|
63
|
+
if (value === null) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(value);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// If it's not JSON, return as-is (for string values)
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
throw new CacheError(`Failed to get key: ${fullKey}`, 'GET_FAILED', err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Set a value in the cache
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* await cache.set(kv, 'user:123', userData, { ttl: 3600 });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export async function set(kv, key, value, options) {
|
|
87
|
+
const fullKey = buildKey(options?.namespace, key);
|
|
88
|
+
const ttl = options?.ttl ?? DEFAULT_TTL_SECONDS;
|
|
89
|
+
let serialized;
|
|
90
|
+
try {
|
|
91
|
+
serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw new CacheError('Failed to serialize value', 'SERIALIZATION_ERROR', err);
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await kv.put(fullKey, serialized, {
|
|
98
|
+
expirationTtl: ttl
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new CacheError(`Failed to set key: ${fullKey}`, 'SET_FAILED', err);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Delete a value from the cache
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* await cache.del(kv, 'user:123');
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export async function del(kv, key, options) {
|
|
114
|
+
const fullKey = buildKey(options?.namespace, key);
|
|
115
|
+
try {
|
|
116
|
+
await kv.delete(fullKey);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
throw new CacheError(`Failed to delete key: ${fullKey}`, 'DELETE_FAILED', err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get a value from cache, or compute and store it if missing
|
|
124
|
+
* This is the most common caching pattern.
|
|
125
|
+
*
|
|
126
|
+
* NOTE: Cache writes are fire-and-forget for better response time. This means
|
|
127
|
+
* subsequent requests might miss the cache briefly until the write completes.
|
|
128
|
+
* Use `getOrSetSync` if you need to ensure the value is cached before returning
|
|
129
|
+
* (e.g., when cache consistency is critical).
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```ts
|
|
133
|
+
* const user = await cache.getOrSet(kv, `user:${id}`, {
|
|
134
|
+
* ttl: 3600,
|
|
135
|
+
* compute: async () => {
|
|
136
|
+
* return await db.queryOne<User>('SELECT * FROM users WHERE id = ?', [id]);
|
|
137
|
+
* }
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export async function getOrSet(kv, key, options) {
|
|
142
|
+
const { compute, forceRefresh, ...cacheOptions } = options;
|
|
143
|
+
// Try to get from cache first (unless forcing refresh)
|
|
144
|
+
if (!forceRefresh) {
|
|
145
|
+
const cached = await get(kv, key, cacheOptions);
|
|
146
|
+
if (cached !== null) {
|
|
147
|
+
return cached;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Compute the value
|
|
151
|
+
let value;
|
|
152
|
+
try {
|
|
153
|
+
value = await compute();
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw new CacheError('Failed to compute value', 'COMPUTE_FAILED', err);
|
|
157
|
+
}
|
|
158
|
+
// Store in cache (don't await - fire and forget for performance)
|
|
159
|
+
// Errors here are logged but don't fail the request
|
|
160
|
+
set(kv, key, value, cacheOptions).catch((err) => {
|
|
161
|
+
console.error(`[Cache] Failed to store key ${key}:`, err);
|
|
162
|
+
});
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get a value from cache, or compute and store it (awaiting the cache write)
|
|
167
|
+
* Use this when you need to ensure the value is cached before returning.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* const user = await cache.getOrSetSync(kv, `user:${id}`, {
|
|
172
|
+
* ttl: 3600,
|
|
173
|
+
* compute: async () => fetchUserFromApi(id)
|
|
174
|
+
* });
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export async function getOrSetSync(kv, key, options) {
|
|
178
|
+
const { compute, forceRefresh, ...cacheOptions } = options;
|
|
179
|
+
// Try to get from cache first (unless forcing refresh)
|
|
180
|
+
if (!forceRefresh) {
|
|
181
|
+
const cached = await get(kv, key, cacheOptions);
|
|
182
|
+
if (cached !== null) {
|
|
183
|
+
return cached;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Compute the value
|
|
187
|
+
let value;
|
|
188
|
+
try {
|
|
189
|
+
value = await compute();
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
throw new CacheError('Failed to compute value', 'COMPUTE_FAILED', err);
|
|
193
|
+
}
|
|
194
|
+
// Store in cache and wait for it
|
|
195
|
+
await set(kv, key, value, cacheOptions);
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// Batch Operations
|
|
200
|
+
// ============================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Delete multiple keys at once
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* await cache.delMany(kv, ['user:1', 'user:2', 'user:3']);
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export async function delMany(kv, keys, options) {
|
|
210
|
+
await Promise.all(keys.map((key) => del(kv, key, options)));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Delete all keys matching a pattern (by listing and deleting)
|
|
214
|
+
* Note: KV list operations have pagination limits
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* await cache.delByPrefix(kv, 'user:'); // Deletes all user cache entries
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export async function delByPrefix(kv, prefix, options) {
|
|
222
|
+
const fullPrefix = buildKey(options?.namespace, prefix);
|
|
223
|
+
let deleted = 0;
|
|
224
|
+
let cursor;
|
|
225
|
+
do {
|
|
226
|
+
const list = await kv.list({ prefix: fullPrefix, cursor });
|
|
227
|
+
const keys = list.keys.map((k) => k.name);
|
|
228
|
+
if (keys.length > 0) {
|
|
229
|
+
await Promise.all(keys.map((key) => kv.delete(key)));
|
|
230
|
+
deleted += keys.length;
|
|
231
|
+
}
|
|
232
|
+
cursor = list.list_complete ? undefined : list.cursor;
|
|
233
|
+
} while (cursor);
|
|
234
|
+
return deleted;
|
|
235
|
+
}
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// Utility Functions
|
|
238
|
+
// ============================================================================
|
|
239
|
+
/**
|
|
240
|
+
* Check if a key exists in the cache
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* if (await cache.has(kv, 'user:123')) {
|
|
245
|
+
* // Key exists
|
|
246
|
+
* }
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
export async function has(kv, key, options) {
|
|
250
|
+
const value = await get(kv, key, options);
|
|
251
|
+
return value !== null;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Touch a key (refresh its TTL without changing value)
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```ts
|
|
258
|
+
* await cache.touch(kv, 'session:abc', { ttl: 3600 });
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
export async function touch(kv, key, options) {
|
|
262
|
+
const value = await get(kv, key, options);
|
|
263
|
+
if (value === null) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
await set(kv, key, value, options);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// Rate Limiting Helpers
|
|
271
|
+
// ============================================================================
|
|
272
|
+
/**
|
|
273
|
+
* Simple rate limiting using KV
|
|
274
|
+
* Returns true if the action is allowed, false if rate limited
|
|
275
|
+
*
|
|
276
|
+
* LIMITATION: This implementation has a read-modify-write pattern that is not
|
|
277
|
+
* atomic. Under high concurrency, multiple requests may exceed the limit slightly.
|
|
278
|
+
* For precise rate limiting in high-traffic scenarios, consider using Cloudflare
|
|
279
|
+
* Durable Objects or an external rate limiting service.
|
|
280
|
+
*
|
|
281
|
+
* For most use cases (login attempts, API throttling), this is sufficient as
|
|
282
|
+
* slight over-allowance is acceptable.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* // Allow 5 login attempts per 15 minutes
|
|
287
|
+
* const result = await cache.rateLimit(kv, `login:${email}`, {
|
|
288
|
+
* limit: 5,
|
|
289
|
+
* windowSeconds: 900
|
|
290
|
+
* });
|
|
291
|
+
* if (!result.allowed) {
|
|
292
|
+
* throw new Error('Too many attempts');
|
|
293
|
+
* }
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
export async function rateLimit(kv, key, options) {
|
|
297
|
+
const fullKey = buildKey(options.namespace ?? 'ratelimit', key);
|
|
298
|
+
// Get current count
|
|
299
|
+
const data = await get(kv, fullKey);
|
|
300
|
+
const now = Math.floor(Date.now() / 1000);
|
|
301
|
+
// If no data or window expired, start fresh
|
|
302
|
+
if (!data || data.resetAt <= now) {
|
|
303
|
+
const resetAt = now + options.windowSeconds;
|
|
304
|
+
await set(kv, fullKey, { count: 1, resetAt }, { ttl: options.windowSeconds });
|
|
305
|
+
return {
|
|
306
|
+
allowed: true,
|
|
307
|
+
remaining: options.limit - 1,
|
|
308
|
+
resetAt
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Check if over limit
|
|
312
|
+
if (data.count >= options.limit) {
|
|
313
|
+
return {
|
|
314
|
+
allowed: false,
|
|
315
|
+
remaining: 0,
|
|
316
|
+
resetAt: data.resetAt
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// Increment count
|
|
320
|
+
const newCount = data.count + 1;
|
|
321
|
+
const remaining = Math.max(0, options.limit - newCount);
|
|
322
|
+
await set(kv, fullKey, { count: newCount, resetAt: data.resetAt }, { ttl: data.resetAt - now });
|
|
323
|
+
return {
|
|
324
|
+
allowed: true,
|
|
325
|
+
remaining,
|
|
326
|
+
resetAt: data.resetAt
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
// ============================================================================
|
|
330
|
+
// Constants Export
|
|
331
|
+
// ============================================================================
|
|
332
|
+
export const CACHE_DEFAULTS = {
|
|
333
|
+
TTL_SECONDS: DEFAULT_TTL_SECONDS,
|
|
334
|
+
KEY_PREFIX
|
|
335
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Service - D1 SQLite Abstraction
|
|
3
|
+
*
|
|
4
|
+
* Provides typed utilities for D1 database operations:
|
|
5
|
+
* - Type-safe query helpers
|
|
6
|
+
* - Transaction/batch support
|
|
7
|
+
* - Specific error types for debugging
|
|
8
|
+
* - Common utility functions
|
|
9
|
+
*
|
|
10
|
+
* Note: Domain-specific operations (users, sessions, etc.) remain in
|
|
11
|
+
* their respective packages. This module provides shared primitives.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Type alias for D1 database or session
|
|
15
|
+
* Allows functions to accept either a raw database or a session-scoped connection
|
|
16
|
+
*/
|
|
17
|
+
export type D1DatabaseOrSession = D1Database | D1DatabaseSession;
|
|
18
|
+
/**
|
|
19
|
+
* Query result metadata from D1
|
|
20
|
+
*/
|
|
21
|
+
export interface QueryMeta {
|
|
22
|
+
/** Number of rows changed (for INSERT/UPDATE/DELETE) */
|
|
23
|
+
changes: number;
|
|
24
|
+
/** Duration of the query in milliseconds */
|
|
25
|
+
duration: number;
|
|
26
|
+
/** Last inserted row ID (SQLite) */
|
|
27
|
+
lastRowId: number;
|
|
28
|
+
/** Number of rows read */
|
|
29
|
+
rowsRead: number;
|
|
30
|
+
/** Number of rows written */
|
|
31
|
+
rowsWritten: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Result from an execute (non-SELECT) operation
|
|
35
|
+
*/
|
|
36
|
+
export interface ExecuteResult {
|
|
37
|
+
success: boolean;
|
|
38
|
+
meta: QueryMeta;
|
|
39
|
+
}
|
|
40
|
+
export declare class DatabaseError extends Error {
|
|
41
|
+
readonly code: DatabaseErrorCode;
|
|
42
|
+
readonly cause?: unknown | undefined;
|
|
43
|
+
constructor(message: string, code: DatabaseErrorCode, cause?: unknown | undefined);
|
|
44
|
+
}
|
|
45
|
+
export type DatabaseErrorCode = 'QUERY_FAILED' | 'NOT_FOUND' | 'CONSTRAINT_VIOLATION' | 'TRANSACTION_FAILED' | 'CONNECTION_ERROR' | 'INVALID_QUERY';
|
|
46
|
+
/**
|
|
47
|
+
* Generate a UUID v4 identifier
|
|
48
|
+
*/
|
|
49
|
+
export declare function generateId(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Get current timestamp in ISO format
|
|
52
|
+
*/
|
|
53
|
+
export declare function now(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Get a timestamp for a future time
|
|
56
|
+
* @param ms - Milliseconds from now
|
|
57
|
+
*/
|
|
58
|
+
export declare function futureTimestamp(ms: number): string;
|
|
59
|
+
/**
|
|
60
|
+
* Check if a timestamp has expired
|
|
61
|
+
*/
|
|
62
|
+
export declare function isExpired(timestamp: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Execute a query expecting a single row result
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const user = await db.queryOne<User>(
|
|
69
|
+
* db,
|
|
70
|
+
* 'SELECT * FROM users WHERE id = ?',
|
|
71
|
+
* [userId]
|
|
72
|
+
* );
|
|
73
|
+
* if (!user) throw new Error('User not found');
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function queryOne<T>(db: D1DatabaseOrSession, sql: string, params?: unknown[]): Promise<T | null>;
|
|
77
|
+
/**
|
|
78
|
+
* Execute a query expecting a single row, throw if not found
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const user = await db.queryOneOrThrow<User>(
|
|
83
|
+
* db,
|
|
84
|
+
* 'SELECT * FROM users WHERE id = ?',
|
|
85
|
+
* [userId]
|
|
86
|
+
* );
|
|
87
|
+
* // user is guaranteed to exist here
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare function queryOneOrThrow<T>(db: D1DatabaseOrSession, sql: string, params?: unknown[], errorMessage?: string): Promise<T>;
|
|
91
|
+
/**
|
|
92
|
+
* Execute a query expecting multiple rows
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const users = await db.queryMany<User>(
|
|
97
|
+
* db,
|
|
98
|
+
* 'SELECT * FROM users WHERE is_admin = ?',
|
|
99
|
+
* [1]
|
|
100
|
+
* );
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare function queryMany<T>(db: D1DatabaseOrSession, sql: string, params?: unknown[]): Promise<T[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Execute a non-SELECT statement (INSERT, UPDATE, DELETE)
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const result = await db.execute(
|
|
110
|
+
* db,
|
|
111
|
+
* 'UPDATE users SET name = ? WHERE id = ?',
|
|
112
|
+
* [newName, userId]
|
|
113
|
+
* );
|
|
114
|
+
* console.log(`Updated ${result.meta.changes} rows`);
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function execute(db: D1DatabaseOrSession, sql: string, params?: unknown[]): Promise<ExecuteResult>;
|
|
118
|
+
/**
|
|
119
|
+
* Execute a statement and throw if no rows were affected
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* await db.executeOrThrow(
|
|
124
|
+
* db,
|
|
125
|
+
* 'DELETE FROM sessions WHERE id = ?',
|
|
126
|
+
* [sessionId],
|
|
127
|
+
* 'Session not found'
|
|
128
|
+
* );
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export declare function executeOrThrow(db: D1DatabaseOrSession, sql: string, params?: unknown[], errorMessage?: string): Promise<ExecuteResult>;
|
|
132
|
+
/**
|
|
133
|
+
* Execute multiple statements atomically
|
|
134
|
+
* All statements succeed or all fail (D1 batch semantics)
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* await db.batch(db, [
|
|
139
|
+
* { sql: 'INSERT INTO users (id, email) VALUES (?, ?)', params: [id, email] },
|
|
140
|
+
* { sql: 'INSERT INTO profiles (user_id) VALUES (?)', params: [id] }
|
|
141
|
+
* ]);
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export declare function batch(db: D1Database, statements: Array<{
|
|
145
|
+
sql: string;
|
|
146
|
+
params?: unknown[];
|
|
147
|
+
}>): Promise<ExecuteResult[]>;
|
|
148
|
+
/**
|
|
149
|
+
* Execute a function within a database session for read consistency
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const result = await db.withSession(db, async (session) => {
|
|
154
|
+
* const user = await queryOne<User>(session, 'SELECT * FROM users WHERE id = ?', [id]);
|
|
155
|
+
* const posts = await queryMany<Post>(session, 'SELECT * FROM posts WHERE user_id = ?', [id]);
|
|
156
|
+
* return { user, posts };
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export declare function withSession<T>(db: D1Database, fn: (session: D1DatabaseSession) => Promise<T>): Promise<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Insert a row and return the generated ID
|
|
163
|
+
*
|
|
164
|
+
* SECURITY: Table and column names are validated to prevent SQL injection.
|
|
165
|
+
* Only use with hardcoded names, never user input.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const id = await db.insert(db, 'users', {
|
|
170
|
+
* email: 'user@example.com',
|
|
171
|
+
* name: 'John'
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export declare function insert(db: D1DatabaseOrSession, table: string, data: Record<string, unknown>, options?: {
|
|
176
|
+
id?: string;
|
|
177
|
+
}): Promise<string>;
|
|
178
|
+
/**
|
|
179
|
+
* Update rows matching a condition
|
|
180
|
+
*
|
|
181
|
+
* SECURITY: Table and column names are validated to prevent SQL injection.
|
|
182
|
+
* Only use with hardcoded names, never user input.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* const changes = await db.update(db, 'users', { name: 'Jane' }, 'id = ?', [userId]);
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export declare function update(db: D1DatabaseOrSession, table: string, data: Record<string, unknown>, where: string, whereParams?: unknown[]): Promise<number>;
|
|
190
|
+
/**
|
|
191
|
+
* Delete rows matching a condition
|
|
192
|
+
*
|
|
193
|
+
* SECURITY: Table name is validated to prevent SQL injection.
|
|
194
|
+
* Only use with hardcoded table names, never user input.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* const deleted = await db.deleteWhere(db, 'sessions', 'expires_at < datetime("now")');
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export declare function deleteWhere(db: D1DatabaseOrSession, table: string, where: string, whereParams?: unknown[]): Promise<number>;
|
|
202
|
+
/**
|
|
203
|
+
* Delete a single row by ID
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* await db.deleteById(db, 'users', userId);
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
export declare function deleteById(db: D1DatabaseOrSession, table: string, id: string): Promise<boolean>;
|
|
211
|
+
/**
|
|
212
|
+
* Check if a row exists
|
|
213
|
+
*
|
|
214
|
+
* SECURITY: Table name is validated to prevent SQL injection.
|
|
215
|
+
* Only use with hardcoded table names, never user input.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* if (await db.exists(db, 'users', 'email = ?', [email])) {
|
|
220
|
+
* throw new Error('Email already registered');
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export declare function exists(db: D1DatabaseOrSession, table: string, where: string, whereParams?: unknown[]): Promise<boolean>;
|
|
225
|
+
/**
|
|
226
|
+
* Count rows matching a condition
|
|
227
|
+
*
|
|
228
|
+
* SECURITY: Table name is validated to prevent SQL injection.
|
|
229
|
+
* Only use with hardcoded table names, never user input.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```ts
|
|
233
|
+
* const activeUsers = await db.count(db, 'users', 'is_active = ?', [1]);
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
export declare function count(db: D1DatabaseOrSession, table: string, where?: string, whereParams?: unknown[]): Promise<number>;
|