@earth-app/collegedb 1.0.2 → 1.0.4

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 CHANGED
@@ -189,6 +189,96 @@ This approach provides:
189
189
  - **Optimal read performance**: Queries use `hash` strategy for consistent, high-performance routing
190
190
  - **Flexibility**: Each operation type can use the most appropriate routing strategy
191
191
 
192
+ ## Multi-Key Shard Mappings
193
+
194
+ CollegeDB supports **multiple lookup keys** for the same record, allowing you to query by username, email, ID, or any unique identifier. Keys are automatically hashed with SHA-256 for security and privacy.
195
+
196
+ ```typescript
197
+ import { collegedb, first, run, KVShardMapper } from 'collegedb';
198
+
199
+ collegedb(
200
+ {
201
+ kv: env.KV,
202
+ shards: { 'db-east': env.DB_EAST, 'db-west': env.DB_WEST },
203
+ hashShardMappings: true, // Default: enabled for security
204
+ strategy: 'hash'
205
+ },
206
+ async () => {
207
+ // Create a user with multiple lookup keys
208
+ const mapper = new KVShardMapper(env.KV, { hashShardMappings: true });
209
+
210
+ await mapper.setShardMapping('user-123', 'db-east', ['username:john_doe', 'email:john@example.com', 'id:123']);
211
+
212
+ // Now you can query by ANY of these keys
213
+ const byId = await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);
214
+ const byUsername = await first('username:john_doe', 'SELECT * FROM users WHERE username = ?', ['john_doe']);
215
+ const byEmail = await first('email:john@example.com', 'SELECT * FROM users WHERE email = ?', ['john@example.com']);
216
+
217
+ // All queries route to the same shard (db-east)
218
+ console.log('All queries find the same user:', byId?.name);
219
+ }
220
+ );
221
+ ```
222
+
223
+ ### Adding Lookup Keys to Existing Mappings
224
+
225
+ s
226
+
227
+ ```typescript
228
+ const mapper = new KVShardMapper(env.KV);
229
+
230
+ // User initially created with just ID
231
+ await mapper.setShardMapping('user-456', 'db-west');
232
+
233
+ // Later, add additional lookup methods
234
+ await mapper.addLookupKeys('user-456', ['email:jane@example.com', 'username:jane']);
235
+
236
+ // Now works with any key
237
+ const user = await first('email:jane@example.com', 'SELECT * FROM users WHERE email = ?', ['jane@example.com']);
238
+ ```
239
+
240
+ ### Security and Privacy
241
+
242
+ **SHA-256 Hashing (Enabled by Default)**: Sensitive data like emails are hashed before being stored as KV keys, protecting user privacy:
243
+
244
+ ```typescript
245
+ // With hashShardMappings: true (default)
246
+ // KV stores: "shard:a1b2c3d4..." instead of "shard:email:user@example.com"
247
+
248
+ const config = {
249
+ kv: env.KV,
250
+ shards: {
251
+ /* ... */
252
+ },
253
+ hashShardMappings: true, // Hashes keys with SHA-256
254
+ strategy: 'hash'
255
+ };
256
+ ```
257
+
258
+ **⚠️ Performance Trade-off**: When hashing is enabled, operations like `getKeysForShard()` cannot return original key names, only hashed versions. For full key recovery, disable hashing:
259
+
260
+ ```typescript
261
+ const config = {
262
+ hashShardMappings: false // Disables hashing - keys stored in plain text
263
+ };
264
+ ```
265
+
266
+ ### Multi-Key Management
267
+
268
+ ```typescript
269
+ const mapper = new KVShardMapper(env.KV);
270
+
271
+ // Get all lookup keys for a mapping
272
+ const allKeys = await mapper.getAllLookupKeys('email:user@example.com');
273
+ console.log(allKeys); // ['user-123', 'username:john', 'email:user@example.com']
274
+
275
+ // Update shard assignment (updates all keys)
276
+ await mapper.updateShardMapping('username:john', 'db-central');
277
+
278
+ // Delete mapping (removes all associated keys)
279
+ await mapper.deleteShardMapping('user-123');
280
+ ```
281
+
192
282
  ## Drop-in Replacement for Existing Databases
193
283
 
194
284
  CollegeDB supports **seamless, automatic integration** with existing D1 databases that already contain data. Simply add your existing databases as shards in the configuration. CollegeDB will automatically detect existing data and create the necessary shard mappings **without requiring any manual migration steps**.
@@ -441,35 +531,38 @@ for (const [table, pkColumn] of Object.entries(customIntegration)) {
441
531
 
442
532
  ## 📚 API Reference
443
533
 
444
- | Function | Description | Parameters |
445
- | ---------------------------------- | ---------------------------------------------------- | ------------------------ |
446
- | `collegedb(config, callback)` | Initialize CollegeDB, then run a callback | `CollegeDBConfig, ()=>T` |
447
- | `initialize(config)` | Initialize CollegeDB with configuration | `CollegeDBConfig` |
448
- | `createSchema(d1)` | Create database schema on a D1 instance | `D1Database` |
449
- | `prepare(key, sql)` | Prepare a SQL statement for execution | `string, string` |
450
- | `run(key, sql, bindings)` | Execute a SQL query with primary key routing | `string, string, any[]` |
451
- | `first(key, sql, bindings)` | Execute a SQL query and return first result | `string, string, any[]` |
452
- | `all(key, sql, bindings)` | Execute a SQL query and return all results | `string, string, any[]` |
453
- | `runShard(shard, sql, bindings)` | Execute a SQL query directly on a specific shard | `string, string, any[]` |
454
- | `allShard(shard, sql, bindings)` | Execute query on specific shard, return all results | `string, string, any[]` |
455
- | `firstShard(shard, sql, bindings)` | Execute query on specific shard, return first result | `string, string, any[]` |
456
- | `reassignShard(key, newShard)` | Move primary key to different shard | `string, string` |
457
- | `listKnownShards()` | Get list of available shards | `void` |
458
- | `getShardStats()` | Get statistics for all shards | `void` |
459
- | `flush()` | Clear all shard mappings (development only) | `void` |
534
+ | Function | Description | Parameters |
535
+ | ------------------------------------------ | ---------------------------------------------------------------- | -------------------------- |
536
+ | `collegedb(config, callback)` | Initialize CollegeDB, then run a callback | `CollegeDBConfig, () => T` |
537
+ | `initialize(config)` | Initialize CollegeDB with configuration | `CollegeDBConfig` |
538
+ | `createSchema(d1)` | Create database schema on a D1 instance | `D1Database` |
539
+ | `prepare(key, sql)` | Prepare a SQL statement for execution | `string, string` |
540
+ | `run(key, sql, bindings)` | Execute a SQL query with primary key routing | `string, string, any[]` |
541
+ | `first(key, sql, bindings)` | Execute a SQL query and return first result | `string, string, any[]` |
542
+ | `all(key, sql, bindings)` | Execute a SQL query and return all results | `string, string, any[]` |
543
+ | `runShard(shard, sql, bindings)` | Execute a query directly on a specific shard | `string, string, any[]` |
544
+ | `allShard(shard, sql, bindings)` | Execute a query on specific shard, return all results | `string, string, any[]` |
545
+ | `firstShard(shard, sql, bindings)` | Execute a query on specific shard, return first result | `string, string, any[]` |
546
+ | `runAllShards(sql, bindings, batchSize)` | Execute query on all shards | `string, any[], number` |
547
+ | `allAllShards(sql, bindings, batchSize)` | Execute query on all shards, return all results from all shards | `string, any[], number` |
548
+ | `firstAllShards(sql, bindings, batchSize)` | Execute query on all shards, return first result from all shards | `string, any[], number` |
549
+ | `reassignShard(key, newShard)` | Move primary key to different shard | `string, string` |
550
+ | `listKnownShards()` | Get list of available shards | `void` |
551
+ | `getShardStats()` | Get statistics for all shards | `void` |
552
+ | `flush()` | Clear all shard mappings (development only) | `void` |
460
553
 
461
554
  ### Drop-in Replacement Functions
462
555
 
463
- | Function | Description | Parameters |
464
- | ----------------------------------------- | ------------------------------------------------------- | ------------------------------ |
465
- | `autoDetectAndMigrate(d1, shard, config)` | **NEW**: Automatically detect and migrate existing data | `D1Database, string, config` |
466
- | `checkMigrationNeeded(d1, shard, config)` | **NEW**: Check if database needs migration | `D1Database, string, config` |
467
- | `validateTableForSharding(d1, table)` | Check if table is suitable for sharding | `D1Database, string` |
468
- | `discoverExistingPrimaryKeys(d1, table)` | Find all primary keys in existing table | `D1Database, string` |
469
- | `integrateExistingDatabase(d1, shard)` | Complete drop-in integration of existing DB | `D1Database, string, mapper` |
470
- | `createMappingsForExistingKeys(keys)` | Create shard mappings for existing keys | `string[], string[], strategy` |
471
- | `listTables(d1)` | Get list of tables in database | `D1Database` |
472
- | `clearMigrationCache()` | Clear automatic migration cache | `void` |
556
+ | Function | Description | Parameters |
557
+ | ----------------------------------------- | ---------------------------------------------- | ------------------------------ |
558
+ | `autoDetectAndMigrate(d1, shard, config)` | Automatically detect and migrate existing data | `D1Database, string, config` |
559
+ | `checkMigrationNeeded(d1, shard, config)` | Check if database needs migration | `D1Database, string, config` |
560
+ | `validateTableForSharding(d1, table)` | Check if table is suitable for sharding | `D1Database, string` |
561
+ | `discoverExistingPrimaryKeys(d1, table)` | Find all primary keys in existing table | `D1Database, string` |
562
+ | `integrateExistingDatabase(d1, shard)` | Complete drop-in integration of existing DB | `D1Database, string, mapper` |
563
+ | `createMappingsForExistingKeys(keys)` | Create shard mappings for existing keys | `string[], string[], strategy` |
564
+ | `listTables(d1)` | Get list of tables in database | `D1Database` |
565
+ | `clearMigrationCache()` | Clear automatic migration cache | `void` |
473
566
 
474
567
  ### Error Handling
475
568
 
@@ -557,10 +650,13 @@ interface CollegeDBConfig {
557
650
  strategy?: ShardingStrategy | MixedShardingStrategy;
558
651
  targetRegion?: D1Region;
559
652
  shardLocations?: Record<string, ShardLocation>;
560
- disableAutoMigration?: boolean;
653
+ disableAutoMigration?: boolean; // Default: false
654
+ hashShardMappings?: boolean; // Default: true
561
655
  }
562
656
  ```
563
657
 
658
+ When `hashShardMappings` is enabled (default), original keys cannot be recovered during shard operations like `getKeysForShard()`. This is intentional for privacy but means you'll get fewer results from such operations. For full key recovery, set `hashShardMappings: false`, but be aware this may expose sensitive data in KV keys.
659
+
564
660
  #### Strategy Types
565
661
 
566
662
  ```typescript
@@ -1176,7 +1272,7 @@ _SELECT, VALUES, TABLE, PRAGMA, ..._
1176
1272
  | CollegeDB (100 shards) | ~60-90ms | 100x parallel capacity | ~75-80x |
1177
1273
  | CollegeDB (1000 shards) | ~65-95ms | 1000x parallel capacity | ~650-700x |
1178
1274
 
1179
- \*Includes KV lookup overhead (~5-15ms)
1275
+ \*Includes KV lookup overhead (~5-15ms) and SHA-256 hashing overhead (~1-3ms when `hashShardMappings: true`)
1180
1276
 
1181
1277
  #### Write Performance
1182
1278
 
@@ -1189,14 +1285,14 @@ _INSERT, UPDATE, DELETE, ..._
1189
1285
  | CollegeDB (100 shards) | ~95-145ms | ~4,200 writes/sec | ~84x |
1190
1286
  | CollegeDB (1000 shards) | ~105-160ms | ~35,000 writes/sec | ~700x |
1191
1287
 
1192
- \*Includes KV mapping creation/update overhead (~10-25ms)
1288
+ \*Includes KV mapping creation/update overhead (~10-25ms) and SHA-256 hashing overhead (~1-3ms when `hashShardMappings: true`)
1193
1289
 
1194
1290
  ### Strategy-Specific Performance
1195
1291
 
1196
1292
  #### Hash Strategy
1197
1293
 
1198
1294
  - **Best for**: Consistent performance, even data distribution
1199
- - **Latency**: Lowest overhead (no coordinator calls)
1295
+ - **Latency**: Lowest overhead (no coordinator calls, ~1-3ms SHA-256 hashing when enabled)
1200
1296
  - **Throughput**: Optimal for high-volume scenarios
1201
1297
 
1202
1298
  | Shards | Avg Latency | Distribution Quality | Coordinator Dependency |
@@ -1323,7 +1419,7 @@ _INSERT, UPDATE, DELETE, ..._
1323
1419
  // Recommended: Hash reads + Location writes
1324
1420
  {
1325
1421
  strategy: { read: 'hash', write: 'location' },
1326
- targetRegion: 'auto', // Or specific region like 'wnam'
1422
+ targetRegion: getClosestRegionFromIP(request), // Dynamic region targeting
1327
1423
  shardLocations: {
1328
1424
  'db-americas': { region: 'wnam', priority: 2 },
1329
1425
  'db-europe': { region: 'weur', priority: 2 },
@@ -1430,6 +1526,58 @@ _INSERT, UPDATE, DELETE, ..._
1430
1526
  | **Balanced** | 50/50 | `{read: 'hash', write: 'hash'}` | Consistent performance |
1431
1527
  | **Analytics** | 95% reads | `{read: 'location', write: 'round-robin'}` | Regional + perfect distribution |
1432
1528
 
1529
+ ### SHA-256 Hashing Performance Impact
1530
+
1531
+ CollegeDB uses SHA-256 hashing by default (`hashShardMappings: true`) to protect sensitive data in KV keys. This adds a small but measurable performance overhead:
1532
+
1533
+ #### Hashing Performance Characteristics
1534
+
1535
+ | Operation Type | SHA-256 Overhead | Total Latency Impact | Security Benefit |
1536
+ | ------------------ | ---------------- | -------------------- | ---------------------------- |
1537
+ | **Query (Read)** | ~1-2ms | 2-4% increase | Keys hashed in KV storage |
1538
+ | **Insert (Write)** | ~2-3ms | 2-3% increase | Multi-key mappings protected |
1539
+ | **Update Mapping** | ~1-3ms | 1-2% increase | Existing keys remain secure |
1540
+
1541
+ #### Performance by Key Length
1542
+
1543
+ | Key Type | Example | Hash Time | Recommendation |
1544
+ | ------------------------ | ------------------------------ | ------------ | ----------------------- |
1545
+ | **Short keys** | `user-123` | ~0.5-1ms | Minimal impact |
1546
+ | **Medium keys** | `email:user@example.com` | ~1-2ms | Good balance |
1547
+ | **Long keys** | `session:very-long-token-here` | ~2-3ms | Consider key shortening |
1548
+ | **Multi-key operations** | 3+ lookup keys | ~3-5ms total | Benefits outweigh cost |
1549
+
1550
+ #### Hashing vs No-Hashing Trade-offs
1551
+
1552
+ ```typescript
1553
+ // With hashing (default - recommended for production)
1554
+ const secureConfig = {
1555
+ hashShardMappings: true // Default
1556
+ // + Privacy: Sensitive data not visible in KV
1557
+ // + Security: Keys cannot be enumerated
1558
+ // - Performance: +1-3ms per operation
1559
+ // - Debugging: Original keys not recoverable
1560
+ };
1561
+
1562
+ // Without hashing (development/debugging only)
1563
+ const developmentConfig = {
1564
+ hashShardMappings: false
1565
+ // + Performance: No hashing overhead
1566
+ // + Debugging: Original keys visible in KV
1567
+ // - Privacy: Sensitive data exposed in KV keys
1568
+ // - Security: Keys can be enumerated
1569
+ };
1570
+ ```
1571
+
1572
+ #### Optimization Recommendations
1573
+
1574
+ 1. **Keep keys reasonably short** - Hash time scales with key length
1575
+ 2. **Use hashing in production** - Security benefits outweigh minimal performance cost
1576
+ 3. **Disable hashing for development** - When debugging shard distribution
1577
+ 4. **Monitor hash performance** - Track operation latencies in high-volume scenarios
1578
+
1579
+ **Bottom Line**: SHA-256 hashing adds 1-3ms overhead but provides essential privacy and security benefits. The performance impact is minimal compared to network latency and D1 query time.
1580
+
1433
1581
  ### Real-World Scaling Benefits
1434
1582
 
1435
1583
  #### Database Size Limits
@@ -1619,7 +1767,7 @@ const stats = await statsResponse.json();
1619
1767
  "lastUpdated": 1672531200000
1620
1768
  },
1621
1769
  {
1622
- "binding": "db-west",
1770
+ "binding": "db-west",
1623
1771
  "count": 1458,
1624
1772
  "lastUpdated": 1672531205000
1625
1773
  }
@@ -1742,7 +1890,9 @@ async function monitorShardHealth(env: Env) {
1742
1890
  }
1743
1891
  ```
1744
1892
 
1745
- #### Error Handling
1893
+ #### Error Handling with ShardCoordinator
1894
+
1895
+ When using the ShardCoordinator, ensure you handle potential errors gracefully:
1746
1896
 
1747
1897
  ```typescript
1748
1898
  try {
package/dist/index.js CHANGED
@@ -1,19 +1,19 @@
1
- var zJ=Object.defineProperty;var h=(J,Q)=>{for(var U in Q)zJ(J,U,{get:Q[U],enumerable:!0,configurable:!0,set:(Z)=>Q[U]=()=>Z})};var b=(J,Q)=>()=>(J&&(Q=J(J=0)),Q);var G;var D=b(()=>{G=class G extends Error{code;constructor(J,Q){super(J);if(this.name="CollegeDBError",this.code=Q,Error.captureStackTrace)Error.captureStackTrace(this,G)}}});var g={};h(g,{KVShardMapper:()=>A});class A{kv;constructor(J){this.kv=J}async getShardMapping(J){let Q=`${_}${J}`;return await this.kv.get(Q,"json")}async setShardMapping(J,Q){let U=`${_}${J}`,Z={shard:Q,createdAt:Date.now(),updatedAt:Date.now()};await this.kv.put(U,JSON.stringify(Z))}async updateShardMapping(J,Q){let U=await this.getShardMapping(J);if(!U)throw new G(`No existing mapping found for primary key: ${J}`,"MAPPING_NOT_FOUND");let Z=`${_}${J}`,$={...U,shard:Q,updatedAt:Date.now()};await this.kv.put(Z,JSON.stringify($))}async deleteShardMapping(J){let Q=`${_}${J}`;await this.kv.delete(Q)}async getKnownShards(){return await this.kv.get(l,"json")||[]}async setKnownShards(J){if(!J||J.length===0)return;await this.kv.put(l,JSON.stringify(J))}async addKnownShard(J){if(!J)return;let Q=await this.getKnownShards();if(!Q.includes(J))Q.push(J),await this.setKnownShards(Q)}async getKeysForShard(J){let Q=[],U=await this.kv.list({prefix:_});for(let Z of U.keys)if((await this.getShardMapping(Z.name.replace(_,"")))?.shard===J)Q.push(Z.name.replace(_,""));return Q}async getShardKeyCounts(){let J={},Q=await this.kv.list({prefix:_});for(let U of Q.keys){let Z=await this.getShardMapping(U.name.replace(_,""));if(Z)J[Z.shard]=(J[Z.shard]||0)+1}return J}async clearAllMappings(){let Q=(await this.kv.list({prefix:_})).keys.map((U)=>this.kv.delete(U.name));await Promise.all(Q)}}var _="shard:",l="known_shards";var v=b(()=>{D()});var k={};h(k,{validateTableForSharding:()=>N,schemaExists:()=>c,migrateRecord:()=>o,listTables:()=>P,integrateExistingDatabase:()=>t,dropSchema:()=>n,discoverExistingPrimaryKeys:()=>S,createSchemaAcrossShards:()=>d,createSchema:()=>p,createMappingsForExistingKeys:()=>i,clearShardMigrationCache:()=>e,clearMigrationCache:()=>a,checkMigrationNeeded:()=>r,autoDetectAndMigrate:()=>s});async function p(J,Q){let U=Q.split(";").map((Z)=>Z.trim()).filter((Z)=>Z.length>0&&!Z.startsWith("--"));for(let Z of U)try{await J.prepare(Z).run()}catch($){throw console.error("Failed to execute schema statement:",Z,$),new G(`Schema migration failed: ${$}`,"SCHEMA_MIGRATION_FAILED")}}async function d(J,Q){let U=Object.entries(J).map(([Z,$])=>{return p($,Q).catch((z)=>{throw new G(`Failed to create schema on shard ${Z}: ${z.message}`,"SCHEMA_CREATION_FAILED")})});await Promise.all(U)}async function c(J,Q){try{return await J.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='?'").bind(Q).first()!==null}catch{return!1}}async function n(J,...Q){for(let U of Q)try{await J.prepare(`DROP TABLE IF EXISTS ${U}`).run()}catch(Z){console.error(`Failed to drop table ${U}:`,Z)}}async function P(J){try{return(await J.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all()).results.map((U)=>U.name)}catch{return[]}}async function o(J,Q,U,Z){let $=await J.prepare(`SELECT * FROM ${Z} WHERE id = ?`).bind(U).first();if(!$)throw new G(`Record with primary key ${U} not found in source database`,"RECORD_NOT_FOUND");if(!await c(Q,Z))await p(Q,Z);let z=Object.keys($),Y=z.map(()=>"?").join(", "),V=z.map((O)=>$[O]),W=`INSERT OR REPLACE INTO ${Z} (${z.join(", ")}) VALUES (${Y})`;await Q.prepare(W).bind(...V).run(),await J.prepare(`DELETE FROM ${Z} WHERE id = ?`).bind(U).run()}async function S(J,Q,U="id"){try{return(await J.prepare(`SELECT ${U} FROM ${Q}`).all()).results.map(($)=>String($[U]))}catch(Z){throw new G(`Failed to discover primary keys in table ${Q}: ${Z}`,"DISCOVERY_FAILED")}}async function i(J,Q,U,Z){let $=Q.length;for(let z=0;z<J.length;z++){let Y=J[z],V;switch(U){case"hash":let W=0;for(let L=0;L<Y.length;L++){let X=Y.charCodeAt(L);W=(W<<5)-W+X,W=W&W}let O=Math.abs(W)%$;V=Q[O];break;case"random":V=Q[Math.floor(Math.random()*$)];break;default:V=Q[z%$];break}await Z.setShardMapping(Y,V)}}async function N(J,Q,U){let Z=[],$=0;try{if(!await J.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").bind(Q).first())return Z.push(`Table '${Q}' does not exist`),{isValid:!1,tableName:Q,primaryKeyColumn:U,recordCount:0,issues:Z};if(!(await J.prepare(`PRAGMA table_info(${Q})`).all()).results.some((O)=>O.name===U&&O.pk===1))Z.push(`Primary key column '${U}' not found or not set as primary key`);if($=(await J.prepare(`SELECT COUNT(*) as count FROM ${Q}`).first())?.count||0,$===0)Z.push(`Table '${Q}' is empty`)}catch(z){Z.push(`Database validation error: ${z}`)}return{isValid:Z.length===0,tableName:Q,primaryKeyColumn:U,recordCount:$,issues:Z}}async function t(J,Q,U,Z={}){let{tables:$,primaryKeyColumn:z="id",strategy:Y="hash",addShardMappingsTable:V=!0,dryRun:W=!1}=Z,O=[],L=0,X=0,x=0;try{let T=($||await P(J)).filter((j)=>j!=="shard_mappings");for(let j of T)try{let w=await N(J,j,z);if(!w.isValid){O.push(`Table ${j}: ${w.issues.join(", ")}`);continue}let M=await S(J,j,z);if(M.length===0){O.push(`Table ${j} has no records to process`);continue}if(!W)for(let I of M)await U.setShardMapping(I,Q),x++;L++,X+=M.length}catch(w){O.push(`Failed to process table ${j}: ${w}`)}if(V&&!W){if(!(await P(J)).includes("shard_mappings"))await J.prepare(`
1
+ var jJ=Object.defineProperty;var i=(J,Z)=>{for(var $ in Z)jJ(J,$,{get:Z[$],enumerable:!0,configurable:!0,set:(V)=>Z[$]=()=>V})};var m=(J,Z)=>()=>(J&&(Z=J(J=0)),Z);var G;var k=m(()=>{G=class G extends Error{code;constructor(J,Z){super(J);if(this.name="CollegeDBError",this.code=Z,Error.captureStackTrace)Error.captureStackTrace(this,G)}}});var y={};i(y,{KVShardMapper:()=>E});class E{kv;hashKeys;constructor(J,Z={}){this.kv=J,this.hashKeys=Z.hashShardMappings??!0}async hashKey(J){if(!this.hashKeys)return J;let $=new TextEncoder().encode(J),V=await crypto.subtle.digest("SHA-256",$),Q=new Uint8Array(V);return Array.from(Q).map((O)=>O.toString(16).padStart(2,"0")).join("")}async getShardMapping(J){let Z=await this.hashKey(J),$=`${w}${Z}`,V=await this.kv.get($,"json");if(V)return V;let Q=await this.kv.get(`${B}${Z}`,"json");if(Q)return{shard:Q.shard,createdAt:Q.createdAt,updatedAt:Q.updatedAt,originalKey:this.hashKeys?void 0:J};return null}async setShardMapping(J,Z,$=[]){let V=[J,...$],Q=Date.now();if(V.length===1){let W=await this.hashKey(J),O=`${w}${W}`,z={shard:Z,createdAt:Q,updatedAt:Q,originalKey:this.hashKeys?void 0:J};await this.kv.put(O,JSON.stringify(z))}else{let W=await this.hashKey(J),O=`${B}${W}`,z={shard:Z,createdAt:Q,updatedAt:Q,keys:this.hashKeys?[]:V};await this.kv.put(O,JSON.stringify(z));let U=V.map(async(j)=>{let Y=await this.hashKey(j),x=`${w}${Y}`,F={shard:Z,createdAt:Q,updatedAt:Q,originalKey:this.hashKeys?void 0:j};return this.kv.put(x,JSON.stringify(F))});await Promise.all(U)}}async updateShardMapping(J,Z){let $=await this.getShardMapping(J);if(!$)throw new G(`No existing mapping found for primary key: ${J}`,"MAPPING_NOT_FOUND");let V=await this.hashKey(J),Q=`${w}${V}`,W=`${B}${V}`,O=await this.kv.get(W,"json");if(O){let z={...O,shard:Z,updatedAt:Date.now()};await this.kv.put(W,JSON.stringify(z));let U=O.keys.map(async(j)=>{let Y=await this.hashKey(j),x=`${w}${Y}`,F={...$,shard:Z,updatedAt:Date.now()};return this.kv.put(x,JSON.stringify(F))});await Promise.all(U)}else{let z={...$,shard:Z,updatedAt:Date.now()};await this.kv.put(Q,JSON.stringify(z))}}async deleteShardMapping(J){let Z=await this.hashKey(J),$=`${w}${Z}`,V=`${B}${Z}`,Q=await this.kv.get(V,"json");if(Q){await this.kv.delete(V);let W=Q.keys.map(async(O)=>{let z=await this.hashKey(O),U=`${w}${z}`;return this.kv.delete(U)});await Promise.all(W)}else await this.kv.delete($)}async getKnownShards(){return await this.kv.get(s,"json")||[]}async setKnownShards(J){if(!J||J.length===0)return;await this.kv.put(s,JSON.stringify(J))}async addKnownShard(J){if(!J)return;let Z=await this.getKnownShards();if(!Z.includes(J))Z.push(J),await this.setKnownShards(Z)}async getKeysForShard(J){let Z=[],$=await this.kv.list({prefix:w});for(let Q of $.keys){let W=await this.kv.get(Q.name,"json");if(W?.shard===J){let O=Q.name.replace(w,"");if(W.originalKey)Z.push(W.originalKey);else if(!this.hashKeys)Z.push(O)}}let V=await this.kv.list({prefix:B});for(let Q of V.keys){let W=await this.kv.get(Q.name,"json");if(W?.shard===J)Z.push(...W.keys)}return[...new Set(Z)]}async getShardKeyCounts(){let J={},Z=await this.kv.list({prefix:w});for(let V of Z.keys){let Q=await this.kv.get(V.name,"json");if(Q)J[Q.shard]=(J[Q.shard]||0)+1}let $=await this.kv.list({prefix:B});for(let V of $.keys){let Q=await this.kv.get(V.name,"json");if(Q)J[Q.shard]=(J[Q.shard]||0)+Q.keys.length}return J}async clearAllMappings(){let Z=(await this.kv.list({prefix:w})).keys.map((Q)=>this.kv.delete(Q.name)),V=(await this.kv.list({prefix:B})).keys.map((Q)=>this.kv.delete(Q.name));await Promise.all([...Z,...V])}async addLookupKeys(J,Z){let $=await this.getShardMapping(J);if(!$)throw new G(`No existing mapping found for primary key: ${J}`,"MAPPING_NOT_FOUND");let V=await this.hashKey(J),Q=`${B}${V}`,W=await this.kv.get(Q,"json"),O=[J,...Z],z=Date.now();if(!W)W={shard:$.shard,createdAt:$.createdAt,updatedAt:z,keys:this.hashKeys?[]:O};else W={...W,updatedAt:z,keys:this.hashKeys?[]:[...new Set([...W.keys,...O])]};await this.kv.put(Q,JSON.stringify(W));let U=Z.map(async(j)=>{let Y=await this.hashKey(j),x=`${w}${Y}`,F={shard:$.shard,createdAt:$.createdAt,updatedAt:z,originalKey:this.hashKeys?void 0:j};return this.kv.put(x,JSON.stringify(F))});await Promise.all(U)}async getAllLookupKeys(J){let Z=await this.hashKey(J),$=`${B}${Z}`,V=await this.kv.get($,"json");if(V)return V.keys;let Q=await this.getShardMapping(J);if(Q)return Q.originalKey?[Q.originalKey]:[J];throw new G(`No mapping found for key: ${J}`,"MAPPING_NOT_FOUND")}}var w="shard:",B="multikey:",s="known_shards";var f=m(()=>{k()});var b={};i(b,{validateTableForSharding:()=>g,schemaExists:()=>d,migrateRecord:()=>e,listTables:()=>C,integrateExistingDatabase:()=>ZJ,dropSchema:()=>a,discoverExistingRecordsWithColumns:()=>n,discoverExistingPrimaryKeys:()=>c,createSchemaAcrossShards:()=>r,createSchema:()=>l,createMappingsForExistingKeys:()=>JJ,clearShardMigrationCache:()=>WJ,clearMigrationCache:()=>VJ,checkMigrationNeeded:()=>QJ,autoDetectAndMigrate:()=>$J});async function l(J,Z){let $=Z.split(";").map((V)=>V.trim()).filter((V)=>V.length>0&&!V.startsWith("--"));for(let V of $)try{await J.prepare(V).run()}catch(Q){throw console.error("Failed to execute schema statement:",V,Q),new G(`Schema migration failed: ${Q}`,"SCHEMA_MIGRATION_FAILED")}}async function r(J,Z){let $=Object.entries(J).map(([V,Q])=>{return l(Q,Z).catch((W)=>{throw new G(`Failed to create schema on shard ${V}: ${W.message}`,"SCHEMA_CREATION_FAILED")})});await Promise.all($)}async function d(J,Z){try{return await J.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").bind(Z).first()!==null}catch{return!1}}async function a(J,...Z){for(let $ of Z)try{await J.prepare(`DROP TABLE IF EXISTS ${$}`).run()}catch(V){console.error(`Failed to drop table ${$}:`,V)}}async function C(J){try{return(await J.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name").all()).results.map(($)=>$.name)}catch{return[]}}async function e(J,Z,$,V){let Q=await J.prepare(`SELECT * FROM ${V} WHERE id = ?`).bind($).first();if(!Q)throw new G(`Record with primary key ${$} not found in source database`,"RECORD_NOT_FOUND");if(!await d(Z,V))await l(Z,V);let W=Object.keys(Q),O=W.map(()=>"?").join(", "),z=W.map((j)=>Q[j]),U=`INSERT OR REPLACE INTO ${V} (${W.join(", ")}) VALUES (${O})`;await Z.prepare(U).bind(...z).run(),await J.prepare(`DELETE FROM ${V} WHERE id = ?`).bind($).run()}async function c(J,Z,$="id"){try{return(await J.prepare(`SELECT ${$} FROM ${Z}`).all()).results.map((Q)=>String(Q[$]))}catch(V){throw new G(`Failed to discover primary keys in table ${Z}: ${V}`,"DISCOVERY_FAILED")}}async function n(J,Z,$="id"){try{let Q=(await J.prepare(`PRAGMA table_info(${Z})`).all()).results.map((U)=>U.name),W=[$];if(Q.includes("username"))W.push("username");if(Q.includes("email"))W.push("email");if(Q.includes("name"))W.push("name");let O=`SELECT ${W.join(", ")} FROM ${Z}`;return(await J.prepare(O).all()).results}catch(V){throw new G(`Failed to discover records with columns in table ${Z}: ${V}`,"DISCOVERY_FAILED")}}async function JJ(J,Z,$,V){let Q=Z.length;for(let W=0;W<J.length;W++){let O=J[W],z;switch($){case"hash":let U=0;for(let Y=0;Y<O.length;Y++){let x=O.charCodeAt(Y);U=(U<<5)-U+x,U=U&U}let j=Math.abs(U)%Q;z=Z[j];break;case"random":z=Z[Math.floor(Math.random()*Q)];break;default:z=Z[W%Q];break}await V.setShardMapping(O,z)}}async function g(J,Z,$){let V=[],Q=0;try{if(!await J.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").bind(Z).first())return V.push(`Table '${Z}' does not exist`),{isValid:!1,tableName:Z,primaryKeyColumn:$,recordCount:0,issues:V};if(!(await J.prepare(`PRAGMA table_info(${Z})`).all()).results.some((j)=>j.name===$&&j.pk===1))V.push(`Primary key column '${$}' not found or not set as primary key`);if(Q=(await J.prepare(`SELECT COUNT(*) as count FROM ${Z}`).first())?.count||0,Q===0)V.push(`Table '${Z}' is empty`)}catch(W){V.push(`Database validation error: ${W}`)}return{isValid:V.length===0,tableName:Z,primaryKeyColumn:$,recordCount:Q,issues:V}}async function ZJ(J,Z,$,V={}){let{tables:Q,primaryKeyColumn:W="id",strategy:O="hash",addShardMappingsTable:z=!0,dryRun:U=!1,migrateOtherColumns:j=!1}=V,Y=[],x=0,F=0,H=0;try{let S=(Q||await C(J)).filter((X)=>X!=="shard_mappings");for(let X of S)try{let I=await g(J,X,W);if(!I.isValid){Y.push(`Table ${X}: ${I.issues.join(", ")}`);continue}if(j){let D=await n(J,X,W);if(D.length===0){Y.push(`Table ${X} has no records to process`);continue}if(!U)for(let L of D){let R=String(L[W]),P=[];if(L.username&&typeof L.username==="string")P.push(`username:${L.username}`);if(L.email&&typeof L.email==="string")P.push(`email:${L.email}`);if(L.name&&typeof L.name==="string")P.push(`name:${L.name}`);await $.setShardMapping(R,Z,P),H++}F+=D.length}else{let D=await c(J,X,W);if(D.length===0){Y.push(`Table ${X} has no records to process`);continue}if(!U)for(let L of D)await $.setShardMapping(L,Z),H++;F+=D.length}x++}catch(I){Y.push(`Failed to process table ${X}: ${I}`)}if(z&&!U){if(!(await C(J)).includes("shard_mappings"))await J.prepare(`
2
2
  CREATE TABLE IF NOT EXISTS shard_mappings (
3
3
  primary_key TEXT PRIMARY KEY,
4
4
  shard_name TEXT NOT NULL,
5
5
  created_at INTEGER NOT NULL,
6
6
  updated_at INTEGER NOT NULL
7
- );`.trim()).run()}if(!W)await U.addKnownShard(Q)}catch(F){O.push(`Integration failed: ${F}`)}return{success:O.length===0||O.length>0&&L>0,shardName:Q,tablesProcessed:L,totalRecords:X,mappingsCreated:x,issues:O}}async function s(J,Q,U,Z={}){let{primaryKeyColumn:$="id",tablesToCheck:z,skipCache:Y=!1,maxRecordsToCheck:V=1000}=Z,W=`${Q}_migration_check`;if(!Y&&E.has(W))return{migrationNeeded:!1,migrationPerformed:!1,recordsMigrated:0,tablesProcessed:0,issues:[]};let O=[],L=0,X=0,x=!1,F=!1;try{let{KVShardMapper:T}=await Promise.resolve().then(() => (v(),g)),j=new T(U.kv),w=await P(J),M=z||w.filter((I)=>I!=="shard_mappings"&&!I.startsWith("sqlite_")&&I!=="sqlite_sequence");if(M.length===0)return E.set(W,!0),{migrationNeeded:!1,migrationPerformed:!1,recordsMigrated:0,tablesProcessed:0,issues:[]};for(let I of M)try{let R=await N(J,I,$);if(!R.isValid||R.recordCount===0)continue;let UJ=Math.min(V,R.recordCount),ZJ=await J.prepare(`
8
- SELECT ${$} FROM ${I}
9
- ORDER BY ${$}
10
- LIMIT ?`.trim()).bind(UJ).all(),m=0,$J=ZJ.results.slice(0,10);for(let f of $J){let q=String(f[$]);if(!await j.getShardMapping(q))m++,x=!0}if(m>0){console.log(`Auto-migrating table ${I} in shard ${Q} (${R.recordCount} records)`);let f=await S(J,I,$),q=0;for(let u of f)if(!await j.getShardMapping(u))await j.setShardMapping(u,Q),q++;L+=q,X++,F=!0,console.log(`Auto-migrated ${q} records from table ${I}`)}}catch(R){O.push(`Auto-migration failed for table ${I}: ${R}`)}if(F){if(await j.addKnownShard(Q),!w.includes("shard_mappings"))await J.prepare(`CREATE TABLE IF NOT EXISTS shard_mappings (
7
+ );`.trim()).run()}if(!U)await $.addKnownShard(Z)}catch(A){Y.push(`Integration failed: ${A}`)}return{success:Y.length===0||Y.length>0&&x>0,shardName:Z,tablesProcessed:x,totalRecords:F,mappingsCreated:H,issues:Y}}async function $J(J,Z,$,V={}){let{primaryKeyColumn:Q="id",tablesToCheck:W,skipCache:O=!1,maxRecordsToCheck:z=1000,migrateOtherColumns:U=!1}=V,j=`${Z}_migration_check`;if(!O&&q.has(j))return{migrationNeeded:!1,migrationPerformed:!1,recordsMigrated:0,tablesProcessed:0,issues:[]};let Y=[],x=0,F=0,H=!1,A=!1;try{let{KVShardMapper:S}=await Promise.resolve().then(() => (f(),y)),X=new S($.kv,{hashShardMappings:$.hashShardMappings}),I=await C(J),D=W||I.filter((L)=>L!=="shard_mappings"&&!L.startsWith("sqlite_")&&L!=="sqlite_sequence");if(D.length===0)return q.set(j,!0),{migrationNeeded:!1,migrationPerformed:!1,recordsMigrated:0,tablesProcessed:0,issues:[]};for(let L of D)try{let R=await g(J,L,Q);if(!R.isValid||R.recordCount===0)continue;let P=Math.min(z,R.recordCount),YJ=await J.prepare(`
8
+ SELECT ${Q} FROM ${L}
9
+ ORDER BY ${Q}
10
+ LIMIT ?`.trim()).bind(P).all(),t=0,zJ=YJ.results.slice(0,10);for(let N of zJ){let _=String(N[Q]);if(!await X.getShardMapping(_))t++,H=!0}if(t>0){if(console.log(`Auto-migrating table ${L} in shard ${Z} (${R.recordCount} records)`),U){let N=await n(J,L,Q),_=0;for(let T of N){let p=String(T[Q]);if(!await X.getShardMapping(p)){let K=[];if(T.username&&typeof T.username==="string")K.push(`username:${T.username}`);if(T.email&&typeof T.email==="string")K.push(`email:${T.email}`);if(T.name&&typeof T.name==="string")K.push(`name:${T.name}`);await X.setShardMapping(p,Z,K),_++}}x+=_}else{let N=await c(J,L,Q),_=0;for(let T of N)if(!await X.getShardMapping(T))await X.setShardMapping(T,Z),_++;x+=_}F++,A=!0,console.log(`Auto-migrated ${x} records from table ${L}`)}}catch(R){Y.push(`Auto-migration failed for table ${L}: ${R}`)}if(A){if(await X.addKnownShard(Z),!I.includes("shard_mappings"))await J.prepare(`CREATE TABLE IF NOT EXISTS shard_mappings (
11
11
  primary_key TEXT PRIMARY KEY,
12
12
  shard_name TEXT NOT NULL,
13
13
  created_at INTEGER NOT NULL,
14
14
  updated_at INTEGER NOT NULL
15
15
  );
16
- `).run()}if(E.set(W,!0),F)console.log(`Auto-migration completed for shard ${Q}: ${L} records from ${X} tables`)}catch(T){O.push(`Auto-migration error: ${T}`)}return{migrationNeeded:x,migrationPerformed:F,recordsMigrated:L,tablesProcessed:X,issues:O}}async function r(J,Q,U){let Z=`${Q}_migration_check`;if(E.has(Z))return!1;try{let $=await P(J);if($.includes("shard_mappings"))return E.set(Z,!0),!1;let{KVShardMapper:Y}=await Promise.resolve().then(() => (v(),g)),V=new Y(U.kv),W=$.filter((O)=>O!=="shard_mappings"&&!O.startsWith("sqlite_")&&O!=="sqlite_sequence");for(let O of W.slice(0,3))try{if(((await J.prepare(`SELECT COUNT(*) as count FROM ${O} LIMIT 1`).first())?.count||0)>0){let x=await J.prepare(`SELECT id FROM ${O} LIMIT 1`).first();if(x){let F=String(x.id);if(!await V.getShardMapping(F))return!0}}}catch{continue}return!1}catch{return!1}}function a(){E.clear()}function e(J){let Q=`${J}_migration_check`;E.delete(Q)}var E;var B=b(()=>{D();E=new Map});D();v();var C=null;function WJ(J){if(C=J,J.shards&&Object.keys(J.shards).length>0&&!J.disableAutoMigration)QJ(J).catch((Q)=>{console.warn("Background auto-migration failed:",Q)})}async function JJ(J){if(C=J,J.shards&&Object.keys(J.shards).length>0&&!J.disableAutoMigration)try{await QJ(J)}catch(Q){console.warn("Auto migration failed:",Q)}}async function YJ(J,Q){return await JJ(J),await Q()}async function QJ(J){try{let{autoDetectAndMigrate:Q}=await Promise.resolve().then(() => (B(),k)),U=Object.keys(J.shards);console.log(`\uD83D\uDD0D Checking ${U.length} shards for existing data...`);let Z=U.map(async(Y)=>{let V=J.shards[Y];if(!V)return null;try{let W=await Q(V,Y,J,{maxRecordsToCheck:1000});return{shardName:Y,...W}}catch(W){return console.warn(`Auto-migration failed for shard ${Y}:`,W),null}}),z=(await Promise.all(Z)).filter((Y)=>Y?.migrationPerformed);if(z.length>0){let Y=z.reduce((V,W)=>V+(W?.recordsMigrated||0),0);console.log(`\uD83C\uDF89 Auto-migration completed! Migrated ${Y} records across ${z.length} shards`),z.forEach((V)=>{if(V)console.log(` ✅ ${V.shardName}: ${V.recordsMigrated} records from ${V.tablesProcessed} tables`)})}else console.log("✅ All shards ready - no migration needed")}catch(Q){console.warn("Background auto-migration setup failed:",Q)}}function LJ(){C=null}function H(){if(!C)throw new G("CollegeDB not initialized. Call initialize() first.","NOT_INITIALIZED");return C}function VJ(J){let Q=J.trim().toUpperCase();if(Q.startsWith("SELECT")||Q.startsWith("VALUES")||Q.startsWith("TABLE")||Q.startsWith("PRAGMA")||Q.startsWith("EXPLAIN")||Q.startsWith("WITH")||Q.startsWith("SHOW"))return"read";return"write"}function OJ(J,Q){let U=J.strategy||"hash";if(typeof U==="string")return U;return U[Q]}function GJ(J,Q){if(J===Q)return 0;let U={wnam:{lat:37.7749,lon:-122.4194},enam:{lat:40.7128,lon:-74.006},weur:{lat:51.5074,lon:-0.1278},eeur:{lat:52.52,lon:13.405},apac:{lat:35.6762,lon:139.6503},oc:{lat:-33.8688,lon:151.2093},me:{lat:25.2048,lon:55.2708},af:{lat:-26.2041,lon:28.0473}},Z=U[J],$=U[Q],z=Z.lat-$.lat,Y=Z.lon-$.lon;return Math.sqrt(z*z+Y*Y)}function XJ(J){let Q=J.cf;if(!Q||!Q.country)return"wnam";let{country:U,continent:Z}=Q;if(["US","CA","MX"].includes(U)){let $=Q.region||Q.regionCode||"",z=Q.timezone||"";if($.includes("CA")||$.includes("WA")||$.includes("OR")||$.includes("NV")||$.includes("AZ")||$.includes("UT")||z.includes("Pacific")||z.includes("America/Los_Angeles"))return"wnam";return"enam"}if(["GL","PM","BM"].includes(U))return"enam";if(["GB","IE","FR","ES","PT","NL","BE","LU","CH","AT","IT"].includes(U))return"weur";if(["DE","PL","CZ","SK","HU","SI","HR","BA","RS","ME","MK","AL","BG","RO","MD","UA","BY","LT","LV","EE","FI","SE","NO","DK","IS"].includes(U))return"eeur";if(U==="RU")return"eeur";if(["JP","KR","CN","HK","TW","MO","MN","KP"].includes(U))return"apac";if(["TH","VN","SG","MY","ID","PH","BN","KH","LA","MM","TL","IN","PK","BD","LK","NP","BT","MV","AF"].includes(U))return"apac";if(["AU","NZ","PG","FJ","NC","VU","SB","WS","TO","KI","NR","PW","FM","MH","TV"].includes(U))return"oc";if(["AE","SA","QA","KW","BH","OM","YE","IQ","IR","SY","LB","JO","IL","PS","TR","CY"].includes(U))return"me";if(Z==="AF"||["EG","LY","TN","DZ","MA","SD","SS","ET","ER","DJ","SO"].includes(U))return"af";if(["KZ","UZ","TM","TJ","KG"].includes(U))return"eeur";if(Z==="SA"||["BR","AR","CL","PE","CO","VE","EC","BO","PY","UY","GY","SR","GF"].includes(U))return"enam";if(["GT","BZ","SV","HN","NI","CR","PA","CU","JM","HT","DO","PR","TT","BB","GD","VC","LC","DM","AG","KN"].includes(U))return"enam";return"wnam"}function xJ(J,Q,U,Z){let $=Q.filter((L)=>U[L]);if($.length===0){let L=0;for(let x=0;x<Z.length;x++){let F=Z.charCodeAt(x);L=(L<<5)-L+F,L=L&L}let X=Math.abs(L)%Q.length;return Q[X]}let z=$.map((L)=>{let X=U[L],x=GJ(J,X.region),F=X.priority||1,T=x-F*0.1;return{shard:L,score:T,distance:x,priority:F}});z.sort((L,X)=>L.score-X.score);let Y=z[0].score,V=z.filter((L)=>Math.abs(L.score-Y)<0.01);if(V.length===1)return V[0].shard;let W=0;for(let L=0;L<Z.length;L++){let X=Z.charCodeAt(L);W=(W<<5)-W+X,W=W&W}let O=Math.abs(W)%V.length;return V[O].shard}async function FJ(J,Q="write"){let U=H(),Z=new A(U.kv),$=await Z.getShardMapping(J);if($)return $.shard;let z=Object.keys(U.shards);if(z.length===0)throw new G("No shards configured","NO_SHARDS");for(let W of z){let O=U.shards[W];if(!O)continue;try{let{autoDetectAndMigrate:L}=await Promise.resolve().then(() => (B(),k));if((await L(O,W,U,{maxRecordsToCheck:100})).migrationPerformed){let x=await Z.getShardMapping(J);if(x)return x.shard}}catch(L){console.warn(`Auto-migration check failed for shard ${W}:`,L)}}let Y,V=OJ(U,Q);if(U.coordinator)try{let W=U.coordinator.idFromName("default"),L=await U.coordinator.get(W).fetch("http://coordinator/allocate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({primaryKey:J,strategy:V,operationType:Q,targetRegion:U.targetRegion,shardLocations:U.shardLocations})});if(L.ok)Y=(await L.json()).shard;else Y=z[Math.floor(Math.random()*z.length)]}catch(W){console.warn("Coordinator allocation failed, falling back to local strategy:",W),Y=z[Math.floor(Math.random()*z.length)]}else switch(V){case"hash":let W=0;for(let L=0;L<J.length;L++){let X=J.charCodeAt(L);W=(W<<5)-W+X,W=W&W}let O=Math.abs(W)%z.length;Y=z[O]||z[0];break;case"location":if(!U.targetRegion){console.warn("Location strategy requires targetRegion in config, falling back to hash");let L=0;for(let x=0;x<J.length;x++){let F=J.charCodeAt(x);L=(L<<5)-L+F,L=L&L}let X=Math.abs(L)%z.length;Y=z[X]||z[0]}else Y=xJ(U.targetRegion,z,U.shardLocations||{},J);break;case"random":Y=z[Math.floor(Math.random()*z.length)]||z[0];break;default:Y=z[0];break}return await Z.setShardMapping(J,Y),Y}async function jJ(J,Q="write"){let U=H(),Z=await FJ(J,Q),$=U.shards[Z];if(!$)throw new G(`Shard ${Z} not found in configuration`,"SHARD_NOT_FOUND");return $}async function IJ(J,Q){let{createSchema:U}=await Promise.resolve().then(() => (B(),k));await U(J,Q)}async function K(J,Q){let U=VJ(Q);return(await jJ(J,U)).prepare(Q)}async function _J(J,Q,U=[]){let $=await(await K(J,Q)).bind(...U).run();if(!$.success)throw new G(`Query failed: ${$.error||"Unknown error"}`,"QUERY_FAILED");return $}async function HJ(J,Q,U=[]){let $=await(await K(J,Q)).bind(...U).all();if(!$.success)throw new G(`Query failed: ${$.error||"Unknown error"}`,"QUERY_FAILED");return $}async function TJ(J,Q,U=[]){return await(await K(J,Q)).bind(...U).first()}async function AJ(J,Q,U){let Z=H();if(!Z.shards[Q])throw new G(`Shard ${Q} not found in configuration`,"SHARD_NOT_FOUND");let $=new A(Z.kv),z=await $.getShardMapping(J);if(!z)throw new G(`No existing mapping found for primary key: ${J}`,"MAPPING_NOT_FOUND");if(z.shard!==Q){let{migrateRecord:Y}=await Promise.resolve().then(() => (B(),k)),V=Z.shards[z.shard],W=Z.shards[Q];if(!V||!W)throw new G("Source or target shard not available","SHARD_UNAVAILABLE");await Y(V,W,J,U)}await $.updateShardMapping(J,Q)}async function wJ(){let J=H();if(J.coordinator)try{let Q=J.coordinator.idFromName("default"),Z=await J.coordinator.get(Q).fetch("http://coordinator/shards");if(Z.ok)return await Z.json()}catch(Q){console.warn("Failed to get shards from coordinator:",Q)}return Object.keys(J.shards)}async function EJ(){let J=H();if(J.coordinator)try{let Z=J.coordinator.idFromName("default"),z=await J.coordinator.get(Z).fetch("http://coordinator/stats");if(z.ok)return await z.json()}catch(Z){console.warn("Failed to get stats from coordinator:",Z)}let U=await new A(J.kv).getShardKeyCounts();return Object.entries(J.shards).map(([Z,$])=>({binding:Z,count:U[Z]||0}))}async function MJ(J,Q,U=[]){let $=H().shards[J];if(!$)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");let z=await $.prepare(Q).bind(...U).run();if(!z.success)throw new G(`Query failed: ${z.error||"Unknown error"}`,"QUERY_FAILED");return z}async function RJ(J,Q,U=[]){let $=H().shards[J];if(!$)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");return await $.prepare(Q).bind(...U).all()}async function DJ(J,Q,U=[]){let $=H().shards[J];if(!$)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");return await $.prepare(Q).bind(...U).first()}async function PJ(){let J=H();if(await new A(J.kv).clearAllMappings(),J.coordinator)try{let U=J.coordinator.idFromName("default");await J.coordinator.get(U).fetch("http://coordinator/flush",{method:"POST"})}catch(U){console.warn("Failed to flush coordinator:",U)}}D();class y{state;constructor(J){this.state=J}async getState(){return await this.state.storage.get("coordinator_state")||{knownShards:[],shardStats:{},strategy:"round-robin",roundRobinIndex:0}}async saveState(J){await this.state.storage.put("coordinator_state",J)}async fetch(J){let U=new URL(J.url).pathname,Z=J.method;try{switch(`${Z} ${U}`){case"GET /shards":return this.handleListShards();case"POST /shards":return this.handleAddShard(J);case"DELETE /shards":return this.handleRemoveShard(J);case"GET /stats":return this.handleGetStats();case"POST /stats":return this.handleUpdateStats(J);case"POST /allocate":return this.handleAllocateShard(J);case"POST /flush":return this.handleFlush();case"GET /health":return new Response("OK",{status:200});default:return new Response("Not Found",{status:404})}}catch($){return console.error("ShardCoordinator error:",$),new Response("Internal Server Error",{status:500})}}async handleListShards(){let J=await this.getState();return new Response(JSON.stringify(J.knownShards),{headers:{"Content-Type":"application/json"}})}async handleAddShard(J){let{shard:Q}=await J.json();if(!Q||typeof Q!=="string")return new Response(JSON.stringify({error:"Missing or invalid shard parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let U=await this.getState();if(!U.knownShards.includes(Q))U.knownShards.push(Q),U.shardStats[Q]={binding:Q,count:0,lastUpdated:Date.now()},await this.saveState(U);return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}async handleRemoveShard(J){let{shard:Q}=await J.json();if(!Q||typeof Q!=="string")return new Response(JSON.stringify({error:"Missing or invalid shard parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let U=await this.getState(),Z=U.knownShards.indexOf(Q);if(Z>-1){if(U.knownShards.splice(Z,1),delete U.shardStats[Q],U.roundRobinIndex>=U.knownShards.length)U.roundRobinIndex=0;await this.saveState(U)}return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}async handleGetStats(){let J=await this.getState(),Q=Object.values(J.shardStats);return new Response(JSON.stringify(Q),{headers:{"Content-Type":"application/json"}})}async handleUpdateStats(J){let{shard:Q,count:U}=await J.json();if(!Q||typeof Q!=="string")return new Response(JSON.stringify({error:"Missing or invalid shard parameter"}),{status:400,headers:{"Content-Type":"application/json"}});if(U===void 0||typeof U!=="number")return new Response(JSON.stringify({error:"Missing or invalid count parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let Z=await this.getState();if(Z.shardStats[Q])Z.shardStats[Q].count=U,Z.shardStats[Q].lastUpdated=Date.now(),await this.saveState(Z);return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}async handleAllocateShard(J){let{primaryKey:Q,strategy:U,operationType:Z}=await J.json();if(!Q||typeof Q!=="string")return new Response(JSON.stringify({error:"Missing or invalid primaryKey parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let $=await this.getState();if($.knownShards.length===0)return new Response(JSON.stringify({error:"No shards available"}),{status:400,headers:{"Content-Type":"application/json"}});let z=this.resolveStrategy($.strategy,U,Z||"write"),Y=this.selectShard(Q,$,z);if(z==="round-robin")$.roundRobinIndex=($.roundRobinIndex+1)%$.knownShards.length,await this.saveState($);return new Response(JSON.stringify({shard:Y}),{headers:{"Content-Type":"application/json"}})}async handleFlush(){return await this.state.storage.deleteAll(),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}resolveStrategy(J,Q,U="write"){if(Q)return Q;if(typeof J==="string")return J;return J[U]}selectShard(J,Q,U){let Z=Q.knownShards;if(Z.length===0)throw new G("No shards available","NO_SHARDS");switch(U){case"round-robin":return Z[Q.roundRobinIndex]??Z[0];case"random":return Z[Math.floor(Math.random()*Z.length)];case"hash":let $=0;for(let W=0;W<J.length;W++){let O=J.charCodeAt(W);$=($<<5)-$+O,$=$&$}let z=Math.abs($)%Z.length;return Z[z];case"location":let Y=0;for(let W=0;W<J.length;W++){let O=J.charCodeAt(W);Y=(Y<<5)-Y+O,Y=Y&Y}let V=Math.abs(Y)%Z.length;return Z[V];default:return Z[0]}}async incrementShardCount(J){let Q=await this.getState();if(Q.shardStats[J])Q.shardStats[J].count++,Q.shardStats[J].lastUpdated=Date.now(),await this.saveState(Q)}async decrementShardCount(J){let Q=await this.getState();if(Q.shardStats[J]&&Q.shardStats[J].count>0)Q.shardStats[J].count--,Q.shardStats[J].lastUpdated=Date.now(),await this.saveState(Q)}}if(typeof global!=="undefined"){class J{data=new Map;async get(Z){return this.data.get(Z)}async put(Z,$){this.data.set(Z,$)}async delete(Z){return this.data.delete(Z)}async deleteAll(){this.data.clear()}async list(Z){if(!Z?.prefix)return new Map(this.data);let $=new Map;for(let[z,Y]of this.data.entries())if(z.startsWith(Z.prefix))$.set(z,Y);return $}}class Q{storage;constructor(){this.storage=new J}}class U{coordinator;mockState;constructor(){this.mockState=new Q,this.coordinator=new y(this.mockState)}async testShardAllocation(){await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-east"})})),await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-west"})}));let $=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"user-1",strategy:"round-robin"})}))).json(),Y=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"user-2",strategy:"round-robin"})}))).json();console.assert($.shard!==Y.shard,"Round-robin should alternate shards");let W=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"consistent-key",strategy:"hash"})}))).json(),L=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"consistent-key",strategy:"hash"})}))).json();console.assert(W.shard===L.shard,"Hash allocation should be consistent"),console.log("✅ Shard allocation tests passed")}async testShardStats(){await this.coordinator.fetch(new Request("http://test/flush",{method:"POST"})),await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-stats-test"})})),await this.coordinator.fetch(new Request("http://test/stats",{method:"POST",body:JSON.stringify({shard:"db-stats-test",count:42})}));let $=await(await this.coordinator.fetch(new Request("http://test/stats",{method:"GET"}))).json();console.assert($.length===1,"Should have one shard stat"),console.assert($[0]?.binding==="db-stats-test","Should have correct binding name"),console.assert($[0]?.count===42,"Should have correct count"),console.log("✅ Shard stats tests passed")}async testErrorHandling(){await this.coordinator.fetch(new Request("http://test/flush",{method:"POST"}));let Z=await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"test-key"})}));console.assert(Z.status===400,"Should return 400 for no shards available");let $=await this.coordinator.fetch(new Request("http://test/invalid",{method:"GET"}));console.assert($.status===404,"Should return 404 for invalid endpoint"),console.log("✅ Error handling tests passed")}async testCountManagement(){await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-count-test"})})),await this.coordinator.incrementShardCount("db-count-test"),await this.coordinator.incrementShardCount("db-count-test");let Z=await this.coordinator.fetch(new Request("http://test/stats",{method:"GET"})),$=await Z.json(),z=$.find((V)=>V.binding==="db-count-test");console.assert(z?.count===2,"Count should be 2 after two increments"),await this.coordinator.decrementShardCount("db-count-test"),Z=await this.coordinator.fetch(new Request("http://test/stats",{method:"GET"})),$=await Z.json();let Y=$.find((V)=>V.binding==="db-count-test");console.assert(Y?.count===1,"Count should be 1 after decrement"),console.log("✅ Count management tests passed")}async runAllTests(){console.log("\uD83E\uDDEA Running ShardCoordinator tests...");try{return await this.testShardAllocation(),await this.testShardStats(),await this.testErrorHandling(),await this.testCountManagement(),console.log("\uD83C\uDF89 All ShardCoordinator tests passed!"),!0}catch(Z){return console.error("❌ ShardCoordinator tests failed:",Z),!1}}}globalThis.testShardCoordinator=()=>new U}D();v();B();export{N as validateTableForSharding,c as schemaExists,MJ as runShard,_J as run,LJ as resetConfig,AJ as reassignShard,K as prepare,o as migrateRecord,P as listTables,wJ as listKnownShards,t as integrateExistingDatabase,JJ as initializeAsync,WJ as initialize,EJ as getShardStats,XJ as getClosestRegionFromIP,PJ as flush,DJ as firstShard,TJ as first,n as dropSchema,S as discoverExistingPrimaryKeys,d as createSchemaAcrossShards,IJ as createSchema,i as createMappingsForExistingKeys,YJ as collegedb,e as clearShardMigrationCache,a as clearMigrationCache,r as checkMigrationNeeded,s as autoDetectAndMigrate,RJ as allShard,HJ as all,y as ShardCoordinator,A as KVShardMapper,G as CollegeDBError};
16
+ `).run()}if(q.set(j,!0),A)console.log(`Auto-migration completed for shard ${Z}: ${x} records from ${F} tables`)}catch(S){Y.push(`Auto-migration error: ${S}`)}return{migrationNeeded:H,migrationPerformed:A,recordsMigrated:x,tablesProcessed:F,issues:Y}}async function QJ(J,Z,$){let V=`${Z}_migration_check`;if(q.has(V))return!1;try{let Q=await C(J);if(Q.includes("shard_mappings"))return q.set(V,!0),!1;let{KVShardMapper:O}=await Promise.resolve().then(() => (f(),y)),z=new O($.kv,{hashShardMappings:$.hashShardMappings}),U=Q.filter((j)=>j!=="shard_mappings"&&!j.startsWith("sqlite_")&&j!=="sqlite_sequence");for(let j of U.slice(0,3))try{if(((await J.prepare(`SELECT COUNT(*) as count FROM ${j} LIMIT 1`).first())?.count||0)>0){let F=await J.prepare(`SELECT id FROM ${j} LIMIT 1`).first();if(F){let H=String(F.id);if(!await z.getShardMapping(H))return!0}}}catch{continue}return!1}catch{return!1}}function VJ(){q.clear()}function WJ(J){let Z=`${J}_migration_check`;q.delete(Z)}var q;var M=m(()=>{k();q=new Map});k();f();var u=null;function xJ(J){if(u=J,J.shards&&Object.keys(J.shards).length>0&&!J.disableAutoMigration)UJ(J).catch((Z)=>{console.warn("Background auto-migration failed:",Z)})}async function OJ(J){if(u=J,J.shards&&Object.keys(J.shards).length>0&&!J.disableAutoMigration)try{await UJ(J)}catch(Z){console.warn("Auto migration failed:",Z)}}async function FJ(J,Z){return await OJ(J),await Z()}async function UJ(J){try{let{autoDetectAndMigrate:Z}=await Promise.resolve().then(() => (M(),b)),$=Object.keys(J.shards);console.log(`\uD83D\uDD0D Checking ${$.length} shards for existing data...`);let V=$.map(async(O)=>{let z=J.shards[O];if(!z)return null;try{let U=await Z(z,O,J,{maxRecordsToCheck:1000});return{shardName:O,...U}}catch(U){return console.warn(`Auto-migration failed for shard ${O}:`,U),null}}),W=(await Promise.all(V)).filter((O)=>O?.migrationPerformed);if(W.length>0){let O=W.reduce((z,U)=>z+(U?.recordsMigrated||0),0);console.log(`\uD83C\uDF89 Auto-migration completed! Migrated ${O} records across ${W.length} shards`),W.forEach((z)=>{if(z)console.log(` ✅ ${z.shardName}: ${z.recordsMigrated} records from ${z.tablesProcessed} tables`)})}else console.log("✅ All shards ready - no migration needed")}catch(Z){console.warn("Background auto-migration setup failed:",Z)}}function GJ(){u=null}function v(){if(!u)throw new G("CollegeDB not initialized. Call initialize() first.","NOT_INITIALIZED");return u}function LJ(J){let Z=J.trim().toUpperCase();if(Z.startsWith("SELECT")||Z.startsWith("VALUES")||Z.startsWith("TABLE")||Z.startsWith("PRAGMA")||Z.startsWith("EXPLAIN")||Z.startsWith("WITH")||Z.startsWith("SHOW"))return"read";return"write"}function XJ(J,Z){let $=J.strategy||"hash";if(typeof $==="string")return $;return $[Z]}function HJ(J,Z){if(J===Z)return 0;let $={wnam:{lat:37.7749,lon:-122.4194},enam:{lat:40.7128,lon:-74.006},weur:{lat:51.5074,lon:-0.1278},eeur:{lat:52.52,lon:13.405},apac:{lat:35.6762,lon:139.6503},oc:{lat:-33.8688,lon:151.2093},me:{lat:25.2048,lon:55.2708},af:{lat:-26.2041,lon:28.0473}},V=$[J],Q=$[Z],W=V.lat-Q.lat,O=V.lon-Q.lon;return Math.sqrt(W*W+O*O)}function TJ(J){let Z=J.cf;if(!Z||!Z.country)return"wnam";let{country:$,continent:V}=Z;if(["US","CA","MX"].includes($)){let Q=Z.region||Z.regionCode||"",W=Z.timezone||"";if(Q.includes("CA")||Q.includes("WA")||Q.includes("OR")||Q.includes("NV")||Q.includes("AZ")||Q.includes("UT")||W.includes("Pacific")||W.includes("America/Los_Angeles"))return"wnam";return"enam"}if(["GL","PM","BM"].includes($))return"enam";if(["GB","IE","FR","ES","PT","NL","BE","LU","CH","AT","IT"].includes($))return"weur";if(["DE","PL","CZ","SK","HU","SI","HR","BA","RS","ME","MK","AL","BG","RO","MD","UA","BY","LT","LV","EE","FI","SE","NO","DK","IS"].includes($))return"eeur";if($==="RU")return"eeur";if(["JP","KR","CN","HK","TW","MO","MN","KP"].includes($))return"apac";if(["TH","VN","SG","MY","ID","PH","BN","KH","LA","MM","TL","IN","PK","BD","LK","NP","BT","MV","AF"].includes($))return"apac";if(["AU","NZ","PG","FJ","NC","VU","SB","WS","TO","KI","NR","PW","FM","MH","TV"].includes($))return"oc";if(["AE","SA","QA","KW","BH","OM","YE","IQ","IR","SY","LB","JO","IL","PS","TR","CY"].includes($))return"me";if(V==="AF"||["EG","LY","TN","DZ","MA","SD","SS","ET","ER","DJ","SO"].includes($))return"af";if(["KZ","UZ","TM","TJ","KG"].includes($))return"eeur";if(V==="SA"||["BR","AR","CL","PE","CO","VE","EC","BO","PY","UY","GY","SR","GF"].includes($))return"enam";if(["GT","BZ","SV","HN","NI","CR","PA","CU","JM","HT","DO","PR","TT","BB","GD","VC","LC","DM","AG","KN"].includes($))return"enam";return"wnam"}function wJ(J,Z,$,V){let Q=Z.filter((Y)=>$[Y]);if(Q.length===0){let Y=0;for(let F=0;F<V.length;F++){let H=V.charCodeAt(F);Y=(Y<<5)-Y+H,Y=Y&Y}let x=Math.abs(Y)%Z.length;return Z[x]}let W=Q.map((Y)=>{let x=$[Y],F=HJ(J,x.region),H=x.priority||1,A=F-H*0.1;return{shard:Y,score:A,distance:F,priority:H}});W.sort((Y,x)=>Y.score-x.score);let O=W[0].score,z=W.filter((Y)=>Math.abs(Y.score-O)<0.01);if(z.length===1)return z[0].shard;let U=0;for(let Y=0;Y<V.length;Y++){let x=V.charCodeAt(Y);U=(U<<5)-U+x,U=U&U}let j=Math.abs(U)%z.length;return z[j].shard}async function AJ(J,Z="write"){let $=v(),V=new E($.kv,{hashShardMappings:$.hashShardMappings}),Q=await V.getShardMapping(J);if(Q)return Q.shard;let W=Object.keys($.shards);if(W.length===0)throw new G("No shards configured","NO_SHARDS");for(let U of W){let j=$.shards[U];if(!j)continue;try{let{autoDetectAndMigrate:Y}=await Promise.resolve().then(() => (M(),b));if((await Y(j,U,$,{maxRecordsToCheck:100})).migrationPerformed){let F=await V.getShardMapping(J);if(F)return F.shard}}catch(Y){console.warn(`Auto-migration check failed for shard ${U}:`,Y)}}let O,z=XJ($,Z);if($.coordinator)try{let U=$.coordinator.idFromName("default"),Y=await $.coordinator.get(U).fetch("http://coordinator/allocate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({primaryKey:J,strategy:z,operationType:Z,targetRegion:$.targetRegion,shardLocations:$.shardLocations})});if(Y.ok)O=(await Y.json()).shard;else O=W[Math.floor(Math.random()*W.length)]}catch(U){console.warn("Coordinator allocation failed, falling back to local strategy:",U),O=W[Math.floor(Math.random()*W.length)]}else switch(z){case"hash":let U=0;for(let Y=0;Y<J.length;Y++){let x=J.charCodeAt(Y);U=(U<<5)-U+x,U=U&U}let j=Math.abs(U)%W.length;O=W[j]||W[0];break;case"location":if(!$.targetRegion){console.warn("Location strategy requires targetRegion in config, falling back to hash");let Y=0;for(let F=0;F<J.length;F++){let H=J.charCodeAt(F);Y=(Y<<5)-Y+H,Y=Y&Y}let x=Math.abs(Y)%W.length;O=W[x]||W[0]}else O=wJ($.targetRegion,W,$.shardLocations||{},J);break;case"random":O=W[Math.floor(Math.random()*W.length)]||W[0];break;default:O=W[0];break}return await V.setShardMapping(J,O),O}async function DJ(J,Z="write"){let $=v(),V=await AJ(J,Z),Q=$.shards[V];if(!Q)throw new G(`Shard ${V} not found in configuration`,"SHARD_NOT_FOUND");return Q}async function BJ(J,Z){let{createSchema:$}=await Promise.resolve().then(() => (M(),b));await $(J,Z)}async function h(J,Z){let $=LJ(Z);return(await DJ(J,$)).prepare(Z)}async function vJ(J,Z,$=[]){let Q=await(await h(J,Z)).bind(...$).run();if(!Q.success)throw new G(`Query failed: ${Q.error||"Unknown error"}`,"QUERY_FAILED");return Q}async function RJ(J,Z,$=[]){let Q=await(await h(J,Z)).bind(...$).all();if(!Q.success)throw new G(`Query failed: ${Q.error||"Unknown error"}`,"QUERY_FAILED");return Q}async function EJ(J,Z,$=[]){return await(await h(J,Z)).bind(...$).first()}async function IJ(J,Z,$){let V=v();if(!V.shards[Z])throw new G(`Shard ${Z} not found in configuration`,"SHARD_NOT_FOUND");let Q=new E(V.kv,{hashShardMappings:V.hashShardMappings}),W=await Q.getShardMapping(J);if(!W)throw new G(`No existing mapping found for primary key: ${J}`,"MAPPING_NOT_FOUND");if(W.shard!==Z){let{migrateRecord:O}=await Promise.resolve().then(() => (M(),b)),z=V.shards[W.shard],U=V.shards[Z];if(!z||!U)throw new G("Source or target shard not available","SHARD_UNAVAILABLE");await O(z,U,J,$)}await Q.updateShardMapping(J,Z)}async function _J(){let J=v();if(J.coordinator)try{let Z=J.coordinator.idFromName("default"),V=await J.coordinator.get(Z).fetch("http://coordinator/shards");if(V.ok)return await V.json()}catch(Z){console.warn("Failed to get shards from coordinator:",Z)}return Object.keys(J.shards)}async function qJ(){let J=v();if(J.coordinator)try{let V=J.coordinator.idFromName("default"),W=await J.coordinator.get(V).fetch("http://coordinator/stats");if(W.ok)return await W.json()}catch(V){console.warn("Failed to get stats from coordinator:",V)}let $=await new E(J.kv,{hashShardMappings:J.hashShardMappings}).getShardKeyCounts();return Object.entries(J.shards).map(([V,Q])=>({binding:V,count:$[V]||0}))}async function PJ(J,Z,$=[]){let Q=v().shards[J];if(!Q)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");let W=await Q.prepare(Z).bind(...$).run();if(!W.success)throw new G(`Query failed: ${W.error||"Unknown error"}`,"QUERY_FAILED");return W}async function kJ(J,Z,$=[]){let Q=v().shards[J];if(!Q)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");return await Q.prepare(Z).bind(...$).all()}async function CJ(J,Z,$=[]){let Q=v().shards[J];if(!Q)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");return await Q.prepare(Z).bind(...$).first()}async function MJ(){let J=v();if(await new E(J.kv,{hashShardMappings:J.hashShardMappings}).clearAllMappings(),J.coordinator)try{let $=J.coordinator.idFromName("default");await J.coordinator.get($).fetch("http://coordinator/flush",{method:"POST"})}catch($){console.warn("Failed to flush coordinator:",$)}}k();class o{state;constructor(J){this.state=J}async getState(){return await this.state.storage.get("coordinator_state")||{knownShards:[],shardStats:{},strategy:"round-robin",roundRobinIndex:0}}async saveState(J){await this.state.storage.put("coordinator_state",J)}async fetch(J){let $=new URL(J.url).pathname,V=J.method;try{switch(`${V} ${$}`){case"GET /shards":return this.handleListShards();case"POST /shards":return this.handleAddShard(J);case"DELETE /shards":return this.handleRemoveShard(J);case"GET /stats":return this.handleGetStats();case"POST /stats":return this.handleUpdateStats(J);case"POST /allocate":return this.handleAllocateShard(J);case"POST /flush":return this.handleFlush();case"GET /health":return new Response("OK",{status:200});default:return new Response("Not Found",{status:404})}}catch(Q){return console.error("ShardCoordinator error:",Q),new Response("Internal Server Error",{status:500})}}async handleListShards(){let J=await this.getState();return new Response(JSON.stringify(J.knownShards),{headers:{"Content-Type":"application/json"}})}async handleAddShard(J){let{shard:Z}=await J.json();if(!Z||typeof Z!=="string")return new Response(JSON.stringify({error:"Missing or invalid shard parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let $=await this.getState();if(!$.knownShards.includes(Z))$.knownShards.push(Z),$.shardStats[Z]={binding:Z,count:0,lastUpdated:Date.now()},await this.saveState($);return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}async handleRemoveShard(J){let{shard:Z}=await J.json();if(!Z||typeof Z!=="string")return new Response(JSON.stringify({error:"Missing or invalid shard parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let $=await this.getState(),V=$.knownShards.indexOf(Z);if(V>-1){if($.knownShards.splice(V,1),delete $.shardStats[Z],$.roundRobinIndex>=$.knownShards.length)$.roundRobinIndex=0;await this.saveState($)}return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}async handleGetStats(){let J=await this.getState(),Z=Object.values(J.shardStats);return new Response(JSON.stringify(Z),{headers:{"Content-Type":"application/json"}})}async handleUpdateStats(J){let{shard:Z,count:$}=await J.json();if(!Z||typeof Z!=="string")return new Response(JSON.stringify({error:"Missing or invalid shard parameter"}),{status:400,headers:{"Content-Type":"application/json"}});if($===void 0||typeof $!=="number")return new Response(JSON.stringify({error:"Missing or invalid count parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let V=await this.getState();if(V.shardStats[Z])V.shardStats[Z].count=$,V.shardStats[Z].lastUpdated=Date.now(),await this.saveState(V);return new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}async handleAllocateShard(J){let{primaryKey:Z,strategy:$,operationType:V}=await J.json();if(!Z||typeof Z!=="string")return new Response(JSON.stringify({error:"Missing or invalid primaryKey parameter"}),{status:400,headers:{"Content-Type":"application/json"}});let Q=await this.getState();if(Q.knownShards.length===0)return new Response(JSON.stringify({error:"No shards available"}),{status:400,headers:{"Content-Type":"application/json"}});let W=this.resolveStrategy(Q.strategy,$,V||"write"),O=this.selectShard(Z,Q,W);if(W==="round-robin")Q.roundRobinIndex=(Q.roundRobinIndex+1)%Q.knownShards.length,await this.saveState(Q);return new Response(JSON.stringify({shard:O}),{headers:{"Content-Type":"application/json"}})}async handleFlush(){return await this.state.storage.deleteAll(),new Response(JSON.stringify({success:!0}),{headers:{"Content-Type":"application/json"}})}resolveStrategy(J,Z,$="write"){if(Z)return Z;if(typeof J==="string")return J;return J[$]}selectShard(J,Z,$){let V=Z.knownShards;if(V.length===0)throw new G("No shards available","NO_SHARDS");switch($){case"round-robin":return V[Z.roundRobinIndex]??V[0];case"random":return V[Math.floor(Math.random()*V.length)];case"hash":let Q=0;for(let U=0;U<J.length;U++){let j=J.charCodeAt(U);Q=(Q<<5)-Q+j,Q=Q&Q}let W=Math.abs(Q)%V.length;return V[W];case"location":let O=0;for(let U=0;U<J.length;U++){let j=J.charCodeAt(U);O=(O<<5)-O+j,O=O&O}let z=Math.abs(O)%V.length;return V[z];default:return V[0]}}async incrementShardCount(J){let Z=await this.getState();if(Z.shardStats[J])Z.shardStats[J].count++,Z.shardStats[J].lastUpdated=Date.now(),await this.saveState(Z)}async decrementShardCount(J){let Z=await this.getState();if(Z.shardStats[J]&&Z.shardStats[J].count>0)Z.shardStats[J].count--,Z.shardStats[J].lastUpdated=Date.now(),await this.saveState(Z)}}if(typeof global!=="undefined"){class J{data=new Map;async get(V){return this.data.get(V)}async put(V,Q){this.data.set(V,Q)}async delete(V){return this.data.delete(V)}async deleteAll(){this.data.clear()}async list(V){if(!V?.prefix)return new Map(this.data);let Q=new Map;for(let[W,O]of this.data.entries())if(W.startsWith(V.prefix))Q.set(W,O);return Q}}class Z{storage;constructor(){this.storage=new J}}class ${coordinator;mockState;constructor(){this.mockState=new Z,this.coordinator=new o(this.mockState)}async testShardAllocation(){await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-east"})})),await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-west"})}));let Q=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"user-1",strategy:"round-robin"})}))).json(),O=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"user-2",strategy:"round-robin"})}))).json();console.assert(Q.shard!==O.shard,"Round-robin should alternate shards");let U=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"consistent-key",strategy:"hash"})}))).json(),Y=await(await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"consistent-key",strategy:"hash"})}))).json();console.assert(U.shard===Y.shard,"Hash allocation should be consistent"),console.log("✅ Shard allocation tests passed")}async testShardStats(){await this.coordinator.fetch(new Request("http://test/flush",{method:"POST"})),await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-stats-test"})})),await this.coordinator.fetch(new Request("http://test/stats",{method:"POST",body:JSON.stringify({shard:"db-stats-test",count:42})}));let Q=await(await this.coordinator.fetch(new Request("http://test/stats",{method:"GET"}))).json();console.assert(Q.length===1,"Should have one shard stat"),console.assert(Q[0]?.binding==="db-stats-test","Should have correct binding name"),console.assert(Q[0]?.count===42,"Should have correct count"),console.log("✅ Shard stats tests passed")}async testErrorHandling(){await this.coordinator.fetch(new Request("http://test/flush",{method:"POST"}));let V=await this.coordinator.fetch(new Request("http://test/allocate",{method:"POST",body:JSON.stringify({primaryKey:"test-key"})}));console.assert(V.status===400,"Should return 400 for no shards available");let Q=await this.coordinator.fetch(new Request("http://test/invalid",{method:"GET"}));console.assert(Q.status===404,"Should return 404 for invalid endpoint"),console.log("✅ Error handling tests passed")}async testCountManagement(){await this.coordinator.fetch(new Request("http://test/shards",{method:"POST",body:JSON.stringify({shard:"db-count-test"})})),await this.coordinator.incrementShardCount("db-count-test"),await this.coordinator.incrementShardCount("db-count-test");let V=await this.coordinator.fetch(new Request("http://test/stats",{method:"GET"})),Q=await V.json(),W=Q.find((z)=>z.binding==="db-count-test");console.assert(W?.count===2,"Count should be 2 after two increments"),await this.coordinator.decrementShardCount("db-count-test"),V=await this.coordinator.fetch(new Request("http://test/stats",{method:"GET"})),Q=await V.json();let O=Q.find((z)=>z.binding==="db-count-test");console.assert(O?.count===1,"Count should be 1 after decrement"),console.log("✅ Count management tests passed")}async runAllTests(){console.log("\uD83E\uDDEA Running ShardCoordinator tests...");try{return await this.testShardAllocation(),await this.testShardStats(),await this.testErrorHandling(),await this.testCountManagement(),console.log("\uD83C\uDF89 All ShardCoordinator tests passed!"),!0}catch(V){return console.error("❌ ShardCoordinator tests failed:",V),!1}}}globalThis.testShardCoordinator=()=>new $}k();f();M();export{g as validateTableForSharding,d as schemaExists,PJ as runShard,vJ as run,GJ as resetConfig,IJ as reassignShard,h as prepare,e as migrateRecord,C as listTables,_J as listKnownShards,ZJ as integrateExistingDatabase,OJ as initializeAsync,xJ as initialize,qJ as getShardStats,TJ as getClosestRegionFromIP,MJ as flush,CJ as firstShard,EJ as first,a as dropSchema,c as discoverExistingPrimaryKeys,r as createSchemaAcrossShards,BJ as createSchema,JJ as createMappingsForExistingKeys,FJ as collegedb,WJ as clearShardMigrationCache,VJ as clearMigrationCache,QJ as checkMigrationNeeded,$J as autoDetectAndMigrate,kJ as allShard,RJ as all,o as ShardCoordinator,E as KVShardMapper,G as CollegeDBError};
17
17
 
18
- //# debugId=757741E0B0241C8464756E2164756E21
18
+ //# debugId=688156402D62C8D964756E2164756E21
19
19
  //# sourceMappingURL=index.js.map