@autumnsgrove/groveengine 0.5.0 → 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/UpgradePrompt.svelte +8 -7
- package/dist/groveauth/limits.js +11 -3
- package/dist/groveauth/types.d.ts +9 -7
- package/dist/groveauth/types.js +16 -12
- 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,450 @@
|
|
|
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
|
+
// Errors
|
|
15
|
+
// ============================================================================
|
|
16
|
+
export class DatabaseError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
cause;
|
|
19
|
+
constructor(message, code, cause) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.cause = cause;
|
|
23
|
+
this.name = 'DatabaseError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Utility Functions
|
|
28
|
+
// ============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Generate a UUID v4 identifier
|
|
31
|
+
*/
|
|
32
|
+
export function generateId() {
|
|
33
|
+
return crypto.randomUUID();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get current timestamp in ISO format
|
|
37
|
+
*/
|
|
38
|
+
export function now() {
|
|
39
|
+
return new Date().toISOString();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a timestamp for a future time
|
|
43
|
+
* @param ms - Milliseconds from now
|
|
44
|
+
*/
|
|
45
|
+
export function futureTimestamp(ms) {
|
|
46
|
+
return new Date(Date.now() + ms).toISOString();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a timestamp has expired
|
|
50
|
+
*/
|
|
51
|
+
export function isExpired(timestamp) {
|
|
52
|
+
return new Date(timestamp) < new Date();
|
|
53
|
+
}
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Query Helpers
|
|
56
|
+
// ============================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Execute a query expecting a single row result
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const user = await db.queryOne<User>(
|
|
63
|
+
* db,
|
|
64
|
+
* 'SELECT * FROM users WHERE id = ?',
|
|
65
|
+
* [userId]
|
|
66
|
+
* );
|
|
67
|
+
* if (!user) throw new Error('User not found');
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export async function queryOne(db, sql, params = []) {
|
|
71
|
+
try {
|
|
72
|
+
const result = await db
|
|
73
|
+
.prepare(sql)
|
|
74
|
+
.bind(...params)
|
|
75
|
+
.first();
|
|
76
|
+
return result ?? null;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
throw new DatabaseError(`Query failed: ${sql}`, 'QUERY_FAILED', err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute a query expecting a single row, throw if not found
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const user = await db.queryOneOrThrow<User>(
|
|
88
|
+
* db,
|
|
89
|
+
* 'SELECT * FROM users WHERE id = ?',
|
|
90
|
+
* [userId]
|
|
91
|
+
* );
|
|
92
|
+
* // user is guaranteed to exist here
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export async function queryOneOrThrow(db, sql, params = [], errorMessage = 'Record not found') {
|
|
96
|
+
const result = await queryOne(db, sql, params);
|
|
97
|
+
if (result === null) {
|
|
98
|
+
throw new DatabaseError(errorMessage, 'NOT_FOUND');
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Execute a query expecting multiple rows
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const users = await db.queryMany<User>(
|
|
108
|
+
* db,
|
|
109
|
+
* 'SELECT * FROM users WHERE is_admin = ?',
|
|
110
|
+
* [1]
|
|
111
|
+
* );
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export async function queryMany(db, sql, params = []) {
|
|
115
|
+
try {
|
|
116
|
+
const result = await db
|
|
117
|
+
.prepare(sql)
|
|
118
|
+
.bind(...params)
|
|
119
|
+
.all();
|
|
120
|
+
return result.results ?? [];
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
throw new DatabaseError(`Query failed: ${sql}`, 'QUERY_FAILED', err);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Execute a non-SELECT statement (INSERT, UPDATE, DELETE)
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const result = await db.execute(
|
|
132
|
+
* db,
|
|
133
|
+
* 'UPDATE users SET name = ? WHERE id = ?',
|
|
134
|
+
* [newName, userId]
|
|
135
|
+
* );
|
|
136
|
+
* console.log(`Updated ${result.meta.changes} rows`);
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export async function execute(db, sql, params = []) {
|
|
140
|
+
try {
|
|
141
|
+
const result = await db
|
|
142
|
+
.prepare(sql)
|
|
143
|
+
.bind(...params)
|
|
144
|
+
.run();
|
|
145
|
+
return {
|
|
146
|
+
success: result.success,
|
|
147
|
+
meta: {
|
|
148
|
+
changes: result.meta.changes ?? 0,
|
|
149
|
+
duration: result.meta.duration,
|
|
150
|
+
lastRowId: result.meta.last_row_id ?? 0,
|
|
151
|
+
rowsRead: result.meta.rows_read ?? 0,
|
|
152
|
+
rowsWritten: result.meta.rows_written ?? 0
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
throw new DatabaseError(`Execute failed: ${sql}`, 'QUERY_FAILED', err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Execute a statement and throw if no rows were affected
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* await db.executeOrThrow(
|
|
166
|
+
* db,
|
|
167
|
+
* 'DELETE FROM sessions WHERE id = ?',
|
|
168
|
+
* [sessionId],
|
|
169
|
+
* 'Session not found'
|
|
170
|
+
* );
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export async function executeOrThrow(db, sql, params = [], errorMessage = 'No rows affected') {
|
|
174
|
+
const result = await execute(db, sql, params);
|
|
175
|
+
if (result.meta.changes === 0) {
|
|
176
|
+
throw new DatabaseError(errorMessage, 'NOT_FOUND');
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// Batch / Transaction Helpers
|
|
182
|
+
// ============================================================================
|
|
183
|
+
/**
|
|
184
|
+
* Execute multiple statements atomically
|
|
185
|
+
* All statements succeed or all fail (D1 batch semantics)
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* await db.batch(db, [
|
|
190
|
+
* { sql: 'INSERT INTO users (id, email) VALUES (?, ?)', params: [id, email] },
|
|
191
|
+
* { sql: 'INSERT INTO profiles (user_id) VALUES (?)', params: [id] }
|
|
192
|
+
* ]);
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export async function batch(db, statements) {
|
|
196
|
+
try {
|
|
197
|
+
const prepared = statements.map((stmt, index) => {
|
|
198
|
+
try {
|
|
199
|
+
return db.prepare(stmt.sql).bind(...(stmt.params ?? []));
|
|
200
|
+
}
|
|
201
|
+
catch (prepareErr) {
|
|
202
|
+
throw new DatabaseError(`Batch statement ${index + 1}/${statements.length} failed to prepare: ${stmt.sql.slice(0, 100)}`, 'INVALID_QUERY', prepareErr);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
const results = await db.batch(prepared);
|
|
206
|
+
return results.map((result) => ({
|
|
207
|
+
success: result.success,
|
|
208
|
+
meta: {
|
|
209
|
+
changes: result.meta.changes ?? 0,
|
|
210
|
+
duration: result.meta.duration,
|
|
211
|
+
lastRowId: result.meta.last_row_id ?? 0,
|
|
212
|
+
rowsRead: result.meta.rows_read ?? 0,
|
|
213
|
+
rowsWritten: result.meta.rows_written ?? 0
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
if (err instanceof DatabaseError) {
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
// Include statement count for context
|
|
222
|
+
const stmtSummary = statements.map((s, i) => `${i + 1}: ${s.sql.slice(0, 50)}...`).join('; ');
|
|
223
|
+
throw new DatabaseError(`Batch operation failed (${statements.length} statements: ${stmtSummary})`, 'TRANSACTION_FAILED', err);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Execute a function within a database session for read consistency
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* const result = await db.withSession(db, async (session) => {
|
|
232
|
+
* const user = await queryOne<User>(session, 'SELECT * FROM users WHERE id = ?', [id]);
|
|
233
|
+
* const posts = await queryMany<Post>(session, 'SELECT * FROM posts WHERE user_id = ?', [id]);
|
|
234
|
+
* return { user, posts };
|
|
235
|
+
* });
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export async function withSession(db, fn) {
|
|
239
|
+
const session = db.withSession();
|
|
240
|
+
try {
|
|
241
|
+
return await fn(session);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
throw new DatabaseError('Session operation failed', 'TRANSACTION_FAILED', err);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Table Name Validation
|
|
249
|
+
// ============================================================================
|
|
250
|
+
/**
|
|
251
|
+
* Valid identifier pattern - alphanumeric and underscores only
|
|
252
|
+
* This prevents SQL injection in functions that accept table/column names
|
|
253
|
+
*/
|
|
254
|
+
const VALID_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
255
|
+
function validateTableName(table) {
|
|
256
|
+
if (!VALID_IDENTIFIER.test(table)) {
|
|
257
|
+
throw new DatabaseError(`Invalid table name: ${table}. Table names must be alphanumeric with underscores only.`, 'INVALID_QUERY');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function validateColumnName(column) {
|
|
261
|
+
if (!VALID_IDENTIFIER.test(column)) {
|
|
262
|
+
throw new DatabaseError(`Invalid column name: ${column}. Column names must be alphanumeric with underscores only.`, 'INVALID_QUERY');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function validateColumnNames(columns) {
|
|
266
|
+
for (const column of columns) {
|
|
267
|
+
validateColumnName(column);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Insert Helpers
|
|
272
|
+
// ============================================================================
|
|
273
|
+
/**
|
|
274
|
+
* Insert a row and return the generated ID
|
|
275
|
+
*
|
|
276
|
+
* SECURITY: Table and column names are validated to prevent SQL injection.
|
|
277
|
+
* Only use with hardcoded names, never user input.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* const id = await db.insert(db, 'users', {
|
|
282
|
+
* email: 'user@example.com',
|
|
283
|
+
* name: 'John'
|
|
284
|
+
* });
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
export async function insert(db, table, data, options) {
|
|
288
|
+
validateTableName(table);
|
|
289
|
+
validateColumnNames(Object.keys(data));
|
|
290
|
+
const id = options?.id ?? generateId();
|
|
291
|
+
const timestamp = now();
|
|
292
|
+
const dataWithMeta = {
|
|
293
|
+
id,
|
|
294
|
+
...data,
|
|
295
|
+
created_at: timestamp,
|
|
296
|
+
updated_at: timestamp
|
|
297
|
+
};
|
|
298
|
+
const columns = Object.keys(dataWithMeta);
|
|
299
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
300
|
+
const values = Object.values(dataWithMeta);
|
|
301
|
+
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;
|
|
302
|
+
try {
|
|
303
|
+
await db
|
|
304
|
+
.prepare(sql)
|
|
305
|
+
.bind(...values)
|
|
306
|
+
.run();
|
|
307
|
+
return id;
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
if (err instanceof Error && err.message.includes('UNIQUE constraint')) {
|
|
311
|
+
throw new DatabaseError(`Duplicate entry in ${table} (columns: ${columns.join(', ')})`, 'CONSTRAINT_VIOLATION', err);
|
|
312
|
+
}
|
|
313
|
+
throw new DatabaseError(`Insert into ${table} failed (columns: ${columns.join(', ')})`, 'QUERY_FAILED', err);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// Update Helpers
|
|
318
|
+
// ============================================================================
|
|
319
|
+
/**
|
|
320
|
+
* Update rows matching a condition
|
|
321
|
+
*
|
|
322
|
+
* SECURITY: Table and column names are validated to prevent SQL injection.
|
|
323
|
+
* Only use with hardcoded names, never user input.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* const changes = await db.update(db, 'users', { name: 'Jane' }, 'id = ?', [userId]);
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
export async function update(db, table, data, where, whereParams = []) {
|
|
331
|
+
validateTableName(table);
|
|
332
|
+
validateColumnNames(Object.keys(data));
|
|
333
|
+
const dataWithTimestamp = {
|
|
334
|
+
...data,
|
|
335
|
+
updated_at: now()
|
|
336
|
+
};
|
|
337
|
+
const setClauses = Object.keys(dataWithTimestamp)
|
|
338
|
+
.map((key) => `${key} = ?`)
|
|
339
|
+
.join(', ');
|
|
340
|
+
const values = [...Object.values(dataWithTimestamp), ...whereParams];
|
|
341
|
+
const sql = `UPDATE ${table} SET ${setClauses} WHERE ${where}`;
|
|
342
|
+
try {
|
|
343
|
+
const result = await db
|
|
344
|
+
.prepare(sql)
|
|
345
|
+
.bind(...values)
|
|
346
|
+
.run();
|
|
347
|
+
return result.meta.changes ?? 0;
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
const fields = Object.keys(dataWithTimestamp).join(', ');
|
|
351
|
+
throw new DatabaseError(`Update ${table} failed (fields: ${fields}, where: ${where})`, 'QUERY_FAILED', err);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// ============================================================================
|
|
355
|
+
// Delete Helpers
|
|
356
|
+
// ============================================================================
|
|
357
|
+
/**
|
|
358
|
+
* Delete rows matching a condition
|
|
359
|
+
*
|
|
360
|
+
* SECURITY: Table name is validated to prevent SQL injection.
|
|
361
|
+
* Only use with hardcoded table names, never user input.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* const deleted = await db.deleteWhere(db, 'sessions', 'expires_at < datetime("now")');
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
export async function deleteWhere(db, table, where, whereParams = []) {
|
|
369
|
+
validateTableName(table);
|
|
370
|
+
const sql = `DELETE FROM ${table} WHERE ${where}`;
|
|
371
|
+
try {
|
|
372
|
+
const result = await db
|
|
373
|
+
.prepare(sql)
|
|
374
|
+
.bind(...whereParams)
|
|
375
|
+
.run();
|
|
376
|
+
return result.meta.changes ?? 0;
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
throw new DatabaseError(`Delete from ${table} failed (where: ${where})`, 'QUERY_FAILED', err);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Delete a single row by ID
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* await db.deleteById(db, 'users', userId);
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
export async function deleteById(db, table, id) {
|
|
391
|
+
const changes = await deleteWhere(db, table, 'id = ?', [id]);
|
|
392
|
+
return changes > 0;
|
|
393
|
+
}
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// Existence Checks
|
|
396
|
+
// ============================================================================
|
|
397
|
+
/**
|
|
398
|
+
* Check if a row exists
|
|
399
|
+
*
|
|
400
|
+
* SECURITY: Table name is validated to prevent SQL injection.
|
|
401
|
+
* Only use with hardcoded table names, never user input.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* if (await db.exists(db, 'users', 'email = ?', [email])) {
|
|
406
|
+
* throw new Error('Email already registered');
|
|
407
|
+
* }
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
410
|
+
export async function exists(db, table, where, whereParams = []) {
|
|
411
|
+
validateTableName(table);
|
|
412
|
+
const sql = `SELECT 1 FROM ${table} WHERE ${where} LIMIT 1`;
|
|
413
|
+
try {
|
|
414
|
+
const result = await db
|
|
415
|
+
.prepare(sql)
|
|
416
|
+
.bind(...whereParams)
|
|
417
|
+
.first();
|
|
418
|
+
return result !== null;
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
throw new DatabaseError(`Existence check on ${table} failed`, 'QUERY_FAILED', err);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Count rows matching a condition
|
|
426
|
+
*
|
|
427
|
+
* SECURITY: Table name is validated to prevent SQL injection.
|
|
428
|
+
* Only use with hardcoded table names, never user input.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* const activeUsers = await db.count(db, 'users', 'is_active = ?', [1]);
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
export async function count(db, table, where, whereParams = []) {
|
|
436
|
+
validateTableName(table);
|
|
437
|
+
const sql = where
|
|
438
|
+
? `SELECT COUNT(*) as count FROM ${table} WHERE ${where}`
|
|
439
|
+
: `SELECT COUNT(*) as count FROM ${table}`;
|
|
440
|
+
try {
|
|
441
|
+
const result = await db
|
|
442
|
+
.prepare(sql)
|
|
443
|
+
.bind(...whereParams)
|
|
444
|
+
.first();
|
|
445
|
+
return result?.count ?? 0;
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
throw new DatabaseError(`Count on ${table} failed`, 'QUERY_FAILED', err);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grove Engine - Service Modules
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare infrastructure abstraction layer providing clean interfaces
|
|
5
|
+
* for storage (R2), database (D1), and caching (KV) operations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { storage, db, cache } from '@autumnsgrove/groveengine/services';
|
|
10
|
+
*
|
|
11
|
+
* // Storage operations
|
|
12
|
+
* const file = await storage.uploadFile(bucket, database, {
|
|
13
|
+
* data: arrayBuffer,
|
|
14
|
+
* filename: 'photo.jpg',
|
|
15
|
+
* contentType: 'image/jpeg',
|
|
16
|
+
* uploadedBy: userId
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Database operations
|
|
20
|
+
* const user = await db.queryOne<User>(database, 'SELECT * FROM users WHERE id = ?', [id]);
|
|
21
|
+
*
|
|
22
|
+
* // Cache operations
|
|
23
|
+
* const data = await cache.getOrSet(kv, 'user:123', {
|
|
24
|
+
* ttl: 3600,
|
|
25
|
+
* compute: () => fetchUser(id)
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export * as storage from './storage.js';
|
|
30
|
+
export { type StorageFile, type UploadOptions, type GetFileResult, type FileMetadata, StorageError, type StorageErrorCode, uploadFile, getFile, getFileMetadata, fileExists, deleteFile, deleteFileByKey, getFileRecord, getFileRecordByKey, listFiles, listAllFiles, listFolders, updateAltText, validateFile, isAllowedContentType, shouldReturn304, buildFileHeaders } from './storage.js';
|
|
31
|
+
export * as db from './database.js';
|
|
32
|
+
export { type D1DatabaseOrSession, type QueryMeta, type ExecuteResult, DatabaseError, type DatabaseErrorCode, generateId, now, futureTimestamp, isExpired, queryOne, queryOneOrThrow, queryMany, execute, executeOrThrow, batch, withSession, insert, update, deleteWhere, deleteById, exists, count } from './database.js';
|
|
33
|
+
export * as cache from './cache.js';
|
|
34
|
+
export { type CacheOptions, type GetOrSetOptions, CacheError, type CacheErrorCode, get as cacheGet, set as cacheSet, del as cacheDel, getOrSet, getOrSetSync, delMany, delByPrefix, has as cacheHas, touch, rateLimit, CACHE_DEFAULTS } from './cache.js';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grove Engine - Service Modules
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare infrastructure abstraction layer providing clean interfaces
|
|
5
|
+
* for storage (R2), database (D1), and caching (KV) operations.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { storage, db, cache } from '@autumnsgrove/groveengine/services';
|
|
10
|
+
*
|
|
11
|
+
* // Storage operations
|
|
12
|
+
* const file = await storage.uploadFile(bucket, database, {
|
|
13
|
+
* data: arrayBuffer,
|
|
14
|
+
* filename: 'photo.jpg',
|
|
15
|
+
* contentType: 'image/jpeg',
|
|
16
|
+
* uploadedBy: userId
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Database operations
|
|
20
|
+
* const user = await db.queryOne<User>(database, 'SELECT * FROM users WHERE id = ?', [id]);
|
|
21
|
+
*
|
|
22
|
+
* // Cache operations
|
|
23
|
+
* const data = await cache.getOrSet(kv, 'user:123', {
|
|
24
|
+
* ttl: 3600,
|
|
25
|
+
* compute: () => fetchUser(id)
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Storage Service (R2)
|
|
31
|
+
// ============================================================================
|
|
32
|
+
export * as storage from './storage.js';
|
|
33
|
+
export {
|
|
34
|
+
// Errors
|
|
35
|
+
StorageError,
|
|
36
|
+
// Operations
|
|
37
|
+
uploadFile, getFile, getFileMetadata, fileExists, deleteFile, deleteFileByKey,
|
|
38
|
+
// Metadata Operations
|
|
39
|
+
getFileRecord, getFileRecordByKey, listFiles, listAllFiles, listFolders, updateAltText,
|
|
40
|
+
// Validation
|
|
41
|
+
validateFile, isAllowedContentType,
|
|
42
|
+
// Response Helpers
|
|
43
|
+
shouldReturn304, buildFileHeaders } from './storage.js';
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Database Service (D1)
|
|
46
|
+
// ============================================================================
|
|
47
|
+
export * as db from './database.js';
|
|
48
|
+
export {
|
|
49
|
+
// Errors
|
|
50
|
+
DatabaseError,
|
|
51
|
+
// Utilities
|
|
52
|
+
generateId, now, futureTimestamp, isExpired,
|
|
53
|
+
// Query Helpers
|
|
54
|
+
queryOne, queryOneOrThrow, queryMany, execute, executeOrThrow,
|
|
55
|
+
// Batch Operations
|
|
56
|
+
batch, withSession,
|
|
57
|
+
// CRUD Helpers
|
|
58
|
+
insert, update, deleteWhere, deleteById,
|
|
59
|
+
// Existence Checks
|
|
60
|
+
exists, count } from './database.js';
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Cache Service (KV)
|
|
63
|
+
// ============================================================================
|
|
64
|
+
export * as cache from './cache.js';
|
|
65
|
+
export {
|
|
66
|
+
// Errors
|
|
67
|
+
CacheError,
|
|
68
|
+
// Operations
|
|
69
|
+
get as cacheGet, set as cacheSet, del as cacheDel, getOrSet, getOrSetSync,
|
|
70
|
+
// Batch Operations
|
|
71
|
+
delMany, delByPrefix,
|
|
72
|
+
// Utilities
|
|
73
|
+
has as cacheHas, touch,
|
|
74
|
+
// Rate Limiting
|
|
75
|
+
rateLimit,
|
|
76
|
+
// Constants
|
|
77
|
+
CACHE_DEFAULTS } from './cache.js';
|