@earth-app/collegedb 1.0.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/LICENSE +21 -0
- package/README.md +518 -0
- package/dist/durable.d.ts +241 -0
- package/dist/durable.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +14 -0
- package/dist/kvmap.d.ts +255 -0
- package/dist/kvmap.d.ts.map +1 -0
- package/dist/migrations.d.ts +392 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/router.d.ts +509 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +50 -0
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main routing and query distribution logic for CollegeDB
|
|
3
|
+
*
|
|
4
|
+
* This module provides the core functionality for routing database queries to the
|
|
5
|
+
* appropriate D1 shard based on primary key mappings. It handles shard selection,
|
|
6
|
+
* database routing, and provides a unified API for CRUD operations across multiple
|
|
7
|
+
* distributed D1 databases.
|
|
8
|
+
*
|
|
9
|
+
* Key responsibilities:
|
|
10
|
+
* - Initialize and manage the global CollegeDB configuration
|
|
11
|
+
* - Route queries to appropriate shards based on primary key mappings
|
|
12
|
+
* - Implement shard allocation strategies (round-robin, random, hash-based)
|
|
13
|
+
* - Provide unified CRUD operations across distributed shards
|
|
14
|
+
* - Coordinate with Durable Objects for centralized shard management
|
|
15
|
+
* - Handle shard rebalancing and data migration
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { initialize, insert, first, run } from 'collegedb';
|
|
20
|
+
*
|
|
21
|
+
* // Initialize the system
|
|
22
|
+
* initialize({
|
|
23
|
+
* kv: env.KV,
|
|
24
|
+
* coordinator: env.ShardCoordinator,
|
|
25
|
+
* shards: {
|
|
26
|
+
* 'db-east': env.DB_EAST,
|
|
27
|
+
* 'db-west': env.DB_WEST
|
|
28
|
+
* },
|
|
29
|
+
* strategy: 'hash'
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Insert a record (automatically routed to appropriate shard)
|
|
33
|
+
* await run('user-123', 'INSERT INTO users (id, name) VALUES (?, ?)', ['user-123', 'John']);
|
|
34
|
+
*
|
|
35
|
+
* // Query the record (routed to same shard)
|
|
36
|
+
* const result = await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @author CollegeDB Team
|
|
40
|
+
* @since 1.0.0
|
|
41
|
+
*/
|
|
42
|
+
import type { D1Database, D1PreparedStatement, D1Result } from '@cloudflare/workers-types';
|
|
43
|
+
import type { CollegeDBConfig, ShardStats } from './types.js';
|
|
44
|
+
/**
|
|
45
|
+
* Sets up the global configuration for the CollegeDB system. This must be called
|
|
46
|
+
* before any other operations can be performed. The configuration includes KV
|
|
47
|
+
* storage, available D1 shards, optional coordinator, and allocation strategy.
|
|
48
|
+
*
|
|
49
|
+
* This will also automatically detect and migrate existing databases without requiring
|
|
50
|
+
* additional setup. If shards contain existing data with primary keys, CollegeDB
|
|
51
|
+
* will automatically create the necessary mappings for seamless operation.
|
|
52
|
+
*
|
|
53
|
+
* @param config - Configuration object containing all necessary bindings and settings
|
|
54
|
+
* @throws {Error} If configuration is invalid or required bindings are missing
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* // Basic setup with multiple shards - auto-migration happens automatically
|
|
58
|
+
* initialize({
|
|
59
|
+
* kv: env.KV,
|
|
60
|
+
* shards: {
|
|
61
|
+
* 'db-primary': env.DB_PRIMARY, // Existing DB with data
|
|
62
|
+
* 'db-secondary': env.DB_SECONDARY // Another existing DB
|
|
63
|
+
* },
|
|
64
|
+
* strategy: 'round-robin'
|
|
65
|
+
* });
|
|
66
|
+
* // Existing data is now automatically accessible via CollegeDB!
|
|
67
|
+
*
|
|
68
|
+
* // Advanced setup with coordinator
|
|
69
|
+
* initialize({
|
|
70
|
+
* kv: env.KV,
|
|
71
|
+
* coordinator: env.ShardCoordinator,
|
|
72
|
+
* shards: {
|
|
73
|
+
* 'db-east': env.DB_EAST,
|
|
74
|
+
* 'db-west': env.DB_WEST,
|
|
75
|
+
* 'db-central': env.DB_CENTRAL
|
|
76
|
+
* },
|
|
77
|
+
* strategy: 'hash'
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function initialize(config: CollegeDBConfig): void;
|
|
82
|
+
/**
|
|
83
|
+
* Sets up the global configuration for the CollegeDB system asynchronously.
|
|
84
|
+
* This must be called before any other operations can be performed. The
|
|
85
|
+
* configuration includes KVstorage, available D1 shards, optional coordinator,
|
|
86
|
+
* and allocation strategy.
|
|
87
|
+
*
|
|
88
|
+
* This will also automatically detect and migrate existing databases without requiring
|
|
89
|
+
* additional setup. If shards contain existing data with primary keys, CollegeDB
|
|
90
|
+
* will automatically create the necessary mappings for seamless operation.
|
|
91
|
+
*
|
|
92
|
+
* Compared to `initialize`, this method waits for the background check to finish.
|
|
93
|
+
*
|
|
94
|
+
* @param config - Configuration object containing all necessary bindings and settings
|
|
95
|
+
* @throws {Error} If configuration is invalid or required bindings are missing
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* // Basic setup with multiple shards - auto-migration happens automatically
|
|
99
|
+
* initializeAsync({
|
|
100
|
+
* kv: env.KV,
|
|
101
|
+
* shards: {
|
|
102
|
+
* 'db-primary': env.DB_PRIMARY, // Existing DB with data
|
|
103
|
+
* 'db-secondary': env.DB_SECONDARY // Another existing DB
|
|
104
|
+
* },
|
|
105
|
+
* strategy: 'round-robin'
|
|
106
|
+
* });
|
|
107
|
+
* // Existing data is now automatically accessible via CollegeDB!
|
|
108
|
+
*
|
|
109
|
+
* // Advanced setup with coordinator
|
|
110
|
+
* initializeAsync({
|
|
111
|
+
* kv: env.KV,
|
|
112
|
+
* coordinator: env.ShardCoordinator,
|
|
113
|
+
* shards: {
|
|
114
|
+
* 'db-east': env.DB_EAST,
|
|
115
|
+
* 'db-west': env.DB_WEST,
|
|
116
|
+
* 'db-central': env.DB_CENTRAL
|
|
117
|
+
* },
|
|
118
|
+
* strategy: 'hash'
|
|
119
|
+
* });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function initializeAsync(config: CollegeDBConfig): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Initializes the configuration and then performs a callback once the configuration
|
|
125
|
+
* has finished initializing.
|
|
126
|
+
*
|
|
127
|
+
* @param config - CollegeDB Configuration
|
|
128
|
+
* @param callback - The callback to perform after the initialization
|
|
129
|
+
* @returns The result of the callback
|
|
130
|
+
* @example
|
|
131
|
+
* ```
|
|
132
|
+
* import { collegedb, first } from 'collegedb'
|
|
133
|
+
*
|
|
134
|
+
* const result = collegedb({
|
|
135
|
+
* kv: env.KV,
|
|
136
|
+
* shards: {
|
|
137
|
+
* 'db-primary': env.DB_PRIMARY, // Existing DB with data
|
|
138
|
+
* 'db-secondary': env.DB_SECONDARY // Another existing DB
|
|
139
|
+
* },
|
|
140
|
+
* strategy: 'hash'
|
|
141
|
+
* }, async () => {
|
|
142
|
+
* return await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare function collegedb<T>(config: CollegeDBConfig, callback: () => T): Promise<T>;
|
|
147
|
+
/**
|
|
148
|
+
* Creates the database schema in the specified D1 database
|
|
149
|
+
*
|
|
150
|
+
* @param d1 - The D1 database instance to create schema in
|
|
151
|
+
* @param schema - The SQL schema definition to execute
|
|
152
|
+
* @returns Promise that resolves when schema creation is complete
|
|
153
|
+
* @throws {Error} If schema creation fails
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const userSchema = `
|
|
157
|
+
* CREATE TABLE users (
|
|
158
|
+
* id TEXT PRIMARY KEY,
|
|
159
|
+
* name TEXT NOT NULL,
|
|
160
|
+
* email TEXT UNIQUE
|
|
161
|
+
* );
|
|
162
|
+
* `;
|
|
163
|
+
* await createSchema(env.DB_NEW_SHARD, userSchema);
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
export declare function createSchema(d1: D1Database, schema: string): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Prepares a SQL statement for execution.
|
|
169
|
+
*
|
|
170
|
+
* @param key - The primary key to route the query
|
|
171
|
+
* @param sql - The SQL statement to prepare
|
|
172
|
+
* @returns Promise that resolves to a prepared statement
|
|
173
|
+
* @throws {Error} If preparation fails
|
|
174
|
+
*/
|
|
175
|
+
export declare function prepare(key: string, sql: string): Promise<D1PreparedStatement>;
|
|
176
|
+
/**
|
|
177
|
+
* Executes a statement on the appropriate shard based on the primary key.
|
|
178
|
+
* The primary key is used to determine which shard should store the record,
|
|
179
|
+
* ensuring consistent routing for future queries.
|
|
180
|
+
*
|
|
181
|
+
* @template T - Type of the result records
|
|
182
|
+
* @param key - Primary key to route the query (should match the record's primary key)
|
|
183
|
+
* @param sql - SQL statement with parameter placeholders
|
|
184
|
+
* @param bindings - Parameter values to bind to the SQL statement
|
|
185
|
+
* @returns Promise that resolves when the statement is complete
|
|
186
|
+
* @throws {Error} If statement fails or routing fails
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* // Insert a new user
|
|
190
|
+
* await run('user-123',
|
|
191
|
+
* 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
|
|
192
|
+
* ['user-123', 'John Doe', 'john@example.com']
|
|
193
|
+
* );
|
|
194
|
+
*
|
|
195
|
+
* // Insert a post linked to a user
|
|
196
|
+
* await run('post-456',
|
|
197
|
+
* 'INSERT INTO posts (id, user_id, title, content) VALUES (?, ?, ?, ?)',
|
|
198
|
+
* ['post-456', 'user-123', 'Hello World', 'My first post!']
|
|
199
|
+
* );
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* // Update user information
|
|
205
|
+
* await run('user-123',
|
|
206
|
+
* 'UPDATE users SET name = ?, email = ? WHERE id = ?',
|
|
207
|
+
* ['John Smith', 'johnsmith@example.com', 'user-123']
|
|
208
|
+
* );
|
|
209
|
+
*
|
|
210
|
+
* // Update post content
|
|
211
|
+
* await run('post-456',
|
|
212
|
+
* 'UPDATE posts SET title = ?, content = ?, updated_at = strftime("%s", "now") WHERE id = ?',
|
|
213
|
+
* ['Updated Title', 'Updated content here', 'post-456']
|
|
214
|
+
* );
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* // Delete a specific user
|
|
220
|
+
* await run('user-123',
|
|
221
|
+
* 'DELETE FROM users WHERE id = ?',
|
|
222
|
+
* ['user-123']
|
|
223
|
+
* );
|
|
224
|
+
*
|
|
225
|
+
* // Delete user's posts (cascade delete)
|
|
226
|
+
* await run('user-123',
|
|
227
|
+
* 'DELETE FROM posts WHERE user_id = ?',
|
|
228
|
+
* ['user-123']
|
|
229
|
+
* );
|
|
230
|
+
*
|
|
231
|
+
* // Delete with conditions
|
|
232
|
+
* await run('user-123',
|
|
233
|
+
* 'DELETE FROM posts WHERE user_id = ? AND created_at < ?',
|
|
234
|
+
* ['user-123', Date.now() - 86400000] // Posts older than 1 day
|
|
235
|
+
* );
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export declare function run<T = Record<string, unknown>>(key: string, sql: string, bindings?: any[]): Promise<D1Result<T>>;
|
|
239
|
+
/**
|
|
240
|
+
* Retrieves all records matching the query for a given primary key.
|
|
241
|
+
*
|
|
242
|
+
* This function is useful for fetching multiple records based on a primary key.
|
|
243
|
+
* It automatically routes the query to the correct shard based on the provided
|
|
244
|
+
* primary key, ensuring consistent data access.
|
|
245
|
+
* @param key - Primary key to route the query
|
|
246
|
+
* @param sql - The SQL statement to execute
|
|
247
|
+
* @param bindings - Parameter values to bind to the SQL statement
|
|
248
|
+
* @returns Promise that resolves to the result of the update operation
|
|
249
|
+
* @throws {Error} If update fails or routing fails
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```typescript
|
|
253
|
+
* type Post = {
|
|
254
|
+
* id: string;
|
|
255
|
+
* user_id: string;
|
|
256
|
+
* title: string;
|
|
257
|
+
* content: string;
|
|
258
|
+
* };
|
|
259
|
+
*
|
|
260
|
+
*
|
|
261
|
+
* // Get user's posts
|
|
262
|
+
* const postsResult = await all<Post>('user-123',
|
|
263
|
+
* 'SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC',
|
|
264
|
+
* ['user-123']
|
|
265
|
+
* );
|
|
266
|
+
*
|
|
267
|
+
* console.log(`User has ${postsResult.meta.count} posts`);
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
export declare function all<T = Record<string, unknown>>(key: string, sql: string, bindings?: any[]): Promise<D1Result<T>>;
|
|
271
|
+
/**
|
|
272
|
+
* Retrieves the first record matching the query for a given primary key.
|
|
273
|
+
*
|
|
274
|
+
* This function is useful for fetching a single record based on a primary key.
|
|
275
|
+
* It automatically routes the query to the correct shard based on the provided
|
|
276
|
+
* primary key, ensuring consistent data access.
|
|
277
|
+
*
|
|
278
|
+
* @template T - Type of the result record
|
|
279
|
+
* @param key - Primary key to route the query
|
|
280
|
+
* @param sql - SQL statement with parameter placeholders
|
|
281
|
+
* @param bindings - Parameter values to bind to the SQL statement
|
|
282
|
+
* @returns Promise that resolves to the first matching record, or null if not found
|
|
283
|
+
* @throws {Error} If query fails or routing fails
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* type User = {
|
|
288
|
+
* id: string;
|
|
289
|
+
* name: string;
|
|
290
|
+
* email: string;
|
|
291
|
+
* };
|
|
292
|
+
* // Get a specific user
|
|
293
|
+
* const userResult = await first<User>('user-123',
|
|
294
|
+
* 'SELECT * FROM users WHERE id = ?',
|
|
295
|
+
* ['user-123']
|
|
296
|
+
* );
|
|
297
|
+
*
|
|
298
|
+
* if (userResult) {
|
|
299
|
+
* console.log(`Found user: ${userResult.name}`);
|
|
300
|
+
* }
|
|
301
|
+
*/
|
|
302
|
+
export declare function first<T = Record<string, unknown>>(key: string, sql: string, bindings?: any[]): Promise<T | null>;
|
|
303
|
+
/**
|
|
304
|
+
* Reassigns a primary key to a different shard
|
|
305
|
+
*
|
|
306
|
+
* Moves a primary key and its associated data from one shard to another. This
|
|
307
|
+
* operation is useful for load balancing, shard maintenance, or geographic
|
|
308
|
+
* redistribution of data.
|
|
309
|
+
*
|
|
310
|
+
* The reassignment process:
|
|
311
|
+
* 1. Validates the target shard exists in configuration
|
|
312
|
+
* 2. Checks that a mapping exists for the primary key
|
|
313
|
+
* 3. If target shard differs from current, migrates the data
|
|
314
|
+
* 4. Updates the KV mapping to point to the new shard
|
|
315
|
+
*
|
|
316
|
+
* **Note**: This operation involves data migration and should be used
|
|
317
|
+
* carefully in production environments. Consider the impact on ongoing queries.
|
|
318
|
+
*
|
|
319
|
+
* @param primaryKey - Primary key to reassign to a different shard
|
|
320
|
+
* @param newBinding - New shard binding name where the data should be moved
|
|
321
|
+
* @param tableName - Name of the table containing the record to migrate
|
|
322
|
+
* @returns Promise that resolves when reassignment and migration are complete
|
|
323
|
+
* @throws {Error} If target shard not found, mapping doesn't exist, or migration fails
|
|
324
|
+
* @example
|
|
325
|
+
* ```typescript
|
|
326
|
+
* // Move a user from east to west coast for better latency
|
|
327
|
+
* try {
|
|
328
|
+
* await reassignShard('user-california-123', 'db-west', 'users');
|
|
329
|
+
* console.log('User successfully moved to west coast shard');
|
|
330
|
+
* } catch (error) {
|
|
331
|
+
* console.error('Reassignment failed:', error.message);
|
|
332
|
+
* }
|
|
333
|
+
*
|
|
334
|
+
* // Load balancing: move high-activity user to dedicated shard
|
|
335
|
+
* await reassignShard('user-enterprise-456', 'db-dedicated', 'users');
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
export declare function reassignShard(primaryKey: string, newBinding: string, tableName: string): Promise<void>;
|
|
339
|
+
/**
|
|
340
|
+
* Lists all known shards
|
|
341
|
+
*
|
|
342
|
+
* Returns an array of all shard binding names known to the system. First
|
|
343
|
+
* attempts to get the list from the Durable Object coordinator for the most
|
|
344
|
+
* up-to-date information, then falls back to the configured shards if the
|
|
345
|
+
* coordinator is unavailable.
|
|
346
|
+
*
|
|
347
|
+
* @returns Promise resolving to array of shard binding names
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* const shards = await listKnownShards();
|
|
351
|
+
* console.log('Available shards:', shards);
|
|
352
|
+
* // Output: ['db-east', 'db-west', 'db-central']
|
|
353
|
+
*
|
|
354
|
+
* // Check if a specific shard is available
|
|
355
|
+
* if (shards.includes('db-asia')) {
|
|
356
|
+
* console.log('Asia region shard is available');
|
|
357
|
+
* }
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
export declare function listKnownShards(): Promise<string[]>;
|
|
361
|
+
/**
|
|
362
|
+
* Gets statistics for all shards
|
|
363
|
+
*
|
|
364
|
+
* Returns usage statistics for all known shards, including key counts and
|
|
365
|
+
* last updated timestamps. First attempts to get real-time statistics from
|
|
366
|
+
* the Durable Object coordinator, then falls back to KV-based counting.
|
|
367
|
+
*
|
|
368
|
+
* This information is useful for:
|
|
369
|
+
* - Load balancing decisions
|
|
370
|
+
* - Monitoring shard utilization
|
|
371
|
+
* - Capacity planning
|
|
372
|
+
* - Performance analysis
|
|
373
|
+
*
|
|
374
|
+
* @returns Promise resolving to array of shard statistics
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* const stats = await getShardStats();
|
|
378
|
+
* stats.forEach(shard => {
|
|
379
|
+
* console.log(`${shard.binding}: ${shard.count} keys`);
|
|
380
|
+
* if (shard.lastUpdated) {
|
|
381
|
+
* console.log(` Last updated: ${new Date(shard.lastUpdated)}`);
|
|
382
|
+
* }
|
|
383
|
+
* });
|
|
384
|
+
*
|
|
385
|
+
* // Find most loaded shard
|
|
386
|
+
* const mostLoaded = stats.reduce((prev, current) =>
|
|
387
|
+
* (prev.count > current.count) ? prev : current
|
|
388
|
+
* );
|
|
389
|
+
* console.log(`Most loaded shard: ${mostLoaded.binding} (${mostLoaded.count} keys)`);
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
export declare function getShardStats(): Promise<ShardStats[]>;
|
|
393
|
+
/**
|
|
394
|
+
* Bypasses the normal routing logic to execute a query directly on a specified
|
|
395
|
+
* shard. This is useful for administrative operations, cross-shard queries,
|
|
396
|
+
* or when you need to query data that doesn't follow the primary key routing pattern.
|
|
397
|
+
*
|
|
398
|
+
* **Use with caution**: This function bypasses routing safeguards and should
|
|
399
|
+
* be used only when you specifically need to target a particular shard.
|
|
400
|
+
*
|
|
401
|
+
* @param shardBinding - The shard binding name to execute the query on
|
|
402
|
+
* @param sql - SQL statement to execute
|
|
403
|
+
* @param bindings - Parameter values to bind to the SQL statement
|
|
404
|
+
* @returns Promise resolving to the result of the query execution
|
|
405
|
+
* @throws {Error} If shard not found or query fails
|
|
406
|
+
* @example
|
|
407
|
+
* ```typescript
|
|
408
|
+
* // Administrative query: insert a new user directly into a specific shard
|
|
409
|
+
* const result = await runShard('db-east',
|
|
410
|
+
* 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',
|
|
411
|
+
* ['user-789', 'Alice', 'alice@example.com']
|
|
412
|
+
* );
|
|
413
|
+
* console.log(`Inserted user with ID: ${result.lastInsertId}`);
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
export declare function runShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings?: any[]): Promise<D1Result<T>>;
|
|
417
|
+
/**
|
|
418
|
+
* Bypasses the normal routing logic to execute a query directly on a specified
|
|
419
|
+
* shard. This is useful for administrative operations, cross-shard queries,
|
|
420
|
+
* or when you need to query data that doesn't follow the primary key routing pattern.
|
|
421
|
+
*
|
|
422
|
+
* **Use with caution**: This function bypasses routing safeguards and should
|
|
423
|
+
* be used only when you specifically need to target a particular shard.
|
|
424
|
+
*
|
|
425
|
+
* @param shardBinding - The shard binding name to execute the query on
|
|
426
|
+
* @param sql - SQL statement to execute
|
|
427
|
+
* @param bindings - Parameter values to bind to the SQL statement
|
|
428
|
+
* @returns Promise resolving to structured query results
|
|
429
|
+
* @throws {Error} If shard not found or query fails
|
|
430
|
+
* @example
|
|
431
|
+
* ```typescript
|
|
432
|
+
* // Administrative query: count all users across a specific shard
|
|
433
|
+
* const eastCoastStats = await allShard('db-east',
|
|
434
|
+
* 'SELECT COUNT(*) as user_count FROM users'
|
|
435
|
+
* );
|
|
436
|
+
* console.log(`East coast users: ${eastCoastStats.results[0].user_count}`);
|
|
437
|
+
*
|
|
438
|
+
* // Cross-shard analytics: get recent posts from a specific region
|
|
439
|
+
* const recentPosts = await allShard('db-west',
|
|
440
|
+
* 'SELECT id, title, created_at FROM posts WHERE created_at > ? ORDER BY created_at DESC LIMIT ?',
|
|
441
|
+
* [Date.now() - 86400000, 10] // Last 24 hours, limit 10
|
|
442
|
+
* );
|
|
443
|
+
*
|
|
444
|
+
* // Schema inspection on specific shard
|
|
445
|
+
* const tables = await allShard('db-central',
|
|
446
|
+
* "SELECT name FROM sqlite_master WHERE type='table'"
|
|
447
|
+
* );
|
|
448
|
+
* ```
|
|
449
|
+
*/
|
|
450
|
+
export declare function allShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings?: any[]): Promise<D1Result<T>>;
|
|
451
|
+
/**
|
|
452
|
+
* Bypasses the normal routing logic to execute a query directly on a specified
|
|
453
|
+
* shard. This is useful for administrative operations, cross-shard queries,
|
|
454
|
+
* or when you need to query data that doesn't follow the primary key routing pattern.
|
|
455
|
+
*
|
|
456
|
+
* **Use with caution**: This function bypasses routing safeguards and should
|
|
457
|
+
* be used only when you specifically need to target a particular shard.
|
|
458
|
+
*
|
|
459
|
+
* @param shardBinding - The shard binding name to execute the query on
|
|
460
|
+
* @param sql - SQL statement to execute
|
|
461
|
+
* @param bindings - Parameter values to bind to the SQL statement
|
|
462
|
+
* @returns Promise resolving to the first matching record, or null if not found
|
|
463
|
+
* @throws {Error} If shard not found or query fails
|
|
464
|
+
* @example
|
|
465
|
+
* ```typescript
|
|
466
|
+
* // Administrative query: get a specific user from a shard
|
|
467
|
+
* const user = await firstShard('db-east',
|
|
468
|
+
* 'SELECT * FROM users WHERE id = ?',
|
|
469
|
+
* ['user-123']);
|
|
470
|
+
* if (user) {
|
|
471
|
+
* console.log(`Found user: ${user.name}`);
|
|
472
|
+
* } else {
|
|
473
|
+
* console.log('User not found in east shard');
|
|
474
|
+
* }
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
export declare function firstShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings?: any[]): Promise<T | null>;
|
|
478
|
+
/**
|
|
479
|
+
* Flushes all shard mappings (development only)
|
|
480
|
+
*
|
|
481
|
+
* Completely clears all primary key to shard mappings from both KV storage
|
|
482
|
+
* and the Durable Object coordinator. This operation resets the entire
|
|
483
|
+
* routing system to a clean state.
|
|
484
|
+
*
|
|
485
|
+
* **DANGER**: This operation is destructive and irreversible. After flushing,
|
|
486
|
+
* all existing primary keys will be treated as new and may be assigned to
|
|
487
|
+
* different shards than before, causing data routing issues.
|
|
488
|
+
*
|
|
489
|
+
* **Use only for**:
|
|
490
|
+
* - Development and testing environments
|
|
491
|
+
* - Complete system resets
|
|
492
|
+
* - Emergency recovery scenarios
|
|
493
|
+
*
|
|
494
|
+
* @returns Promise that resolves when all mappings are cleared
|
|
495
|
+
* @example
|
|
496
|
+
* ```typescript
|
|
497
|
+
* // Only use in development!
|
|
498
|
+
* if (process.env.NODE_ENV === 'development') {
|
|
499
|
+
* await flush();
|
|
500
|
+
* console.log('All shard mappings cleared for testing');
|
|
501
|
+
*
|
|
502
|
+
* // Now all keys will be reassigned on next access
|
|
503
|
+
* await run('user-123', 'INSERT INTO users (id, name) VALUES (?, ?)',
|
|
504
|
+
* ['user-123', 'Test User']);
|
|
505
|
+
* }
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
export declare function flush(): Promise<void>;
|
|
509
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TypeScript type definitions for CollegeDB
|
|
3
|
+
*
|
|
4
|
+
* This module contains all the TypeScript interfaces and types used throughout
|
|
5
|
+
* the CollegeDB library. These types provide compile-time safety and enable
|
|
6
|
+
* better developer experience with IDE autocompletion and error checking.
|
|
7
|
+
*
|
|
8
|
+
* The types are organized into several categories:
|
|
9
|
+
* - Environment and configuration types
|
|
10
|
+
* - Query result and metadata types
|
|
11
|
+
* - Shard management and statistics types
|
|
12
|
+
* - Strategy and coordination types
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import type { CollegeDBConfig, QueryResult, ShardStats } from './types.js';
|
|
17
|
+
*
|
|
18
|
+
* const config: CollegeDBConfig = {
|
|
19
|
+
* kv: env.KV,
|
|
20
|
+
* shards: { 'db-east': env.DB_EAST },
|
|
21
|
+
* strategy: 'hash'
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @author Gregory Mitchell
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
*/
|
|
28
|
+
import type { D1Database, DurableObjectNamespace, KVNamespace } from '@cloudflare/workers-types';
|
|
29
|
+
/**
|
|
30
|
+
* Sharding strategy options for CollegeDB
|
|
31
|
+
* - `round-robin`: Distributes keys evenly across available shards.
|
|
32
|
+
* - `random`: Selects a random shard for each key.
|
|
33
|
+
* - `hash`: Uses a hash function to determine the shard based on the primary key.
|
|
34
|
+
*/
|
|
35
|
+
export type ShardingStrategy = 'round-robin' | 'random' | 'hash';
|
|
36
|
+
/**
|
|
37
|
+
* Environment bindings for the Cloudflare Worker
|
|
38
|
+
*/
|
|
39
|
+
export interface Env {
|
|
40
|
+
/** Cloudflare KV namespace for storing primary key to shard mappings */
|
|
41
|
+
KV: KVNamespace;
|
|
42
|
+
/** Durable Object binding for shard coordination */
|
|
43
|
+
ShardCoordinator: DurableObjectNamespace;
|
|
44
|
+
/** D1 database bindings - dynamic based on wrangler.toml */
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Configuration for the collegedb sharded database
|
|
49
|
+
*/
|
|
50
|
+
export interface CollegeDBConfig {
|
|
51
|
+
/** KV namespace for storing mappings */
|
|
52
|
+
kv: KVNamespace;
|
|
53
|
+
/** Shard coordinator Durable Object */
|
|
54
|
+
coordinator?: DurableObjectNamespace;
|
|
55
|
+
/** Available D1 database bindings */
|
|
56
|
+
shards: Record<string, D1Database>;
|
|
57
|
+
/** Default shard allocation strategy */
|
|
58
|
+
strategy?: ShardingStrategy;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Shard statistics for monitoring and load balancing
|
|
62
|
+
*/
|
|
63
|
+
export interface ShardStats {
|
|
64
|
+
/** D1 binding name */
|
|
65
|
+
binding: string;
|
|
66
|
+
/** Number of primary keys assigned to this shard */
|
|
67
|
+
count: number;
|
|
68
|
+
/** Last updated timestamp */
|
|
69
|
+
lastUpdated?: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Shard allocation strategy interface
|
|
73
|
+
*/
|
|
74
|
+
export interface ShardStrategy {
|
|
75
|
+
/** Select a shard for a new primary key */
|
|
76
|
+
selectShard(primaryKey: string, availableShards: string[]): string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Primary key to shard mapping stored in KV
|
|
80
|
+
*/
|
|
81
|
+
export interface ShardMapping {
|
|
82
|
+
/** D1 binding name */
|
|
83
|
+
shard: string;
|
|
84
|
+
/** Timestamp when mapping was created */
|
|
85
|
+
createdAt: number;
|
|
86
|
+
/** Timestamp when mapping was last updated */
|
|
87
|
+
updatedAt: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Durable Object state for shard coordination
|
|
91
|
+
*/
|
|
92
|
+
export interface ShardCoordinatorState {
|
|
93
|
+
/** List of known D1 bindings */
|
|
94
|
+
knownShards: string[];
|
|
95
|
+
/** Statistics for each shard */
|
|
96
|
+
shardStats: Record<string, ShardStats>;
|
|
97
|
+
/**
|
|
98
|
+
* Current allocation strategy
|
|
99
|
+
* `round-robin` - distributes keys evenly across shards
|
|
100
|
+
* `random` - selects a random shard for each key
|
|
101
|
+
* `hash` - uses a hash function to determine shard based on primary key (default)
|
|
102
|
+
*/
|
|
103
|
+
strategy: ShardingStrategy;
|
|
104
|
+
/** Round-robin counter for allocation */
|
|
105
|
+
roundRobinIndex: number;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAEjG;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,GAAG;IACnB,wEAAwE;IACxE,EAAE,EAAE,WAAW,CAAC;IAChB,oDAAoD;IACpD,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,4DAA4D;IAC5D,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,wCAAwC;IACxC,EAAE,EAAE,WAAW,CAAC;IAChB,uCAAuC;IACvC,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,2CAA2C;IAC3C,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;CACnE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,gCAAgC;IAChC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,yCAAyC;IACzC,eAAe,EAAE,MAAM,CAAC;CACxB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@earth-app/collegedb",
|
|
3
|
+
"module": "dist/index.js",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"version": "1.0.1",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**/*",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "bun build src/index.ts --outdir=dist --target=node --format=esm --minify --sourcemap=linked && bunx tsc --project tsconfig.build.json",
|
|
18
|
+
"docs:build": "bunx typedoc src --out ./typedoc",
|
|
19
|
+
"prettier": "bunx prettier --write .",
|
|
20
|
+
"prettier:check": "bunx prettier --check .",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"test:coverage": "vitest run --coverage",
|
|
24
|
+
"prepare": "husky install"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@cloudflare/workers-types": "^4.20250726.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@babel/cli": "^7.28.0",
|
|
31
|
+
"@babel/core": "^7.28.0",
|
|
32
|
+
"@babel/preset-env": "^7.28.0",
|
|
33
|
+
"@babel/preset-typescript": "^7.27.1",
|
|
34
|
+
"@types/bun": "latest",
|
|
35
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
36
|
+
"husky": "^9.1.7",
|
|
37
|
+
"jsdoc-babel": "^0.5.0",
|
|
38
|
+
"lint-staged": "^16.1.2",
|
|
39
|
+
"prettier": "^3.6.2",
|
|
40
|
+
"prettier-plugin-organize-imports": "^4.2.0",
|
|
41
|
+
"typedoc": "^0.28.8",
|
|
42
|
+
"vitest": "^3.2.4"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"typescript": "^5"
|
|
46
|
+
},
|
|
47
|
+
"lint-staged": {
|
|
48
|
+
"*.{js,ts,css,md}": "prettier --write"
|
|
49
|
+
}
|
|
50
|
+
}
|