@earth-app/collegedb 1.0.4 → 1.0.6
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/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +5 -5
- package/dist/migrations.d.ts +1 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* @author Gregory Mitchell
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
export { all, allShard, collegedb, createSchema, first, firstShard, flush, getClosestRegionFromIP, getShardStats, initialize, initializeAsync, listKnownShards, prepare, reassignShard, resetConfig, run, runShard } from './router.js';
|
|
11
|
+
export { all, allAllShards, allShard, collegedb, createSchema, first, firstAllShards, firstShard, flush, getClosestRegionFromIP, getShardStats, initialize, initializeAsync, listKnownShards, prepare, reassignShard, resetConfig, run, runAllShards, runShard } from './router.js';
|
|
12
12
|
export { ShardCoordinator } from './durable.js';
|
|
13
13
|
export { CollegeDBError } from './errors.js';
|
|
14
14
|
export { KVShardMapper } from './kvmap.js';
|
|
15
|
-
export { autoDetectAndMigrate, checkMigrationNeeded, clearMigrationCache, clearShardMigrationCache, createMappingsForExistingKeys, createSchemaAcrossShards, discoverExistingPrimaryKeys, dropSchema, integrateExistingDatabase, listTables, migrateRecord, schemaExists, validateTableForSharding, type IntegrationOptions, type IntegrationResult, type ValidationResult } from './migrations.js';
|
|
15
|
+
export { autoDetectAndMigrate, checkMigrationNeeded, clearMigrationCache, clearShardMigrationCache, createMappingsForExistingKeys, createSchemaAcrossShards, discoverExistingPrimaryKeys, discoverExistingRecordsWithColumns, dropSchema, integrateExistingDatabase, listTables, migrateRecord, schemaExists, validateTableForSharding, type IntegrationOptions, type IntegrationResult, type ValidationResult } from './migrations.js';
|
|
16
16
|
export type { CollegeDBConfig, D1Region, Env, MixedShardingStrategy, OperationType, ShardCoordinatorState, ShardLocation, ShardMapping, ShardStats, ShardingStrategy } from './types.js';
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACN,GAAG,EACH,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,KAAK,EACL,UAAU,EACV,KAAK,EACL,sBAAsB,EACtB,aAAa,EACb,UAAU,EACV,eAAe,EACf,eAAe,EACf,OAAO,EACP,aAAa,EACb,WAAW,EACX,GAAG,EACH,QAAQ,EACR,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,EACN,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACxB,2BAA2B,EAC3B,UAAU,EACV,yBAAyB,EACzB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACX,eAAe,EACf,QAAQ,EACR,GAAG,EACH,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACN,GAAG,EACH,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,KAAK,EACL,cAAc,EACd,UAAU,EACV,KAAK,EACL,sBAAsB,EACtB,aAAa,EACb,UAAU,EACV,eAAe,EACf,eAAe,EACf,OAAO,EACP,aAAa,EACb,WAAW,EACX,GAAG,EACH,YAAY,EACZ,QAAQ,EACR,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,EACN,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,6BAA6B,EAC7B,wBAAwB,EACxB,2BAA2B,EAC3B,kCAAkC,EAClC,UAAU,EACV,yBAAyB,EACzB,UAAU,EACV,aAAa,EACb,YAAY,EACZ,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EACX,eAAe,EACf,QAAQ,EACR,GAAG,EACH,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
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
|
|
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 y=(J,Z)=>()=>(J&&(Z=J(J=0)),Z);var G;var C=y(()=>{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 l={};i(l,{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=y(()=>{C()});var b={};i(b,{validateTableForSharding:()=>h,schemaExists:()=>n,migrateRecord:()=>e,listTables:()=>k,integrateExistingDatabase:()=>ZJ,dropSchema:()=>a,discoverExistingRecordsWithColumns:()=>g,discoverExistingPrimaryKeys:()=>c,createSchemaAcrossShards:()=>r,createSchema:()=>d,createMappingsForExistingKeys:()=>JJ,clearShardMigrationCache:()=>WJ,clearMigrationCache:()=>VJ,checkMigrationNeeded:()=>QJ,autoDetectAndMigrate:()=>$J});async function d(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 d(Q,Z).catch((W)=>{throw new G(`Failed to create schema on shard ${V}: ${W.message}`,"SCHEMA_CREATION_FAILED")})});await Promise.all($)}async function n(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 k(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 n(Z,V))await d(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 g(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 h(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 k(J)).filter((X)=>X!=="shard_mappings");for(let X of S)try{let I=await h(J,X,W);if(!I.isValid){Y.push(`Table ${X}: ${I.issues.join(", ")}`);continue}if(j){let v=await g(J,X,W);if(v.length===0){Y.push(`Table ${X} has no records to process`);continue}if(!U)for(let L of v){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+=v.length}else{let v=await c(J,X,W);if(v.length===0){Y.push(`Table ${X} has no records to process`);continue}if(!U)for(let L of v)await $.setShardMapping(L,Z),H++;F+=v.length}x++}catch(I){Y.push(`Failed to process table ${X}: ${I}`)}if(z&&!U){if(!(await k(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(!U)await $.addKnownShard(Z)}catch(
|
|
7
|
+
);`.trim()).run()}if(!U)await $.addKnownShard(Z)}catch(D){Y.push(`Integration failed: ${D}`)}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,D=!1;try{let{KVShardMapper:S}=await Promise.resolve().then(() => (f(),l)),X=new S($.kv,{hashShardMappings:$.hashShardMappings}),I=await k(J),v=W||I.filter((L)=>L!=="shard_mappings"&&!L.startsWith("sqlite_")&&L!=="sqlite_sequence");if(v.length===0)return q.set(j,!0),{migrationNeeded:!1,migrationPerformed:!1,recordsMigrated:0,tablesProcessed:0,issues:[]};for(let L of v)try{let R=await h(J,L,Q);if(!R.isValid||R.recordCount===0)continue;let P=Math.min(z,R.recordCount),YJ=await J.prepare(`
|
|
8
8
|
SELECT ${Q} FROM ${L}
|
|
9
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)`)
|
|
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($.debug)console.log(`Auto-migrating table ${L} in shard ${Z} (${R.recordCount} records)`);if(U){let N=await g(J,L,Q),_=0;for(let T of N){let m=String(T[Q]);if(!await X.getShardMapping(m)){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(m,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+=_}if(F++,D=!0,$.debug)console.log(`Auto-migrated ${x} records from table ${L}`)}}catch(R){Y.push(`Auto-migration failed for table ${L}: ${R}`)}if(D){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(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};
|
|
16
|
+
`).run()}if(q.set(j,!0),D&&$.debug)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:D,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 k(J);if(Q.includes("shard_mappings"))return q.set(V,!0),!1;let{KVShardMapper:O}=await Promise.resolve().then(() => (f(),l)),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=y(()=>{C();q=new Map});C();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(J.debug)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 A(){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,D=F-H*0.1;return{shard:Y,score:D,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 $=A(),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 $=A(),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 vJ(J,Z){let{createSchema:$}=await Promise.resolve().then(() => (M(),b));await $(J,Z)}async function p(J,Z){let $=LJ(Z);return(await DJ(J,$)).prepare(Z)}async function BJ(J,Z,$=[]){let Q=await(await p(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 p(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 p(J,Z)).bind(...$).first()}async function IJ(J,Z,$){let V=A();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=A();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=A();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=A().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 CJ(J,Z,$=[]){let Q=A().shards[J];if(!Q)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");return await Q.prepare(Z).bind(...$).all()}async function kJ(J,Z,$=[]){let Q=A().shards[J];if(!Q)throw new G(`Shard ${J} not found`,"SHARD_NOT_FOUND");return await Q.prepare(Z).bind(...$).first()}async function MJ(J,Z=[],$=50){let V=A(),Q=[];for(let[O,z]of Object.entries(V.shards))try{let U=z.prepare(J).bind(...Z).all().catch((j)=>{return console.error(`Error executing query on shard ${O}:`,j),{success:!1,results:[],meta:{count:0,duration:0}}});Q.push(U)}catch(U){console.error(`Error running on shard ${O}:`,U)}let W=[];for(let O=0;O<Q.length;O+=$)W.push(...await Promise.all(Q.slice(O,O+$)));return W}async function SJ(J,Z=[],$=50){let V=A(),Q=[];for(let[O,z]of Object.entries(V.shards))try{let U=z.prepare(J).bind(...Z).all().catch((j)=>{return console.error(`Error executing query on shard ${O}:`,j),{success:!1,results:[],meta:{count:0,duration:0}}});Q.push(U)}catch(U){console.error(`Error running on shard ${O}:`,U)}let W=[];for(let O=0;O<Q.length;O+=$)W.push(...await Promise.all(Q.slice(O,O+$)));return W}async function NJ(J,Z=[],$=50){let V=A(),Q=[];for(let[O,z]of Object.entries(V.shards))try{let U=z.prepare(J).bind(...Z).first().catch((j)=>{return console.error(`Error executing query on shard ${O}:`,j),null});Q.push(U)}catch(U){console.error(`Error running on shard ${O}:`,U)}let W=[];for(let O=0;O<Q.length;O+=$)W.push(...await Promise.all(Q.slice(O,O+$)));return W}async function fJ(){let J=A();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:",$)}}C();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 $}C();f();M();export{h as validateTableForSharding,n as schemaExists,PJ as runShard,MJ as runAllShards,BJ as run,GJ as resetConfig,IJ as reassignShard,p as prepare,e as migrateRecord,k as listTables,_J as listKnownShards,ZJ as integrateExistingDatabase,OJ as initializeAsync,xJ as initialize,qJ as getShardStats,TJ as getClosestRegionFromIP,fJ as flush,kJ as firstShard,NJ as firstAllShards,EJ as first,a as dropSchema,g as discoverExistingRecordsWithColumns,c as discoverExistingPrimaryKeys,r as createSchemaAcrossShards,vJ as createSchema,JJ as createMappingsForExistingKeys,FJ as collegedb,WJ as clearShardMigrationCache,VJ as clearMigrationCache,QJ as checkMigrationNeeded,$J as autoDetectAndMigrate,CJ as allShard,SJ as allAllShards,RJ as all,o as ShardCoordinator,E as KVShardMapper,G as CollegeDBError};
|
|
17
17
|
|
|
18
|
-
//# debugId=
|
|
18
|
+
//# debugId=AE70033A5549504A64756E2164756E21
|
|
19
19
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @fileoverview Custom error class for CollegeDB operations\n *\n * This module provides the CollegeDBError class that extends the native Error class\n * to provide more specific error information for CollegeDB operations. This allows\n * for better error handling and debugging throughout the application.\n *\n * @example\n * ```typescript\n * import { CollegeDBError } from './errors.js';\n *\n * throw new CollegeDBError('Failed to allocate shard', 'SHARD_ALLOCATION_ERROR');\n * ```\n *\n * @author CollegeDB Team\n * @since 1.0.2\n */\n\n/**\n * Custom error class for CollegeDB operations\n *\n * Extends the native Error class to provide more specific error information\n * for CollegeDB operations. Includes an optional error code for better\n * error categorization and handling.\n *\n * @example\n * ```typescript\n * try {\n * await getShardForKey('invalid-key');\n * } catch (error) {\n * if (error instanceof CollegeDBError) {\n * console.error(`CollegeDB Error (${error.code}): ${error.message}`);\n * }\n * }\n * ```\n */\nexport class CollegeDBError extends Error {\n\t/**\n\t * Optional error code for categorizing different types of errors\n\t */\n\tpublic readonly code?: string;\n\n\t/**\n\t * Creates a new CollegeDBError instance\n\t * @param message - Error message describing what went wrong\n\t * @param code - Optional error code for categorization\n\t * @example\n\t * ```typescript\n\t * throw new CollegeDBError('Shard not found', 'SHARD_NOT_FOUND');\n\t * ```\n\t */\n\tconstructor(message: string, code?: string) {\n\t\tsuper(message);\n\t\tthis.name = 'CollegeDBError';\n\t\tthis.code = code;\n\n\t\t// Maintains proper stack trace for where our error was thrown (only available on V8)\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, CollegeDBError);\n\t\t}\n\t}\n}\n",
|
|
6
6
|
"/**\n * @fileoverview KV-based shard mapping implementation for CollegeDB\n *\n * This module provides the KVShardMapper class that uses Cloudflare KV storage\n * to maintain mappings between primary keys and their assigned D1 database shards.\n * It handles the persistence and retrieval of shard assignments, enabling the\n * database router to consistently route queries to the correct shard.\n *\n * Key features:\n * - Primary key to shard mapping storage and retrieval\n * - Multiple lookup keys for the same shard mapping (e.g., username, email, id)\n * - SHA-256 hashing of keys for security and privacy (enabled by default)\n * - Shard discovery and management\n * - Key counting and statistics for load balancing\n * - Batch operations for efficient KV usage\n *\n * @example\n * ```typescript\n * const mapper = new KVShardMapper(env.KV, { hashShardMappings: true });\n *\n * // Set a mapping with multiple lookup keys\n * await mapper.setShardMapping('user-123', 'db-east', ['username:john', 'email:john@example.com', 'id:123']);\n *\n * // Get a mapping by any of the keys\n * const mapping = await mapper.getShardMapping('email:john@example.com');\n * console.log(mapping?.shard); // 'db-east'\n *\n * // Get statistics\n * const counts = await mapper.getShardKeyCounts();\n * console.log(counts); // { 'db-east': 42, 'db-west': 38 }\n * ```\n *\n * @author Gregory Mitchell\n * @since 1.0.0\n */\n\nimport type { KVNamespace } from '@cloudflare/workers-types';\nimport { CollegeDBError } from './errors.js';\nimport type { CollegeDBConfig, MultiKeyShardMapping, ShardMapping } from './types.js';\n\n/**\n * KV key prefix for shard mappings\n *\n * All primary key to shard mappings are stored with this prefix to namespace\n * them from other data in the KV store and enable efficient prefix-based queries.\n *\n * @constant\n * @private\n */\nconst SHARD_MAPPING_PREFIX = 'shard:';\n\n/**\n * KV key prefix for multi-key shard mappings\n *\n * Multi-key mappings store the primary mapping data and list of all associated keys.\n *\n * @constant\n * @private\n * @since 1.0.3\n */\nconst MULTI_KEY_MAPPING_PREFIX = 'multikey:';\n\n/**\n * KV key for storing the list of known shards\n *\n * A single key that stores the JSON array of all known shard binding names.\n * This provides a quick way to discover available shards without scanning\n * all mapping entries.\n *\n * @constant\n * @private\n */\nconst KNOWN_SHARDS_KEY = 'known_shards';\n\n/**\n * The KVShardMapper class provides a persistent storage layer for mapping\n * primary keys to their assigned D1 database shards. It uses Cloudflare KV\n * for global, eventually consistent storage with low latency reads.\n *\n * Features:\n * - CRUD operations for shard mappings\n * - Multiple lookup keys for the same shard (username, email, id, etc.)\n * - SHA-256 hashing of keys for security and privacy\n * - Atomic updates with timestamp tracking\n * - Efficient bulk operations and statistics\n * - Prefix-based key organization for performance\n *\n * @example\n * ```typescript\n * const mapper = new KVShardMapper(env.KV, { hashShardMappings: true });\n *\n * // Create a new mapping with multiple lookup keys\n * await mapper.setShardMapping('primary-key-123', 'db-central', ['username:john', 'email:john@example.com']);\n *\n * // Update an existing mapping\n * await mapper.updateShardMapping('username:john', 'db-west');\n *\n * // Query mapping by any key\n * const mapping = await mapper.getShardMapping('email:john@example.com');\n * if (mapping) {\n * console.log(`User is on ${mapping.shard}`);\n * }\n * ```\n */\nexport class KVShardMapper {\n\t/**\n\t * Cloudflare KV namespace for storing mappings\n\t * @readonly\n\t */\n\tprivate readonly kv: KVNamespace;\n\n\t/**\n\t * Whether to hash mapping keys with SHA-256\n\t * @readonly\n\t */\n\tprivate readonly hashKeys: boolean;\n\n\t/**\n\t * Creates a new KVShardMapper instance\n\t * @param kv - Cloudflare KV namespace\n\t * @param config - Configuration options including hashing preference\n\t */\n\tconstructor(kv: KVNamespace, config: Partial<Pick<CollegeDBConfig, 'hashShardMappings'>> = {}) {\n\t\tthis.kv = kv;\n\t\tthis.hashKeys = config.hashShardMappings ?? true; // Default to true for security\n\t}\n\n\t/**\n\t * Hashes a key using SHA-256 if hashing is enabled\n\t * @private\n\t * @param key - The key to hash\n\t * @returns The hashed key or original key if hashing is disabled\n\t */\n\tprivate async hashKey(key: string): Promise<string> {\n\t\tif (!this.hashKeys) {\n\t\t\treturn key;\n\t\t}\n\n\t\tconst encoder = new TextEncoder();\n\t\tconst data = encoder.encode(key);\n\t\tconst hashBuffer = await crypto.subtle.digest('SHA-256', data);\n\t\tconst hashArray = new Uint8Array(hashBuffer);\n\t\tconst hashHex = Array.from(hashArray)\n\t\t\t.map((b) => b.toString(16).padStart(2, '0'))\n\t\t\t.join('');\n\t\treturn hashHex;\n\t}\n\n\t/**\n\t * Retrieves the shard assignment for a given primary key from KV storage.\n\t * Returns null if no mapping exists, indicating the key has not been\n\t * assigned to any shard yet. Supports both single-key and multi-key lookups.\n\t * @param primaryKey - The primary key to look up (will be hashed if hashing is enabled)\n\t * @returns Promise resolving to the shard mapping or null if not found\n\t * @throws {Error} If KV read operation fails\n\t * @example\n\t * ```typescript\n\t * const mapping = await mapper.getShardMapping('email:user@example.com');\n\t * if (mapping) {\n\t * console.log(`User is on shard: ${mapping.shard}`);\n\t * console.log(`Created: ${new Date(mapping.createdAt)}`);\n\t * } else {\n\t * console.log('User not yet assigned to any shard');\n\t * }\n\t * ```\n\t */\n\tasync getShardMapping(primaryKey: string): Promise<ShardMapping | null> {\n\t\tconst hashedKey = await this.hashKey(primaryKey);\n\t\tconst key = `${SHARD_MAPPING_PREFIX}${hashedKey}`;\n\n\t\t// Try single-key mapping first\n\t\tconst singleMapping = await this.kv.get<ShardMapping>(key, 'json');\n\t\tif (singleMapping) {\n\t\t\treturn singleMapping;\n\t\t}\n\n\t\t// Try multi-key mapping lookup\n\t\tconst multiKeyMapping = await this.kv.get<MultiKeyShardMapping>(`${MULTI_KEY_MAPPING_PREFIX}${hashedKey}`, 'json');\n\t\tif (multiKeyMapping) {\n\t\t\t// Convert multi-key mapping to single mapping format for compatibility\n\t\t\treturn {\n\t\t\t\tshard: multiKeyMapping.shard,\n\t\t\t\tcreatedAt: multiKeyMapping.createdAt,\n\t\t\t\tupdatedAt: multiKeyMapping.updatedAt,\n\t\t\t\toriginalKey: this.hashKeys ? undefined : primaryKey\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Creates a new shard assignment for a primary key. This is typically used\n\t * when a new primary key is first encountered and needs to be assigned to\n\t * a shard. Sets both created and updated timestamps to the current time.\n\t *\n\t * **Note**: This will overwrite any existing mapping for the same key.\n\t * Use updateShardMapping() if you want to preserve creation timestamp.\n\t * @param primaryKey - The primary key to map\n\t * @param shard - The shard binding name to assign\n\t * @param additionalKeys - Optional array of additional lookup keys for the same mapping\n\t * @returns Promise that resolves when the mapping is stored\n\t * @throws {Error} If KV write operation fails\n\t * @example\n\t * ```typescript\n\t * // Assign a new user to the west coast shard with multiple lookup keys\n\t * await mapper.setShardMapping('user-123', 'db-west', ['username:john', 'email:john@example.com']);\n\t * ```\n\t */\n\tasync setShardMapping(primaryKey: string, shard: string, additionalKeys: string[] = []): Promise<void> {\n\t\tconst allKeys = [primaryKey, ...additionalKeys];\n\t\tconst timestamp = Date.now();\n\n\t\tif (allKeys.length === 1) {\n\t\t\t// Single key mapping - use the original format for backward compatibility\n\t\t\tconst hashedKey = await this.hashKey(primaryKey);\n\t\t\tconst key = `${SHARD_MAPPING_PREFIX}${hashedKey}`;\n\t\t\tconst mapping: ShardMapping = {\n\t\t\t\tshard,\n\t\t\t\tcreatedAt: timestamp,\n\t\t\t\tupdatedAt: timestamp,\n\t\t\t\toriginalKey: this.hashKeys ? undefined : primaryKey\n\t\t\t};\n\n\t\t\tawait this.kv.put(key, JSON.stringify(mapping));\n\t\t} else {\n\t\t\t// Multi-key mapping - store the primary mapping and create lookup entries\n\t\t\tconst primaryHashedKey = await this.hashKey(primaryKey);\n\t\t\tconst primaryMappingKey = `${MULTI_KEY_MAPPING_PREFIX}${primaryHashedKey}`;\n\n\t\t\tconst multiKeyMapping: MultiKeyShardMapping = {\n\t\t\t\tshard,\n\t\t\t\tcreatedAt: timestamp,\n\t\t\t\tupdatedAt: timestamp,\n\t\t\t\tkeys: this.hashKeys ? [] : allKeys // Only store original keys if hashing is disabled\n\t\t\t};\n\n\t\t\t// Store the primary multi-key mapping\n\t\t\tawait this.kv.put(primaryMappingKey, JSON.stringify(multiKeyMapping));\n\n\t\t\t// Create lookup entries for all keys pointing to the primary mapping\n\t\t\tconst lookupPromises = allKeys.map(async (lookupKey) => {\n\t\t\t\tconst hashedLookupKey = await this.hashKey(lookupKey);\n\t\t\t\tconst lookupMappingKey = `${SHARD_MAPPING_PREFIX}${hashedLookupKey}`;\n\t\t\t\tconst lookupMapping: ShardMapping = {\n\t\t\t\t\tshard,\n\t\t\t\t\tcreatedAt: timestamp,\n\t\t\t\t\tupdatedAt: timestamp,\n\t\t\t\t\toriginalKey: this.hashKeys ? undefined : lookupKey\n\t\t\t\t};\n\t\t\t\treturn this.kv.put(lookupMappingKey, JSON.stringify(lookupMapping));\n\t\t\t});\n\n\t\t\tawait Promise.all(lookupPromises);\n\t\t}\n\t}\n\n\t/**\n\t * Changes the shard assignment for a primary key that already has a mapping.\n\t * Preserves the original creation timestamp while updating the modified\n\t * timestamp. Throws an error if no existing mapping is found.\n\t *\n\t * This is typically used during shard rebalancing or data migration operations.\n\t * Works with both single-key and multi-key mappings.\n\t * @param primaryKey - The primary key to update\n\t * @param newShard - The new shard binding name to assign\n\t * @returns Promise that resolves when the mapping is updated\n\t * @throws {Error} If no existing mapping found or KV operation fails\n\t * @example\n\t * ```typescript\n\t * try {\n\t * // Move user to a different shard for rebalancing\n\t * await mapper.updateShardMapping('email:user@example.com', 'db-central');\n\t * console.log('User successfully moved to central shard');\n\t * } catch (error) {\n\t * console.error('Failed to update mapping:', error.message);\n\t * }\n\t * ```\n\t */\n\tasync updateShardMapping(primaryKey: string, newShard: string): Promise<void> {\n\t\tconst existing = await this.getShardMapping(primaryKey);\n\t\tif (!existing) {\n\t\t\tthrow new CollegeDBError(`No existing mapping found for primary key: ${primaryKey}`, 'MAPPING_NOT_FOUND');\n\t\t}\n\n\t\tconst hashedKey = await this.hashKey(primaryKey);\n\t\tconst singleMappingKey = `${SHARD_MAPPING_PREFIX}${hashedKey}`;\n\t\tconst multiKeyMappingKey = `${MULTI_KEY_MAPPING_PREFIX}${hashedKey}`;\n\n\t\t// Check if this is a multi-key mapping\n\t\tconst multiKeyMapping = await this.kv.get<MultiKeyShardMapping>(multiKeyMappingKey, 'json');\n\n\t\tif (multiKeyMapping) {\n\t\t\t// Update multi-key mapping\n\t\t\tconst updatedMultiKeyMapping: MultiKeyShardMapping = {\n\t\t\t\t...multiKeyMapping,\n\t\t\t\tshard: newShard,\n\t\t\t\tupdatedAt: Date.now()\n\t\t\t};\n\t\t\tawait this.kv.put(multiKeyMappingKey, JSON.stringify(updatedMultiKeyMapping));\n\n\t\t\t// Update all lookup entries\n\t\t\tconst lookupPromises = multiKeyMapping.keys.map(async (lookupKey) => {\n\t\t\t\tconst hashedLookupKey = await this.hashKey(lookupKey);\n\t\t\t\tconst lookupMappingKey = `${SHARD_MAPPING_PREFIX}${hashedLookupKey}`;\n\t\t\t\tconst lookupMapping: ShardMapping = {\n\t\t\t\t\t...existing,\n\t\t\t\t\tshard: newShard,\n\t\t\t\t\tupdatedAt: Date.now()\n\t\t\t\t};\n\t\t\t\treturn this.kv.put(lookupMappingKey, JSON.stringify(lookupMapping));\n\t\t\t});\n\n\t\t\tawait Promise.all(lookupPromises);\n\t\t} else {\n\t\t\t// Update single-key mapping\n\t\t\tconst updatedMapping: ShardMapping = {\n\t\t\t\t...existing,\n\t\t\t\tshard: newShard,\n\t\t\t\tupdatedAt: Date.now()\n\t\t\t};\n\t\t\tawait this.kv.put(singleMappingKey, JSON.stringify(updatedMapping));\n\t\t}\n\t}\n\n\t/**\n\t * Completely removes the shard assignment for a primary key from KV storage.\n\t * This is typically used when data is being permanently deleted or when\n\t * cleaning up orphaned mappings. Handles both single-key and multi-key mappings.\n\t *\n\t * **WARNING**: After deletion, the primary key will be treated as new\n\t * and may be assigned to a different shard on next access.\n\t *\n\t * @param primaryKey - The primary key mapping to remove\n\t * @returns Promise that resolves when the mapping is deleted\n\t * @throws {Error} If KV delete operation fails\n\t * @example\n\t * ```typescript\n\t * // Remove mapping for deleted user\n\t * await mapper.deleteShardMapping('email:deleted@example.com');\n\t * console.log('Mapping removed for deleted user');\n\t * ```\n\t */\n\tasync deleteShardMapping(primaryKey: string): Promise<void> {\n\t\tconst hashedKey = await this.hashKey(primaryKey);\n\t\tconst singleMappingKey = `${SHARD_MAPPING_PREFIX}${hashedKey}`;\n\t\tconst multiKeyMappingKey = `${MULTI_KEY_MAPPING_PREFIX}${hashedKey}`;\n\n\t\t// Check if this is a multi-key mapping\n\t\tconst multiKeyMapping = await this.kv.get<MultiKeyShardMapping>(multiKeyMappingKey, 'json');\n\n\t\tif (multiKeyMapping) {\n\t\t\t// Delete multi-key mapping\n\t\t\tawait this.kv.delete(multiKeyMappingKey);\n\n\t\t\t// Delete all lookup entries\n\t\t\tconst deletePromises = multiKeyMapping.keys.map(async (lookupKey) => {\n\t\t\t\tconst hashedLookupKey = await this.hashKey(lookupKey);\n\t\t\t\tconst lookupMappingKey = `${SHARD_MAPPING_PREFIX}${hashedLookupKey}`;\n\t\t\t\treturn this.kv.delete(lookupMappingKey);\n\t\t\t});\n\n\t\t\tawait Promise.all(deletePromises);\n\t\t} else {\n\t\t\t// Delete single-key mapping\n\t\t\tawait this.kv.delete(singleMappingKey);\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves the list of all shard binding names that have been registered\n\t * with the system. This is maintained separately from the individual mappings\n\t * for efficient shard discovery.\n\t *\n\t * @returns Promise resolving to array of shard binding names\n\t * @throws {Error} If KV read operation fails\n\t * @example\n\t * ```typescript\n\t * const shards = await mapper.getKnownShards();\n\t * console.log('Available shards:', shards);\n\t * // Output: ['db-east', 'db-west', 'db-central']\n\t * ```\n\t */\n\tasync getKnownShards(): Promise<string[]> {\n\t\tconst shards = await this.kv.get<string[]>(KNOWN_SHARDS_KEY, 'json');\n\t\treturn shards || [];\n\t}\n\n\t/**\n\t * Replaces the entire list of known shards with a new list. This is typically\n\t * used during system initialization or when shards are added/removed in bulk.\n\t *\n\t * @param shards - Array of shard binding names to store\n\t * @returns Promise that resolves when the list is updated\n\t * @throws {Error} If KV write operation fails\n\t * @example\n\t * ```typescript\n\t * // Update shard list after adding new regions\n\t * await mapper.setKnownShards(['db-east', 'db-west', 'db-central', 'db-asia']);\n\t * ```\n\t */\n\tasync setKnownShards(shards: string[]): Promise<void> {\n\t\tif (!shards || shards.length === 0) return;\n\t\tawait this.kv.put(KNOWN_SHARDS_KEY, JSON.stringify(shards));\n\t}\n\n\t/**\n\t * Appends a new shard to the list of known shards if it's not already present.\n\t * This operation is idempotent - adding the same shard multiple times has no effect.\n\t *\n\t * @param shard - The shard binding name to add\n\t * @returns Promise that resolves when the shard is added\n\t * @throws {Error} If KV operations fail\n\t * @example\n\t * ```typescript\n\t * // Register a new shard when it comes online\n\t * await mapper.addKnownShard('db-europe');\n\t * console.log('European shard registered');\n\t * ```\n\t */\n\tasync addKnownShard(shard: string): Promise<void> {\n\t\tif (!shard) return;\n\n\t\tconst knownShards = await this.getKnownShards();\n\t\tif (!knownShards.includes(shard)) {\n\t\t\tknownShards.push(shard);\n\t\t\tawait this.setKnownShards(knownShards);\n\t\t}\n\t}\n\n\t/**\n\t * Scans all shard mappings to find primary keys assigned to the specified shard.\n\t * This operation requires reading all mappings and can be expensive for large\n\t * datasets. Consider caching results or using getShardKeyCounts() for statistics.\n\t *\n\t * @param shard - The shard binding name to search for\n\t * @returns Promise resolving to array of primary keys assigned to the shard\n\t * @throws {Error} If KV operations fail\n\t * @example\n\t * ```typescript\n\t * // Find all users on the east coast shard\n\t * const eastCoastUsers = await mapper.getKeysForShard('db-east');\n\t * console.log(`East coast has ${eastCoastUsers.length} users`);\n\t * ```\n\t */\n\tasync getKeysForShard(shard: string): Promise<string[]> {\n\t\tconst keys: string[] = [];\n\n\t\t// Scan single-key mappings\n\t\tconst singleKeyList = await this.kv.list({ prefix: SHARD_MAPPING_PREFIX });\n\t\tfor (const kvKey of singleKeyList.keys) {\n\t\t\tconst mapping = await this.kv.get<ShardMapping>(kvKey.name, 'json');\n\t\t\tif (mapping?.shard === shard) {\n\t\t\t\tconst originalKey = kvKey.name.replace(SHARD_MAPPING_PREFIX, '');\n\t\t\t\t// If hashing is enabled, we can't recover the original key\n\t\t\t\tif (mapping.originalKey) {\n\t\t\t\t\tkeys.push(mapping.originalKey);\n\t\t\t\t} else if (!this.hashKeys) {\n\t\t\t\t\tkeys.push(originalKey);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Scan multi-key mappings\n\t\tconst multiKeyList = await this.kv.list({ prefix: MULTI_KEY_MAPPING_PREFIX });\n\t\tfor (const kvKey of multiKeyList.keys) {\n\t\t\tconst mapping = await this.kv.get<MultiKeyShardMapping>(kvKey.name, 'json');\n\t\t\tif (mapping?.shard === shard) {\n\t\t\t\t// Add all keys from this multi-key mapping\n\t\t\t\tkeys.push(...mapping.keys);\n\t\t\t}\n\t\t}\n\n\t\treturn [...new Set(keys)]; // Remove duplicates\n\t}\n\n\t/**\n\t * Scans all shard mappings to count how many primary keys are assigned to\n\t * each shard. Returns a mapping of shard names to their key counts. This\n\t * is useful for load balancing and monitoring shard utilization.\n\t *\n\t * **Performance Note**: This operation scans all mappings and can be\n\t * expensive for large datasets. Consider implementing caching for frequently\n\t * accessed statistics.\n\t *\n\t * @returns Promise resolving to object mapping shard names to key counts\n\t * @throws {Error} If KV operations fail\n\t * @example\n\t * ```typescript\n\t * const counts = await mapper.getShardKeyCounts();\n\t * console.log('Shard utilization:', counts);\n\t * // Output: { 'db-east': 1247, 'db-west': 982, 'db-central': 1156 }\n\t *\n\t * // Find the least loaded shard\n\t * const leastLoaded = Object.entries(counts)\n\t * .sort(([,a], [,b]) => a - b)[0][0];\n\t * console.log('Least loaded shard:', leastLoaded);\n\t * ```\n\t */\n\tasync getShardKeyCounts(): Promise<Record<string, number>> {\n\t\tconst counts: Record<string, number> = {};\n\n\t\t// Count single-key mappings\n\t\tconst singleKeyList = await this.kv.list({ prefix: SHARD_MAPPING_PREFIX });\n\t\tfor (const kvKey of singleKeyList.keys) {\n\t\t\tconst mapping = await this.kv.get<ShardMapping>(kvKey.name, 'json');\n\t\t\tif (mapping) {\n\t\t\t\tcounts[mapping.shard] = (counts[mapping.shard] || 0) + 1;\n\t\t\t}\n\t\t}\n\n\t\t// Count multi-key mappings\n\t\tconst multiKeyList = await this.kv.list({ prefix: MULTI_KEY_MAPPING_PREFIX });\n\t\tfor (const kvKey of multiKeyList.keys) {\n\t\t\tconst mapping = await this.kv.get<MultiKeyShardMapping>(kvKey.name, 'json');\n\t\t\tif (mapping) {\n\t\t\t\t// Each multi-key mapping represents multiple keys, so count them all\n\t\t\t\tcounts[mapping.shard] = (counts[mapping.shard] || 0) + mapping.keys.length;\n\t\t\t}\n\t\t}\n\n\t\treturn counts;\n\t}\n\n\t/**\n\t * Deletes ALL shard mappings from KV storage. This is a destructive operation\n\t * that removes all primary key assignments. After this operation, all keys\n\t * will be treated as new and may be assigned to different shards.\n\t *\n\t * **DANGER**: This operation is irreversible and will cause data routing\n\t * issues if used in production. Only use during development, testing, or\n\t * complete system resets.\n\t *\n\t * @returns Promise that resolves when all mappings are deleted\n\t * @throws {Error} If KV operations fail\n\t * @example\n\t * ```typescript\n\t * // Only use in development/testing!\n\t * if (process.env.NODE_ENV === 'development') {\n\t * await mapper.clearAllMappings();\n\t * console.log('All mappings cleared for testing');\n\t * }\n\t * ```\n\t */\n\tasync clearAllMappings(): Promise<void> {\n\t\t// Clear single-key mappings\n\t\tconst singleKeyList = await this.kv.list({ prefix: SHARD_MAPPING_PREFIX });\n\t\tconst singleKeyPromises = singleKeyList.keys.map((key) => this.kv.delete(key.name));\n\n\t\t// Clear multi-key mappings\n\t\tconst multiKeyList = await this.kv.list({ prefix: MULTI_KEY_MAPPING_PREFIX });\n\t\tconst multiKeyPromises = multiKeyList.keys.map((key) => this.kv.delete(key.name));\n\n\t\tawait Promise.all([...singleKeyPromises, ...multiKeyPromises]);\n\t}\n\n\t/**\n\t * Adds additional lookup keys to an existing shard mapping. This allows you to\n\t * query the same shard mapping using multiple identifiers (e.g., username, email, id).\n\t *\n\t * @param primaryKey - An existing key in the mapping\n\t * @param additionalKeys - New keys to add for lookup\n\t * @returns Promise that resolves when the additional keys are added\n\t * @throws {Error} If no existing mapping found or KV operations fail\n\t * @example\n\t * ```typescript\n\t * // Add email lookup to an existing user mapping\n\t * await mapper.addLookupKeys('user-123', ['email:user@example.com']);\n\t * ```\n\t * @since 1.0.3\n\t */\n\tasync addLookupKeys(primaryKey: string, additionalKeys: string[]): Promise<void> {\n\t\tconst existing = await this.getShardMapping(primaryKey);\n\t\tif (!existing) {\n\t\t\tthrow new CollegeDBError(`No existing mapping found for primary key: ${primaryKey}`, 'MAPPING_NOT_FOUND');\n\t\t}\n\n\t\t// Create a new multi-key mapping or add to existing one\n\t\tconst hashedPrimaryKey = await this.hashKey(primaryKey);\n\t\tconst multiKeyMappingKey = `${MULTI_KEY_MAPPING_PREFIX}${hashedPrimaryKey}`;\n\t\tlet multiKeyMapping = await this.kv.get<MultiKeyShardMapping>(multiKeyMappingKey, 'json');\n\n\t\tconst allKeys = [primaryKey, ...additionalKeys];\n\t\tconst timestamp = Date.now();\n\n\t\tif (!multiKeyMapping) {\n\t\t\t// Convert single-key to multi-key mapping\n\t\t\tmultiKeyMapping = {\n\t\t\t\tshard: existing.shard,\n\t\t\t\tcreatedAt: existing.createdAt,\n\t\t\t\tupdatedAt: timestamp,\n\t\t\t\tkeys: this.hashKeys ? [] : allKeys\n\t\t\t};\n\t\t} else {\n\t\t\t// Add to existing multi-key mapping\n\t\t\tmultiKeyMapping = {\n\t\t\t\t...multiKeyMapping,\n\t\t\t\tupdatedAt: timestamp,\n\t\t\t\tkeys: this.hashKeys ? [] : [...new Set([...multiKeyMapping.keys, ...allKeys])]\n\t\t\t};\n\t\t}\n\n\t\t// Store the updated multi-key mapping\n\t\tawait this.kv.put(multiKeyMappingKey, JSON.stringify(multiKeyMapping));\n\n\t\t// Create lookup entries for the new additional keys\n\t\tconst lookupPromises = additionalKeys.map(async (lookupKey) => {\n\t\t\tconst hashedLookupKey = await this.hashKey(lookupKey);\n\t\t\tconst lookupMappingKey = `${SHARD_MAPPING_PREFIX}${hashedLookupKey}`;\n\t\t\tconst lookupMapping: ShardMapping = {\n\t\t\t\tshard: existing.shard,\n\t\t\t\tcreatedAt: existing.createdAt,\n\t\t\t\tupdatedAt: timestamp,\n\t\t\t\toriginalKey: this.hashKeys ? undefined : lookupKey\n\t\t\t};\n\t\t\treturn this.kv.put(lookupMappingKey, JSON.stringify(lookupMapping));\n\t\t});\n\n\t\tawait Promise.all(lookupPromises);\n\t}\n\n\t/**\n\t * Gets all lookup keys associated with a shard mapping. This is useful for\n\t * understanding what keys resolve to the same shard.\n\t *\n\t * @param primaryKey - Any key in the mapping\n\t * @returns Promise resolving to array of all keys in the mapping\n\t * @throws {Error} If no existing mapping found\n\t * @example\n\t * ```typescript\n\t * const allKeys = await mapper.getAllLookupKeys('email:user@example.com');\n\t * console.log(allKeys); // ['user-123', 'username:john', 'email:user@example.com']\n\t * ```\n\t * @since 1.0.3\n\t */\n\tasync getAllLookupKeys(primaryKey: string): Promise<string[]> {\n\t\tconst hashedKey = await this.hashKey(primaryKey);\n\t\tconst multiKeyMappingKey = `${MULTI_KEY_MAPPING_PREFIX}${hashedKey}`;\n\n\t\t// Check if this is a multi-key mapping\n\t\tconst multiKeyMapping = await this.kv.get<MultiKeyShardMapping>(multiKeyMappingKey, 'json');\n\t\tif (multiKeyMapping) {\n\t\t\treturn multiKeyMapping.keys;\n\t\t}\n\n\t\t// If not a multi-key mapping, check if single-key mapping exists\n\t\tconst singleMapping = await this.getShardMapping(primaryKey);\n\t\tif (singleMapping) {\n\t\t\treturn singleMapping.originalKey ? [singleMapping.originalKey] : [primaryKey];\n\t\t}\n\n\t\tthrow new CollegeDBError(`No mapping found for key: ${primaryKey}`, 'MAPPING_NOT_FOUND');\n\t}\n}\n",
|
|
7
|
-
"/**\n * @fileoverview Database schema management and data migration utilities for CollegeDB\n *\n * This module provides utilities for managing database schemas across multiple D1 shards\n * and migrating data between shards. It includes default schema definitions, schema\n * validation, and data migration functions that ensure consistency across the distributed\n * database system.\n *\n * Key features:\n * - Default schema creation for typical use cases\n * - Schema validation and existence checking\n * - Data migration between D1 database instances\n * - Batch schema operations across multiple shards\n * - Table discovery and management utilities\n *\n * @example\n * ```typescript\n * import { createSchema, migrateRecord, schemaExists } from './migrations.js';\n *\n * // Create schema on a new shard\n * await createSchema(env.DB_EAST);\n *\n * // Check if schema exists\n * const hasSchema = await schemaExists(env.DB_WEST);\n *\n * // Migrate a user from one shard to another\n * await migrateRecord(env.DB_EAST, env.DB_WEST, 'user-123', 'users');\n * ```\n *\n * @author Gregory Mitchell\n * @since 1.0.0\n */\n\nimport type { D1Database } from '@cloudflare/workers-types';\nimport { CollegeDBError } from './errors.js';\nimport type { KVShardMapper } from './kvmap.js';\nimport type { CollegeDBConfig, ShardingStrategy } from './types.js';\n\n/**\n * Cache for migration status to avoid repeated checks\n * @private\n */\nconst migrationStatusCache = new Map<string, boolean>();\n\n/**\n * Executes SQL statements to create the default table structure and indexes\n * in the specified D1 database. Supports custom schemas and handles SQL\n * statement parsing with comment filtering.\n *\n * The function:\n * 1. Splits the schema into individual SQL statements\n * 2. Filters out comments and empty statements\n * 3. Executes each statement using prepared statements\n * 4. Provides detailed error reporting on failures\n *\n * @param d1 - The D1 database instance to create schema in\n * @param schema - Schema SQL to use\n * @returns Promise that resolves when all schema statements are executed\n * @throws {Error} If any schema statement fails with detailed error information\n * @example\n * ```typescript\n * const sql = `\n * CREATE TABLE products (\n * id TEXT PRIMARY KEY,\n * name TEXT NOT NULL,\n * price REAL\n * );\n * `;\n * await createSchema(env.DB_PRODUCTS, sql);\n * ```\n */\nexport async function createSchema(d1: D1Database, schema: string): Promise<void> {\n\tconst statements = schema\n\t\t.split(';')\n\t\t.map((stmt) => stmt.trim())\n\t\t.filter((stmt) => stmt.length > 0 && !stmt.startsWith('--')); // Filter out comments\n\n\tfor (const statement of statements) {\n\t\ttry {\n\t\t\tawait d1.prepare(statement).run();\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to execute schema statement:', statement, error);\n\t\t\tthrow new CollegeDBError(`Schema migration failed: ${error}`, 'SCHEMA_MIGRATION_FAILED');\n\t\t}\n\t}\n}\n\n/**\n * Applies the schema to all provided D1 database instances in parallel.\n * This is useful for initializing a complete sharded database system\n * where all shards need the same table structure.\n *\n * The function executes schema creation on all shards concurrently for\n * performance, but provides detailed error reporting that identifies\n * which specific shard failed if any errors occur.\n *\n * @param shards - Record mapping shard names to D1 database instances\n * @param schema - Schema SQL to use\n * @returns Promise that resolves when schema is created on all shards\n * @throws {Error} If schema creation fails on any shard, with shard identification\n * @example\n * ```typescript\n * const shards = {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST,\n * 'db-central': env.DB_CENTRAL\n * };\n *\n * try {\n * await createSchemaAcrossShards(shards);\n * console.log('Schema created on all shards successfully');\n * } catch (error) {\n * console.error('Schema creation failed:', error.message);\n * // Error will specify which shard failed\n * }\n * ```\n */\nexport async function createSchemaAcrossShards(shards: Record<string, D1Database>, schema: string): Promise<void> {\n\tconst promises = Object.entries(shards).map(([shardName, db]) => {\n\t\treturn createSchema(db, schema).catch((error) => {\n\t\t\tthrow new CollegeDBError(`Failed to create schema on shard ${shardName}: ${error.message}`, 'SCHEMA_CREATION_FAILED');\n\t\t});\n\t});\n\n\tawait Promise.all(promises);\n}\n\n/**\n * Performs a lightweight check to determine if the expected schema is present\n * in the database.\n *\n * @param d1 - The D1 database instance to check\n * @param table - The name of the table to check\n * @returns Promise resolving to true if schema tables exist, false otherwise\n * @example\n * ```typescript\n * const hasSchema = await schemaExists(env.DB_NEW_SHARD, \"users\");\n * if (!hasSchema) {\n * console.log('Creating schema on new shard...');\n * await createSchema(env.DB_NEW_SHARD, usersSchema);\n * }\n * ```\n */\nexport async function schemaExists(d1: D1Database, table: string): Promise<boolean> {\n\ttry {\n\t\tconst result = await d1.prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\").bind(table).first();\n\t\treturn result !== null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Removes all tables that are part of the default CollegeDB schema from\n * the specified database. This is a destructive operation that cannot be undone.\n *\n * **DANGER**: This operation permanently deletes all data in the affected\n * tables. Only use during development, testing, or complete system resets.\n *\n * @param d1 - The D1 database instance to drop tables from\n * @param tables - The table schemas to drop\n * @returns Promise that resolves when all tables are dropped\n * @example\n * ```typescript\n * // Only use in development/testing environments!\n * if (process.env.NODE_ENV === 'development') {\n * await dropSchema(env.DB_TEST);\n * console.log('Test database reset completed');\n * }\n * ```\n */\nexport async function dropSchema(d1: D1Database, ...tables: string[]): Promise<void> {\n\tfor (const table of tables) {\n\t\ttry {\n\t\t\tawait d1.prepare(`DROP TABLE IF EXISTS ${table}`).run();\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to drop table ${table}:`, error);\n\t\t}\n\t}\n}\n\n/**\n * Queries the SQLite system catalog to retrieve all user-created tables\n * in the database. This is useful for schema inspection, validation,\n * and debugging purposes.\n *\n * @param d1 - The D1 database instance to inspect\n * @returns Promise resolving to array of table names, sorted alphabetically\n * @throws Returns empty array if query fails or database is inaccessible\n * @example\n * ```typescript\n * const tables = await listTables(env.DB_EAST);\n * console.log('Available tables:', tables);\n * // Output: ['posts', 'shard_mappings', 'users']\n *\n * // Check for specific table\n * if (tables.includes('users')) {\n * console.log('Users table exists');\n * }\n * ```\n */\nexport async function listTables(d1: D1Database): Promise<string[]> {\n\ttry {\n\t\tconst result = await d1.prepare(\"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name\").all();\n\t\treturn result.results.map((row: any) => row.name as string);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Moves a single record from a source D1 database to a target D1 database.\n * This is typically used during shard rebalancing operations when data needs\n * to be redistributed across shards for load balancing.\n *\n * The migration process:\n * 1. Retrieves the complete record from the source database\n * 2. Ensures the target database has the required schema\n * 3. Inserts the record into the target database (using REPLACE for safety)\n * 4. Deletes the record from the source database\n *\n * The operation is atomic from the perspective of each database, but not\n * across databases. If the operation fails partway through, manual cleanup\n * may be required.\n *\n * @param source - Source D1 database containing the record\n * @param target - Target D1 database to receive the record\n * @param primaryKey - Primary key of the record to migrate\n * @param tableName - Name of the table containing the record\n * @returns Promise that resolves when migration is complete\n * @throws {Error} If source record not found, schema creation fails, or database operations fail\n * @example\n * ```typescript\n * // Migrate a user from east to west shard\n * try {\n * await migrateRecord(env.DB_EAST, env.DB_WEST, 'user-123', 'users');\n * console.log('User migration completed successfully');\n * } catch (error) {\n * console.error('Migration failed:', error.message);\n * // May need manual cleanup depending on where it failed\n * }\n *\n * // Migrate a post between shards\n * await migrateRecord(source, target, 'post-456', 'posts');\n * ```\n */\nexport async function migrateRecord(source: D1Database, target: D1Database, primaryKey: string, tableName: string): Promise<void> {\n\tconst sourceRecord = await source.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).bind(primaryKey).first();\n\n\tif (!sourceRecord) {\n\t\tthrow new CollegeDBError(`Record with primary key ${primaryKey} not found in source database`, 'RECORD_NOT_FOUND');\n\t}\n\n\t// Create schema if it doesn't exist in target\n\tif (!(await schemaExists(target, tableName))) {\n\t\tawait createSchema(target, tableName);\n\t}\n\n\t// Get column names\n\tconst columns = Object.keys(sourceRecord);\n\tconst placeholders = columns.map(() => '?').join(', ');\n\tconst values = columns.map((col) => sourceRecord[col as keyof typeof sourceRecord]);\n\n\t// Insert into target database\n\tconst insertSQL = `INSERT OR REPLACE INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;\n\tawait target\n\t\t.prepare(insertSQL)\n\t\t.bind(...values)\n\t\t.run();\n\n\t// Delete from source database\n\tawait source.prepare(`DELETE FROM ${tableName} WHERE id = ?`).bind(primaryKey).run();\n}\n\n/**\n * Scans an existing table to find all primary keys that need to be mapped to shards.\n * This is useful when integrating CollegeDB with an existing database that already\n * contains data. The function assumes the table has an 'id' column as the primary key.\n *\n * @param d1 - The D1 database instance to scan\n * @param tableName - Name of the table to discover primary keys from\n * @param primaryKeyColumn - Name of the primary key column (defaults to 'id')\n * @returns Promise resolving to array of primary key values\n * @throws {Error} If table doesn't exist or database query fails\n * @example\n * ```typescript\n * // Discover all user IDs in an existing users table\n * const userIds = await discoverExistingPrimaryKeys(env.DB_EXISTING, 'users');\n * console.log(`Found ${userIds.length} existing users`);\n *\n * // Discover with custom primary key column\n * const orderIds = await discoverExistingPrimaryKeys(env.DB_ORDERS, 'orders', 'order_id');\n * ```\n */\nexport async function discoverExistingPrimaryKeys(d1: D1Database, tableName: string, primaryKeyColumn: string = 'id'): Promise<string[]> {\n\ttry {\n\t\tconst result = await d1.prepare(`SELECT ${primaryKeyColumn} FROM ${tableName}`).all();\n\t\treturn result.results.map((row: any) => String(row[primaryKeyColumn]));\n\t} catch (error) {\n\t\tthrow new CollegeDBError(`Failed to discover primary keys in table ${tableName}: ${error}`, 'DISCOVERY_FAILED');\n\t}\n}\n\n/**\n * Discovers existing records with additional columns for multi-key mapping support.\n * Scans a table to find primary keys along with username, email, and name columns\n * when they exist, allowing these additional columns to be used as lookup keys.\n *\n * @param d1 - The D1 database instance to scan\n * @param tableName - Name of the table to discover records from\n * @param primaryKeyColumn - Name of the primary key column (defaults to 'id')\n * @returns Promise resolving to array of record data with available columns\n * @throws {Error} If table doesn't exist or database query fails\n * @example\n * ```typescript\n * // Discover all user records with available lookup columns\n * const records = await discoverExistingRecordsWithColumns(env.DB_EXISTING, 'users');\n * console.log(`Found ${records.length} user records`);\n * records.forEach(record => {\n * console.log(`ID: ${record.id}, Email: ${record.email || 'N/A'}`);\n * });\n * ```\n */\nexport async function discoverExistingRecordsWithColumns(\n\td1: D1Database,\n\ttableName: string,\n\tprimaryKeyColumn: string = 'id'\n): Promise<Array<{ [key: string]: any }>> {\n\ttry {\n\t\t// First, discover what columns exist in the table\n\t\tconst columnInfo = await d1.prepare(`PRAGMA table_info(${tableName})`).all();\n\t\tconst availableColumns = (columnInfo.results as any[]).map((col) => col.name as string);\n\n\t\t// Build SELECT statement with available columns\n\t\tconst columnsToSelect = [primaryKeyColumn];\n\n\t\t// Add optional columns if they exist\n\t\tif (availableColumns.includes('username')) {\n\t\t\tcolumnsToSelect.push('username');\n\t\t}\n\t\tif (availableColumns.includes('email')) {\n\t\t\tcolumnsToSelect.push('email');\n\t\t}\n\t\tif (availableColumns.includes('name')) {\n\t\t\tcolumnsToSelect.push('name');\n\t\t}\n\n\t\tconst selectQuery = `SELECT ${columnsToSelect.join(', ')} FROM ${tableName}`;\n\t\tconst result = await d1.prepare(selectQuery).all();\n\n\t\treturn result.results as Array<{ [key: string]: any }>;\n\t} catch (error) {\n\t\tthrow new CollegeDBError(`Failed to discover records with columns in table ${tableName}: ${error}`, 'DISCOVERY_FAILED');\n\t}\n}\n\n/**\n * Takes a list of existing primary keys and creates shard mappings for them using\n * the specified allocation strategy. This allows existing data to be integrated\n * into the CollegeDB sharding system without data migration.\n *\n * @param primaryKeys - Array of primary key values to create mappings for\n * @param shardBindings - Array of available shard binding names\n * @param strategy - Allocation strategy to use ('hash', 'round-robin', or 'random')\n * @param mapper - KVShardMapper instance for storing mappings\n * @returns Promise that resolves when all mappings are created\n * @throws {Error} If mapping creation fails\n * @example\n * ```typescript\n * import { KVShardMapper } from './kvmap.js';\n *\n * const mapper = new KVShardMapper(env.KV);\n * const existingIds = await discoverExistingPrimaryKeys(env.DB_EXISTING, 'users');\n * const shards = ['db-east', 'db-west', 'db-central'];\n *\n * await createMappingsForExistingKeys(existingIds, shards, 'hash', mapper);\n * console.log('All existing users mapped to shards');\n * ```\n */\nexport async function createMappingsForExistingKeys(\n\tprimaryKeys: string[],\n\tshardBindings: string[],\n\tstrategy: 'hash' | 'round-robin' | 'random',\n\tmapper: any // KVShardMapper instance\n): Promise<void> {\n\tconst totalShards = shardBindings.length;\n\n\tfor (let i = 0; i < primaryKeys.length; i++) {\n\t\tconst primaryKey = primaryKeys[i]!;\n\t\tlet selectedShard: string;\n\n\t\tswitch (strategy) {\n\t\t\tcase 'hash':\n\t\t\t\tlet hash = 0;\n\t\t\t\tfor (let j = 0; j < primaryKey.length; j++) {\n\t\t\t\t\tconst char = primaryKey.charCodeAt(j);\n\t\t\t\t\thash = (hash << 5) - hash + char;\n\t\t\t\t\thash = hash & hash;\n\t\t\t\t}\n\t\t\t\tconst hashIndex = Math.abs(hash) % totalShards;\n\t\t\t\tselectedShard = shardBindings[hashIndex]!;\n\t\t\t\tbreak;\n\t\t\tcase 'random':\n\t\t\t\tselectedShard = shardBindings[Math.floor(Math.random() * totalShards)]!;\n\t\t\t\tbreak;\n\t\t\tdefault: // round-robin\n\t\t\t\tselectedShard = shardBindings[i % totalShards]!;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tawait mapper.setShardMapping(primaryKey, selectedShard);\n\t}\n}\n\n/**\n * Represents the result of validating a table for sharding.\n * Contains information about the table structure, primary key, record count,\n * and any issues encountered during validation.\n */\nexport type ValidationResult = {\n\tisValid: boolean;\n\ttableName: string;\n\tprimaryKeyColumn: string;\n\trecordCount: number;\n\tissues: string[];\n};\n\n/**\n * Checks if a table exists and has a primary key column that can be used\n * for sharding. Returns information about the table structure and primary key.\n *\n * @param d1 - The D1 database instance to check\n * @param tableName - Name of the table to validate\n * @param primaryKeyColumn - Expected primary key column name (defaults to 'id')\n * @returns Promise resolving to validation result with table info\n * @throws {Error} If table doesn't exist or validation fails\n * @example\n * ```typescript\n * const validation = await validateTableForSharding(env.DB_EXISTING, 'users');\n * if (validation.isValid) {\n * console.log(`Table ${validation.tableName} is ready for sharding`);\n * console.log(`Primary key: ${validation.primaryKeyColumn}`);\n * console.log(`Record count: ${validation.recordCount}`);\n * } else {\n * console.error('Table validation failed:', validation.issues);\n * }\n * ```\n */\nexport async function validateTableForSharding(d1: D1Database, tableName: string, primaryKeyColumn: string): Promise<ValidationResult> {\n\tconst issues: string[] = [];\n\tlet recordCount = 0;\n\n\ttry {\n\t\t// Check if table exists\n\t\tconst tableCheck = await d1.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).bind(tableName).first();\n\n\t\tif (!tableCheck) {\n\t\t\tissues.push(`Table '${tableName}' does not exist`);\n\t\t\treturn {\n\t\t\t\tisValid: false,\n\t\t\t\ttableName,\n\t\t\t\tprimaryKeyColumn,\n\t\t\t\trecordCount: 0,\n\t\t\t\tissues\n\t\t\t} satisfies ValidationResult;\n\t\t}\n\n\t\t// Check if primary key column exists\n\t\tconst columnCheck = await d1.prepare(`PRAGMA table_info(${tableName})`).all();\n\t\tconst hasIdColumn = columnCheck.results.some((col: any) => col.name === primaryKeyColumn && col.pk === 1);\n\n\t\tif (!hasIdColumn) {\n\t\t\tissues.push(`Primary key column '${primaryKeyColumn}' not found or not set as primary key`);\n\t\t}\n\n\t\t// Get record count\n\t\tconst countResult = await d1.prepare(`SELECT COUNT(*) as count FROM ${tableName}`).first();\n\t\trecordCount = (countResult as any)?.count || 0;\n\n\t\tif (recordCount === 0) {\n\t\t\tissues.push(`Table '${tableName}' is empty`);\n\t\t}\n\t} catch (error) {\n\t\tissues.push(`Database validation error: ${error}`);\n\t}\n\n\treturn {\n\t\tisValid: issues.length === 0,\n\t\ttableName,\n\t\tprimaryKeyColumn,\n\t\trecordCount,\n\t\tissues\n\t} satisfies ValidationResult;\n}\n\n/**\n * Configuration options for integrating an existing database with CollegeDB.\n * Allows customization of which tables to process, primary key column,\n * sharding strategy, and whether to add the shard_mappings table.\n */\nexport type IntegrationOptions = {\n\ttables?: string[];\n\tprimaryKeyColumn?: string;\n\tstrategy?: ShardingStrategy;\n\taddShardMappingsTable?: boolean;\n\tdryRun?: boolean;\n\tmigrateOtherColumns?: boolean;\n};\n\n/**\n * Represents the result of integrating an existing database with CollegeDB.\n * Contains information about the success of the integration, shard name,\n * number of tables processed, total records integrated, mappings created,\n * and any issues encountered during the process.\n */\nexport type IntegrationResult = {\n\tsuccess: boolean;\n\tshardName: string;\n\ttablesProcessed: number;\n\ttotalRecords: number;\n\tmappingsCreated: number;\n\tissues: string[];\n};\n\n/**\n * Performs a complete drop-in integration of an existing database.\n * This is the main function for integrating CollegeDB with an existing database\n * that already contains data. It discovers tables, validates them, creates shard\n * mappings, and optionally adds the shard_mappings table if needed.\n *\n * When `migrateOtherColumns` is enabled, the function will also create additional\n * lookup keys for username, email, and name columns if they exist in the table.\n * This allows these fields to be used as lookup keys in addition to the primary key.\n *\n * @param d1 - The existing D1 database to integrate\n * @param shardName - The shard binding name for this database\n * @param mapper - KVShardMapper instance for storing mappings\n * @param options - Configuration options for the integration\n * @param options.migrateOtherColumns - When true, creates additional lookup keys for username, email, and name columns\n * @returns Promise resolving to integration summary\n * @throws {Error} If integration fails\n * @example\n * ```typescript\n * import { KVShardMapper } from './kvmap.js';\n *\n * const mapper = new KVShardMapper(env.KV);\n * const result = await integrateExistingDatabase(env.DB_EXISTING, 'db-existing', mapper, {\n * tables: ['users', 'posts'],\n * strategy: 'hash',\n * addShardMappingsTable: true,\n * migrateOtherColumns: true // Creates additional lookup keys\n * });\n *\n * console.log(`Integrated ${result.totalRecords} records from ${result.tablesProcessed} tables`);\n * // Now users can be looked up by username:john, email:john@example.com, or name:John Doe\n * ```\n */\nexport async function integrateExistingDatabase(\n\td1: D1Database,\n\tshardName: string,\n\tmapper: KVShardMapper,\n\toptions: IntegrationOptions = {}\n): Promise<IntegrationResult> {\n\tconst {\n\t\ttables,\n\t\tprimaryKeyColumn = 'id',\n\t\tstrategy = 'hash',\n\t\taddShardMappingsTable = true,\n\t\tdryRun = false,\n\t\tmigrateOtherColumns = false\n\t} = options;\n\n\tconst issues: string[] = [];\n\tlet tablesProcessed = 0;\n\tlet totalRecords = 0;\n\tlet mappingsCreated = 0;\n\n\ttry {\n\t\t// Discover tables if not specified\n\t\tconst tablesToProcess = tables || (await listTables(d1));\n\n\t\t// Filter out the shard_mappings table if it already exists\n\t\tconst dataTableNames = tablesToProcess.filter((table) => table !== 'shard_mappings');\n\n\t\tfor (const tableName of dataTableNames) {\n\t\t\ttry {\n\t\t\t\t// Validate table\n\t\t\t\tconst validation = await validateTableForSharding(d1, tableName, primaryKeyColumn);\n\n\t\t\t\tif (!validation.isValid) {\n\t\t\t\t\tissues.push(`Table ${tableName}: ${validation.issues.join(', ')}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (migrateOtherColumns) {\n\t\t\t\t\t// Use the new function to get records with additional columns\n\t\t\t\t\tconst records = await discoverExistingRecordsWithColumns(d1, tableName, primaryKeyColumn);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tissues.push(`Table ${tableName} has no records to process`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!dryRun) {\n\t\t\t\t\t\tfor (const record of records) {\n\t\t\t\t\t\t\tconst primaryKey = String(record[primaryKeyColumn]);\n\n\t\t\t\t\t\t\t// Create additional lookup keys based on available columns\n\t\t\t\t\t\t\tconst additionalKeys: string[] = [];\n\n\t\t\t\t\t\t\tif (record.username && typeof record.username === 'string') {\n\t\t\t\t\t\t\t\tadditionalKeys.push(`username:${record.username}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (record.email && typeof record.email === 'string') {\n\t\t\t\t\t\t\t\tadditionalKeys.push(`email:${record.email}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (record.name && typeof record.name === 'string') {\n\t\t\t\t\t\t\t\tadditionalKeys.push(`name:${record.name}`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Set the primary mapping with additional lookup keys\n\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName, additionalKeys);\n\t\t\t\t\t\t\tmappingsCreated++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttotalRecords += records.length;\n\t\t\t\t} else {\n\t\t\t\t\t// Original behavior: only use primary keys\n\t\t\t\t\tconst primaryKeys = await discoverExistingPrimaryKeys(d1, tableName, primaryKeyColumn);\n\t\t\t\t\tif (primaryKeys.length === 0) {\n\t\t\t\t\t\tissues.push(`Table ${tableName} has no records to process`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!dryRun) {\n\t\t\t\t\t\tfor (const primaryKey of primaryKeys) {\n\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName);\n\t\t\t\t\t\t\tmappingsCreated++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttotalRecords += primaryKeys.length;\n\t\t\t\t}\n\n\t\t\t\ttablesProcessed++;\n\t\t\t} catch (error) {\n\t\t\t\tissues.push(`Failed to process table ${tableName}: ${error}`);\n\t\t\t}\n\t\t}\n\n\t\tif (addShardMappingsTable && !dryRun) {\n\t\t\tconst hasMappingsTable = (await listTables(d1)).includes('shard_mappings');\n\t\t\tif (!hasMappingsTable) {\n\t\t\t\tawait d1\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`\n\t\t\t\t\tCREATE TABLE IF NOT EXISTS shard_mappings (\n\t\t\t\t\t\tprimary_key TEXT PRIMARY KEY,\n\t\t\t\t\t\tshard_name TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t\t);`.trim()\n\t\t\t\t\t)\n\t\t\t\t\t.run();\n\t\t\t}\n\t\t}\n\n\t\t// Add this shard to known shards list\n\t\tif (!dryRun) {\n\t\t\tawait mapper.addKnownShard(shardName);\n\t\t}\n\t} catch (error) {\n\t\tissues.push(`Integration failed: ${error}`);\n\t}\n\n\treturn {\n\t\tsuccess: issues.length === 0 || (issues.length > 0 && tablesProcessed > 0),\n\t\tshardName,\n\t\ttablesProcessed,\n\t\ttotalRecords,\n\t\tmappingsCreated,\n\t\tissues\n\t};\n}\n\n/**\n * Automatically detects if a database needs migration and performs it\n *\n * This function is called automatically by CollegeDB operations to detect\n * existing databases that contain data but haven't been integrated into the\n * sharding system. It performs seamless migration without user intervention.\n *\n * The detection process:\n * 1. Checks if the database has data tables with primary keys\n * 2. Verifies if primary key mappings exist in KV\n * 3. If unmapped data is found, performs automatic integration\n * 4. Caches results to avoid repeated checks\n *\n * When `migrateOtherColumns` is enabled, additional lookup keys will be created\n * for username, email, and name columns if they exist in the tables.\n *\n * @param d1 - The D1 database instance to check and potentially migrate\n * @param shardName - The shard binding name for this database\n * @param config - CollegeDB configuration containing KV and strategy\n * @param options - Optional migration configuration\n * @param options.migrateOtherColumns - When true, creates additional lookup keys for username, email, and name columns\n * @returns Promise resolving to migration result summary\n * @example\n * ```typescript\n * // Called automatically by CollegeDB operations\n * const result = await autoDetectAndMigrate(env.DB_EXISTING, 'db-existing', config, {\n * migrateOtherColumns: true\n * });\n * if (result.migrationPerformed) {\n * console.log(`Auto-migrated ${result.recordsMigrated} records`);\n * }\n * ```\n */\nexport async function autoDetectAndMigrate(\n\td1: D1Database,\n\tshardName: string,\n\tconfig: CollegeDBConfig,\n\toptions: {\n\t\tprimaryKeyColumn?: string;\n\t\ttablesToCheck?: string[];\n\t\tskipCache?: boolean;\n\t\tmaxRecordsToCheck?: number;\n\t\tmigrateOtherColumns?: boolean;\n\t} = {}\n): Promise<{\n\tmigrationNeeded: boolean;\n\tmigrationPerformed: boolean;\n\trecordsMigrated: number;\n\ttablesProcessed: number;\n\tissues: string[];\n}> {\n\tconst { primaryKeyColumn = 'id', tablesToCheck, skipCache = false, maxRecordsToCheck = 1000, migrateOtherColumns = false } = options;\n\n\tconst cacheKey = `${shardName}_migration_check`;\n\n\t// Check cache to avoid repeated migration checks\n\tif (!skipCache && migrationStatusCache.has(cacheKey)) {\n\t\treturn {\n\t\t\tmigrationNeeded: false,\n\t\t\tmigrationPerformed: false,\n\t\t\trecordsMigrated: 0,\n\t\t\ttablesProcessed: 0,\n\t\t\tissues: []\n\t\t};\n\t}\n\n\tconst issues: string[] = [];\n\tlet recordsMigrated = 0;\n\tlet tablesProcessed = 0;\n\tlet migrationNeeded = false;\n\tlet migrationPerformed = false;\n\n\ttry {\n\t\tconst { KVShardMapper } = await import('./kvmap.js');\n\t\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\t\t// Discover tables to check\n\t\tconst allTables = await listTables(d1);\n\t\tconst dataTableNames =\n\t\t\ttablesToCheck ||\n\t\t\tallTables.filter((table) => table !== 'shard_mappings' && !table.startsWith('sqlite_') && table !== 'sqlite_sequence');\n\n\t\tif (dataTableNames.length === 0) {\n\t\t\t// No data tables found, mark as migrated\n\t\t\tmigrationStatusCache.set(cacheKey, true);\n\t\t\treturn {\n\t\t\t\tmigrationNeeded: false,\n\t\t\t\tmigrationPerformed: false,\n\t\t\t\trecordsMigrated: 0,\n\t\t\t\ttablesProcessed: 0,\n\t\t\t\tissues: []\n\t\t\t};\n\t\t}\n\n\t\t// Check each table for unmapped data\n\t\tfor (const tableName of dataTableNames) {\n\t\t\ttry {\n\t\t\t\t// Quick validation\n\t\t\t\tconst validation = await validateTableForSharding(d1, tableName, primaryKeyColumn);\n\t\t\t\tif (!validation.isValid || validation.recordCount === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Sample some primary keys to check if they're mapped\n\t\t\t\tconst sampleSize = Math.min(maxRecordsToCheck, validation.recordCount);\n\t\t\t\tconst sampleKeys = await d1\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`\n\t\t\t\t\tSELECT ${primaryKeyColumn} FROM ${tableName}\n\t\t\t\t\tORDER BY ${primaryKeyColumn}\n\t\t\t\t\tLIMIT ?`.trim()\n\t\t\t\t\t)\n\t\t\t\t\t.bind(sampleSize)\n\t\t\t\t\t.all();\n\n\t\t\t\tlet unmappedCount = 0;\n\t\t\t\tconst keysToCheck = sampleKeys.results.slice(0, 10); // Check first 10 as sample\n\n\t\t\t\tfor (const row of keysToCheck) {\n\t\t\t\t\tconst primaryKey = String((row as any)[primaryKeyColumn]);\n\t\t\t\t\tconst mapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\tif (!mapping) {\n\t\t\t\t\t\tunmappedCount++;\n\t\t\t\t\t\tmigrationNeeded = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (unmappedCount > 0) {\n\t\t\t\t\tconsole.log(`Auto-migrating table ${tableName} in shard ${shardName} (${validation.recordCount} records)`);\n\n\t\t\t\t\tif (migrateOtherColumns) {\n\t\t\t\t\t\t// Use multi-column discovery for migration with additional lookup keys\n\t\t\t\t\t\tconst allRecords = await discoverExistingRecordsWithColumns(d1, tableName, primaryKeyColumn);\n\n\t\t\t\t\t\t// Create mappings for all unmapped keys with additional lookup keys\n\t\t\t\t\t\tlet newMappings = 0;\n\t\t\t\t\t\tfor (const record of allRecords) {\n\t\t\t\t\t\t\tconst primaryKey = String(record[primaryKeyColumn]);\n\t\t\t\t\t\t\tconst existingMapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\t\t\tif (!existingMapping) {\n\t\t\t\t\t\t\t\t// Create additional lookup keys based on available columns\n\t\t\t\t\t\t\t\tconst additionalKeys: string[] = [];\n\n\t\t\t\t\t\t\t\tif (record.username && typeof record.username === 'string') {\n\t\t\t\t\t\t\t\t\tadditionalKeys.push(`username:${record.username}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (record.email && typeof record.email === 'string') {\n\t\t\t\t\t\t\t\t\tadditionalKeys.push(`email:${record.email}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (record.name && typeof record.name === 'string') {\n\t\t\t\t\t\t\t\t\tadditionalKeys.push(`name:${record.name}`);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Set the primary mapping with additional lookup keys\n\t\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName, additionalKeys);\n\t\t\t\t\t\t\t\tnewMappings++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trecordsMigrated += newMappings;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Original behavior: only use primary keys\n\t\t\t\t\t\tconst allPrimaryKeys = await discoverExistingPrimaryKeys(d1, tableName, primaryKeyColumn);\n\n\t\t\t\t\t\t// Create mappings for all unmapped keys\n\t\t\t\t\t\tlet newMappings = 0;\n\t\t\t\t\t\tfor (const primaryKey of allPrimaryKeys) {\n\t\t\t\t\t\t\tconst existingMapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\t\t\tif (!existingMapping) {\n\t\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName);\n\t\t\t\t\t\t\t\tnewMappings++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trecordsMigrated += newMappings;\n\t\t\t\t\t}\n\n\t\t\t\t\ttablesProcessed++;\n\t\t\t\t\tmigrationPerformed = true;\n\n\t\t\t\t\tconsole.log(`Auto-migrated ${recordsMigrated} records from table ${tableName}`);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tissues.push(`Auto-migration failed for table ${tableName}: ${error}`);\n\t\t\t}\n\t\t}\n\n\t\t// Add shard to known shards if migration was performed\n\t\tif (migrationPerformed) {\n\t\t\tawait mapper.addKnownShard(shardName);\n\n\t\t\t// Add shard_mappings table if it doesn't exist\n\t\t\tconst hasMappingsTable = allTables.includes('shard_mappings');\n\t\t\tif (!hasMappingsTable) {\n\t\t\t\tawait d1\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`CREATE TABLE IF NOT EXISTS shard_mappings (\n\t\t\t\t\t\tprimary_key TEXT PRIMARY KEY,\n\t\t\t\t\t\tshard_name TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t\t);\n\t\t\t\t`\n\t\t\t\t\t)\n\t\t\t\t\t.run();\n\t\t\t}\n\t\t}\n\n\t\t// Cache the result to avoid repeated checks\n\t\tmigrationStatusCache.set(cacheKey, true);\n\n\t\tif (migrationPerformed) {\n\t\t\tconsole.log(`Auto-migration completed for shard ${shardName}: ${recordsMigrated} records from ${tablesProcessed} tables`);\n\t\t}\n\t} catch (error) {\n\t\tissues.push(`Auto-migration error: ${error}`);\n\t}\n\n\treturn {\n\t\tmigrationNeeded,\n\t\tmigrationPerformed,\n\t\trecordsMigrated,\n\t\ttablesProcessed,\n\t\tissues\n\t};\n}\n\n/**\n * Performs a lightweight check to determine if a database contains\n * existing data that hasn't been mapped to the sharding system.\n * This is used internally to trigger automatic migration.\n *\n * @param d1 - The D1 database instance to check\n * @param shardName - The shard binding name\n * @param config - CollegeDB configuration\n * @returns Promise resolving to true if migration is needed\n * @example\n * ```typescript\n * const needsMigration = await checkMigrationNeeded(env.DB, 'db-main', config);\n * if (needsMigration) {\n * console.log('Database contains unmapped data');\n * }\n * ```\n */\nexport async function checkMigrationNeeded(d1: D1Database, shardName: string, config: CollegeDBConfig): Promise<boolean> {\n\tconst cacheKey = `${shardName}_migration_check`;\n\n\t// Check cache first (but not during tests with skip cache)\n\tif (migrationStatusCache.has(cacheKey)) {\n\t\treturn false; // Already checked/migrated\n\t}\n\n\ttry {\n\t\t// Check if shard_mappings table exists as indicator of previous migration\n\t\tconst tables = await listTables(d1);\n\t\tconst hasShardMappingsTable = tables.includes('shard_mappings');\n\n\t\tif (hasShardMappingsTable) {\n\t\t\t// If shard_mappings table exists, this database has been processed before\n\t\t\tmigrationStatusCache.set(cacheKey, true);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst { KVShardMapper } = await import('./kvmap.js');\n\t\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\t\t// Quick check: look for any table with data\n\t\tconst dataTableNames = tables.filter(\n\t\t\t(table) => table !== 'shard_mappings' && !table.startsWith('sqlite_') && table !== 'sqlite_sequence'\n\t\t);\n\n\t\tfor (const tableName of dataTableNames.slice(0, 3)) {\n\t\t\t// Check first 3 tables only\n\t\t\ttry {\n\t\t\t\t// Check if table has records\n\t\t\t\tconst countResult = await d1.prepare(`SELECT COUNT(*) as count FROM ${tableName} LIMIT 1`).first();\n\t\t\t\tconst recordCount = (countResult as any)?.count || 0;\n\n\t\t\t\tif (recordCount > 0) {\n\t\t\t\t\t// Sample one record to see if it's mapped\n\t\t\t\t\tconst sampleRecord = await d1.prepare(`SELECT id FROM ${tableName} LIMIT 1`).first();\n\t\t\t\t\tif (sampleRecord) {\n\t\t\t\t\t\tconst primaryKey = String((sampleRecord as any).id);\n\t\t\t\t\t\tconst mapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\t\tif (!mapping) {\n\t\t\t\t\t\t\treturn true; // Found unmapped data\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip tables that don't have 'id' column or have other issues\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t} catch {\n\t\treturn false; // Assume no migration needed if check fails\n\t}\n}\n\n/**\n * Clears the migration status cache\n *\n * Resets the internal cache used to track which databases have been\n * checked for migration. Useful for testing or forcing re-checks.\n *\n * @example\n * ```typescript\n * // Force re-check of all databases\n * clearMigrationCache();\n * ```\n */\nexport function clearMigrationCache(): void {\n\tmigrationStatusCache.clear();\n}\n\n/**\n * Clears a specific migration cache entry\n *\n * Resets the cache for a specific shard, forcing re-check on next\n * migration detection call.\n *\n * @param shardName - The shard name to clear from cache\n * @example\n * ```typescript\n * // Force re-check of specific shard\n * clearShardMigrationCache('db-auto');\n * ```\n */\nexport function clearShardMigrationCache(shardName: string): void {\n\tconst cacheKey = `${shardName}_migration_check`;\n\tmigrationStatusCache.delete(cacheKey);\n}\n",
|
|
8
|
-
"/**\n * @fileoverview Main routing and query distribution logic for CollegeDB\n *\n * This module provides the core functionality for routing database queries to the\n * appropriate D1 shard based on primary key mappings. It handles shard selection,\n * database routing, and provides a unified API for CRUD operations across multiple\n * distributed D1 databases.\n *\n * Key responsibilities:\n * - Initialize and manage the global CollegeDB configuration\n * - Route queries to appropriate shards based on primary key mappings\n * - Implement shard allocation strategies (round-robin, random, hash-based)\n * - Provide unified CRUD operations across distributed shards\n * - Coordinate with Durable Objects for centralized shard management\n * - Handle shard rebalancing and data migration\n *\n * @example\n * ```typescript\n * import { initialize, insert, first, run } from 'collegedb';\n *\n * // Initialize the system\n * initialize({\n * kv: env.KV,\n * coordinator: env.ShardCoordinator,\n * shards: {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST\n * },\n * strategy: 'hash'\n * });\n *\n * // Insert a record (automatically routed to appropriate shard)\n * await run('user-123', 'INSERT INTO users (id, name) VALUES (?, ?)', ['user-123', 'John']);\n *\n * // Query the record (routed to same shard)\n * const result = await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);\n * ```\n *\n * @author CollegeDB Team\n * @since 1.0.0\n */\n\nimport type { D1Database, D1PreparedStatement, D1Result, Request } from '@cloudflare/workers-types';\nimport { CollegeDBError } from './errors.js';\nimport { KVShardMapper } from './kvmap.js';\nimport type { CollegeDBConfig, D1Region, OperationType, ShardLocation, ShardStats, ShardingStrategy } from './types.js';\n\n/**\n * Global configuration for the collegedb instance\n *\n * Stores the system-wide configuration including KV namespace, available shards,\n * coordinator settings, and allocation strategy. Must be initialized before\n * any routing operations can be performed.\n *\n * @private\n */\nlet globalConfig: CollegeDBConfig | null = null;\n\n/**\n * Sets up the global configuration for the CollegeDB system. This must be called\n * before any other operations can be performed. The configuration includes KV\n * storage, available D1 shards, optional coordinator, and allocation strategy.\n *\n * This will also automatically detect and migrate existing databases without requiring\n * additional setup. If shards contain existing data with primary keys, CollegeDB\n * will automatically create the necessary mappings for seamless operation.\n *\n * @param config - Configuration object containing all necessary bindings and settings\n * @throws {Error} If configuration is invalid or required bindings are missing\n * @example\n * ```typescript\n * // Basic setup with multiple shards - auto-migration happens automatically\n * initialize({\n * kv: env.KV,\n * shards: {\n * 'db-primary': env.DB_PRIMARY, // Existing DB with data\n * 'db-secondary': env.DB_SECONDARY // Another existing DB\n * },\n * strategy: 'round-robin'\n * });\n * // Existing data is now automatically accessible via CollegeDB!\n *\n * // Advanced setup with coordinator\n * initialize({\n * kv: env.KV,\n * coordinator: env.ShardCoordinator,\n * shards: {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST,\n * 'db-central': env.DB_CENTRAL\n * },\n * strategy: 'hash'\n * });\n * ```\n */\nexport function initialize(config: CollegeDBConfig) {\n\tglobalConfig = config;\n\n\tif (config.shards && Object.keys(config.shards).length > 0 && !config.disableAutoMigration) {\n\t\tperformAutoMigration(config).catch((error) => {\n\t\t\tconsole.warn('Background auto-migration failed:', error);\n\t\t});\n\t}\n}\n\n/**\n * Sets up the global configuration for the CollegeDB system asynchronously.\n * This must be called before any other operations can be performed. The\n * configuration includes KVstorage, available D1 shards, optional coordinator,\n * and allocation strategy.\n *\n * This will also automatically detect and migrate existing databases without requiring\n * additional setup. If shards contain existing data with primary keys, CollegeDB\n * will automatically create the necessary mappings for seamless operation.\n *\n * Compared to `initialize`, this method waits for the background check to finish.\n *\n * @param config - Configuration object containing all necessary bindings and settings\n * @throws {Error} If configuration is invalid or required bindings are missing\n * @example\n * ```typescript\n * // Basic setup with multiple shards - auto-migration happens automatically\n * initializeAsync({\n * kv: env.KV,\n * shards: {\n * 'db-primary': env.DB_PRIMARY, // Existing DB with data\n * 'db-secondary': env.DB_SECONDARY // Another existing DB\n * },\n * strategy: 'round-robin'\n * });\n * // Existing data is now automatically accessible via CollegeDB!\n *\n * // Advanced setup with coordinator\n * initializeAsync({\n * kv: env.KV,\n * coordinator: env.ShardCoordinator,\n * shards: {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST,\n * 'db-central': env.DB_CENTRAL\n * },\n * strategy: 'hash'\n * });\n * ```\n */\nexport async function initializeAsync(config: CollegeDBConfig) {\n\tglobalConfig = config;\n\n\tif (config.shards && Object.keys(config.shards).length > 0 && !config.disableAutoMigration)\n\t\ttry {\n\t\t\tawait performAutoMigration(config);\n\t\t} catch (error) {\n\t\t\tconsole.warn('Auto migration failed:', error);\n\t\t}\n}\n\n/**\n * Initializes the configuration and then performs a callback once the configuration\n * has finished initializing.\n *\n * @param config - CollegeDB Configuration\n * @param callback - The callback to perform after the initialization\n * @returns The result of the callback\n * @example\n * ```\n * import { collegedb, first } from 'collegedb'\n *\n * const result = collegedb({\n * kv: env.KV,\n * shards: {\n * 'db-primary': env.DB_PRIMARY, // Existing DB with data\n * 'db-secondary': env.DB_SECONDARY // Another existing DB\n * },\n * strategy: 'hash'\n * }, async () => {\n * return await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);\n * });\n * ```\n */\nexport async function collegedb<T>(config: CollegeDBConfig, callback: () => T) {\n\tawait initializeAsync(config);\n\treturn await callback();\n}\n\n/**\n * Performs automatic migration detection for all shards in the background\n *\n * This function runs asynchronously after initialization to check all configured\n * shards for existing data that needs migration. It's designed to be non-blocking\n * and won't interfere with immediate database operations.\n *\n * @private\n * @param config - CollegeDB configuration\n */\nasync function performAutoMigration(config: CollegeDBConfig): Promise<void> {\n\ttry {\n\t\tconst { autoDetectAndMigrate } = await import('./migrations.js');\n\t\tconst shardNames = Object.keys(config.shards);\n\n\t\tconsole.log(`🔍 Checking ${shardNames.length} shards for existing data...`);\n\n\t\t// Check each shard for migration needs\n\t\tconst migrationPromises = shardNames.map(async (shardName) => {\n\t\t\tconst database = config.shards[shardName];\n\t\t\tif (!database) return null;\n\n\t\t\ttry {\n\t\t\t\tconst result = await autoDetectAndMigrate(database, shardName, config, {\n\t\t\t\t\tmaxRecordsToCheck: 1000\n\t\t\t\t});\n\n\t\t\t\treturn {\n\t\t\t\t\tshardName,\n\t\t\t\t\t...result\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(`Auto-migration failed for shard ${shardName}:`, error);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\n\t\tconst results = await Promise.all(migrationPromises);\n\t\tconst successfulMigrations = results.filter((r) => r?.migrationPerformed);\n\n\t\tif (successfulMigrations.length > 0) {\n\t\t\tconst totalRecords = successfulMigrations.reduce((sum, r) => sum + (r?.recordsMigrated || 0), 0);\n\t\t\tconsole.log(`🎉 Auto-migration completed! Migrated ${totalRecords} records across ${successfulMigrations.length} shards`);\n\t\t\tsuccessfulMigrations.forEach((result) => {\n\t\t\t\tif (result) {\n\t\t\t\t\tconsole.log(` ✅ ${result.shardName}: ${result.recordsMigrated} records from ${result.tablesProcessed} tables`);\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tconsole.log('✅ All shards ready - no migration needed');\n\t\t}\n\t} catch (error) {\n\t\tconsole.warn('Background auto-migration setup failed:', error);\n\t}\n}\n\n/**\n * Resets the global configuration (for testing purposes only)\n *\n * @private\n * @internal\n */\nexport function resetConfig(): void {\n\tglobalConfig = null;\n}\n\n/**\n * Gets the global configuration, throwing an error if not initialized\n *\n * Internal utility function that retrieves the global configuration and\n * ensures the system has been properly initialized before performing\n * any operations.\n *\n * @private\n * @returns The global CollegeDB configuration\n * @throws {Error} If initialize() has not been called yet\n */\nfunction getConfig(): CollegeDBConfig {\n\tif (!globalConfig) {\n\t\tthrow new CollegeDBError('CollegeDB not initialized. Call initialize() first.', 'NOT_INITIALIZED');\n\t}\n\treturn globalConfig;\n}\n\n/**\n * Determines the operation type from a SQL statement\n * @private\n * @param sql - The SQL statement to analyze\n * @returns The operation type ('read' for SELECT, 'write' for INSERT/UPDATE/DELETE)\n */\nfunction getOperationType(sql: string): OperationType {\n\tconst sql0 = sql.trim().toUpperCase();\n\n\tif (\n\t\tsql0.startsWith('SELECT') ||\n\t\tsql0.startsWith('VALUES') ||\n\t\tsql0.startsWith('TABLE') ||\n\t\tsql0.startsWith('PRAGMA') ||\n\t\tsql0.startsWith('EXPLAIN') ||\n\t\tsql0.startsWith('WITH') ||\n\t\tsql0.startsWith('SHOW')\n\t) {\n\t\treturn 'read';\n\t}\n\n\t// All other operations (INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.) are considered writes\n\treturn 'write';\n}\n\n/**\n * Resolves the effective sharding strategy based on configuration and operation type\n * @private\n * @param config - CollegeDB configuration\n * @param type - The type of operation being performed\n * @returns The effective sharding strategy to use\n */\nfunction resolveStrategy(config: CollegeDBConfig, type: OperationType): ShardingStrategy {\n\tconst strategy = config.strategy || 'hash';\n\n\tif (typeof strategy === 'string') {\n\t\treturn strategy;\n\t}\n\n\treturn strategy[type];\n}\n\n/**\n * Calculates the relative distance between two D1 regions for location-based sharding.\n * Lower values indicate closer regions with better expected latency.\n *\n * @private\n * @param from - Source region\n * @param to - Target region\n * @returns Relative distance score (lower is better)\n */\nfunction calculateRegionDistance(from: D1Region, to: D1Region): number {\n\t// Same region = optimal\n\tif (from === to) return 0;\n\n\t// Define region coordinates (approximate)\n\tconst regionCoords: Record<D1Region, { lat: number; lon: number }> = {\n\t\twnam: { lat: 37.7749, lon: -122.4194 }, // San Francisco\n\t\tenam: { lat: 40.7128, lon: -74.006 }, // New York\n\t\tweur: { lat: 51.5074, lon: -0.1278 }, // London\n\t\teeur: { lat: 52.52, lon: 13.405 }, // Berlin\n\t\tapac: { lat: 35.6762, lon: 139.6503 }, // Tokyo\n\t\toc: { lat: -33.8688, lon: 151.2093 }, // Sydney\n\t\tme: { lat: 25.2048, lon: 55.2708 }, // Dubai\n\t\taf: { lat: -26.2041, lon: 28.0473 } // Johannesburg\n\t};\n\n\tconst fromCoord = regionCoords[from];\n\tconst toCoord = regionCoords[to];\n\n\t// Simple Euclidean distance calculation\n\tconst latDiff = fromCoord.lat - toCoord.lat;\n\tconst lonDiff = fromCoord.lon - toCoord.lon;\n\treturn Math.sqrt(latDiff * latDiff + lonDiff * lonDiff);\n}\n\n/**\n * Determines the closest D1 region based on an IP address.\n * Uses IP geolocation to estimate the user's location and find the nearest D1 region.\n *\n * This function uses Cloudflare's CF object which provides geolocation data\n * in Cloudflare Workers environment. Falls back to 'wnam' if geolocation fails.\n *\n * @param request - The incoming Request object (contains CF geolocation data in Cloudflare Workers)\n * @returns The closest D1Region based on IP geolocation\n * @example\n * ```typescript\n * // In a Cloudflare Worker\n * export default {\n * async fetch(request: Request, env: Env) {\n * const userRegion = getClosestRegionFromIP(request);\n *\n * initialize({\n * kv: env.KV,\n * strategy: 'location',\n * targetRegion: userRegion, // Automatically optimized for user location\n * shardLocations: { ... },\n * shards: { ... }\n * });\n * }\n * };\n * ```\n */\nexport function getClosestRegionFromIP(request: Request): D1Region {\n\tconst cf = request.cf;\n\n\tif (!cf || !cf.country) {\n\t\treturn 'wnam';\n\t}\n\n\tconst country = cf.country as string;\n\tconst continent = cf.continent as string;\n\n\t// Western North America\n\tif (['US', 'CA', 'MX'].includes(country)) {\n\t\t// Further refine by region/state if available\n\t\tconst region = (cf.region || cf.regionCode || '') as string;\n\t\tconst timezone = (cf.timezone || '') as string;\n\n\t\t// West Coast indicators\n\t\tif (\n\t\t\tregion.includes('CA') ||\n\t\t\tregion.includes('WA') ||\n\t\t\tregion.includes('OR') ||\n\t\t\tregion.includes('NV') ||\n\t\t\tregion.includes('AZ') ||\n\t\t\tregion.includes('UT') ||\n\t\t\ttimezone.includes('Pacific') ||\n\t\t\ttimezone.includes('America/Los_Angeles')\n\t\t) {\n\t\t\treturn 'wnam';\n\t\t}\n\n\t\t// East Coast and Central - default to Eastern North America\n\t\treturn 'enam';\n\t}\n\n\t// Eastern North America (broader North America)\n\tif (['GL', 'PM', 'BM'].includes(country)) {\n\t\treturn 'enam';\n\t}\n\n\t// Western Europe\n\tif (['GB', 'IE', 'FR', 'ES', 'PT', 'NL', 'BE', 'LU', 'CH', 'AT', 'IT'].includes(country)) {\n\t\treturn 'weur';\n\t}\n\n\t// Eastern Europe\n\tif (\n\t\t[\n\t\t\t'DE',\n\t\t\t'PL',\n\t\t\t'CZ',\n\t\t\t'SK',\n\t\t\t'HU',\n\t\t\t'SI',\n\t\t\t'HR',\n\t\t\t'BA',\n\t\t\t'RS',\n\t\t\t'ME',\n\t\t\t'MK',\n\t\t\t'AL',\n\t\t\t'BG',\n\t\t\t'RO',\n\t\t\t'MD',\n\t\t\t'UA',\n\t\t\t'BY',\n\t\t\t'LT',\n\t\t\t'LV',\n\t\t\t'EE',\n\t\t\t'FI',\n\t\t\t'SE',\n\t\t\t'NO',\n\t\t\t'DK',\n\t\t\t'IS'\n\t\t].includes(country)\n\t) {\n\t\treturn 'eeur';\n\t}\n\n\t// Russia - closer to Eastern Europe for most population centers\n\tif (country === 'RU') {\n\t\treturn 'eeur';\n\t}\n\n\t// Asia Pacific\n\tif (['JP', 'KR', 'CN', 'HK', 'TW', 'MO', 'MN', 'KP'].includes(country)) {\n\t\treturn 'apac';\n\t}\n\n\t// Southeast Asia and South Asia -> APAC\n\tif (\n\t\t['TH', 'VN', 'SG', 'MY', 'ID', 'PH', 'BN', 'KH', 'LA', 'MM', 'TL', 'IN', 'PK', 'BD', 'LK', 'NP', 'BT', 'MV', 'AF'].includes(country)\n\t) {\n\t\treturn 'apac';\n\t}\n\n\t// Oceania\n\tif (['AU', 'NZ', 'PG', 'FJ', 'NC', 'VU', 'SB', 'WS', 'TO', 'KI', 'NR', 'PW', 'FM', 'MH', 'TV'].includes(country)) {\n\t\treturn 'oc';\n\t}\n\n\t// Middle East\n\tif (['AE', 'SA', 'QA', 'KW', 'BH', 'OM', 'YE', 'IQ', 'IR', 'SY', 'LB', 'JO', 'IL', 'PS', 'TR', 'CY'].includes(country)) {\n\t\treturn 'me';\n\t}\n\n\t// Africa\n\tif (continent === 'AF' || ['EG', 'LY', 'TN', 'DZ', 'MA', 'SD', 'SS', 'ET', 'ER', 'DJ', 'SO'].includes(country)) {\n\t\treturn 'af';\n\t}\n\n\t// Central Asia -> closer to Eastern Europe\n\tif (['KZ', 'UZ', 'TM', 'TJ', 'KG'].includes(country)) {\n\t\treturn 'eeur';\n\t}\n\n\t// South America -> geographically closer to Eastern North America\n\tif (continent === 'SA' || ['BR', 'AR', 'CL', 'PE', 'CO', 'VE', 'EC', 'BO', 'PY', 'UY', 'GY', 'SR', 'GF'].includes(country)) {\n\t\treturn 'enam';\n\t}\n\n\t// Central America and Caribbean -> Eastern North America\n\tif (\n\t\t['GT', 'BZ', 'SV', 'HN', 'NI', 'CR', 'PA', 'CU', 'JM', 'HT', 'DO', 'PR', 'TT', 'BB', 'GD', 'VC', 'LC', 'DM', 'AG', 'KN'].includes(\n\t\t\tcountry\n\t\t)\n\t) {\n\t\treturn 'enam';\n\t}\n\n\t// Default fallback - Western North America (major Cloudflare hub)\n\treturn 'wnam';\n}\n\n/**\n * Selects the optimal shard for location-based allocation strategy.\n * Prioritizes shards in the target region, then nearby regions by distance.\n *\n * @private\n * @param targetRegion - The preferred region for allocation\n * @param availableShards - List of available shard names\n * @param shardLocations - Geographic locations of each shard\n * @param primaryKey - The primary key being allocated (for consistent tiebreaking)\n * @returns Selected shard name\n */\nfunction selectShardByLocation(\n\ttargetRegion: D1Region,\n\tavailableShards: string[],\n\tshardLocations: Record<string, ShardLocation>,\n\tprimaryKey: string\n): string {\n\t// Filter shards that have location information\n\tconst locatedShards = availableShards.filter((shard) => shardLocations[shard]);\n\n\tif (locatedShards.length === 0) {\n\t\t// Fallback to hash if no location info available\n\t\tlet hash = 0;\n\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\thash = (hash << 5) - hash + char;\n\t\t\thash = hash & hash;\n\t\t}\n\t\tconst index = Math.abs(hash) % availableShards.length;\n\t\treturn availableShards[index]!;\n\t}\n\n\t// Calculate distances and priorities\n\tconst shardScores = locatedShards.map((shard) => {\n\t\tconst location = shardLocations[shard]!;\n\t\tconst distance = calculateRegionDistance(targetRegion, location.region);\n\t\tconst priority = location.priority || 1;\n\n\t\t// Lower score is better (distance penalty, priority bonus)\n\t\tconst score = distance - priority * 0.1;\n\n\t\treturn { shard, score, distance, priority };\n\t});\n\n\t// Sort by score (lower is better)\n\tshardScores.sort((a, b) => a.score - b.score);\n\n\t// For ties in the best score range, use consistent hashing\n\tconst bestScore = shardScores[0]!.score;\n\tconst bestShards = shardScores.filter((s) => Math.abs(s.score - bestScore) < 0.01);\n\n\tif (bestShards.length === 1) {\n\t\treturn bestShards[0]!.shard;\n\t}\n\n\t// Consistent selection among best candidates\n\tlet hash = 0;\n\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\tconst char = primaryKey.charCodeAt(i);\n\t\thash = (hash << 5) - hash + char;\n\t\thash = hash & hash;\n\t}\n\tconst index = Math.abs(hash) % bestShards.length;\n\treturn bestShards[index]!.shard;\n}\n\n/**\n * Gets or allocates a shard for a primary key with operation-specific strategy\n *\n * This is the core routing function that determines which shard should handle\n * a given primary key. If a mapping already exists, it returns the existing\n * shard. If not, it allocates a new shard using the configured strategy.\n *\n * Allocation strategies:\n * - **round-robin**: Cycles through shards in order (with coordinator)\n * - **random**: Randomly selects from available shards\n * - **hash**: Uses consistent hashing for deterministic assignment\n * - **location**: Selects shards based on geographic proximity to target region\n *\n * The function prefers using the Durable Object coordinator when available\n * for centralized allocation decisions, falling back to local strategies\n * when the coordinator is unavailable.\n *\n * @private\n * @param primaryKey - The primary key to route\n * @param operationType - The type of operation (read/write) for mixed strategy support\n * @returns Promise resolving to the shard binding name\n * @throws {Error} If no shards are configured or allocation fails\n * @example\n * ```typescript\n * // This function is called internally by CRUD operations\n * const readShard = await getShardForKey('user-123', 'read');\n * const writeShard = await getShardForKey('user-123', 'write');\n * console.log(`User 123 reads from: ${readShard}, writes to: ${writeShard}`);\n * ```\n */\nasync function getShardForKey(primaryKey: string, operationType: OperationType = 'write'): Promise<string> {\n\tconst config = getConfig();\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\t// Check if mapping already exists\n\tconst existingMapping = await mapper.getShardMapping(primaryKey);\n\tif (existingMapping) {\n\t\treturn existingMapping.shard;\n\t}\n\n\t// Before allocating a new shard, check if any existing shards contain this key\n\t// and perform automatic migration if needed\n\tconst availableShards = Object.keys(config.shards);\n\tif (availableShards.length === 0) {\n\t\tthrow new CollegeDBError('No shards configured', 'NO_SHARDS');\n\t}\n\n\t// Check existing shards for unmapped data containing this primary key\n\tfor (const shardName of availableShards) {\n\t\tconst database = config.shards[shardName];\n\t\tif (!database) continue;\n\n\t\ttry {\n\t\t\t// Quick check if this primary key exists in any table in this shard\n\t\t\tconst { autoDetectAndMigrate } = await import('./migrations.js');\n\t\t\tconst migrationResult = await autoDetectAndMigrate(database, shardName, config, {\n\t\t\t\tmaxRecordsToCheck: 100 // Limit check for performance\n\t\t\t});\n\n\t\t\tif (migrationResult.migrationPerformed) {\n\t\t\t\t// Re-check mapping after migration\n\t\t\t\tconst newMapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\tif (newMapping) {\n\t\t\t\t\treturn newMapping.shard;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Don't fail the operation if auto-migration fails\n\t\t\tconsole.warn(`Auto-migration check failed for shard ${shardName}:`, error);\n\t\t}\n\t}\n\n\t// If no existing mapping found after auto-migration, allocate a new shard\n\tlet selectedShard: string;\n\n\t// Resolve the effective strategy for this operation type\n\tconst effectiveStrategy = resolveStrategy(config, operationType);\n\n\t// Use coordinator if available for allocation\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tconst response = await coordinator.fetch('http://coordinator/allocate', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tprimaryKey,\n\t\t\t\t\tstrategy: effectiveStrategy, // Use resolved strategy instead of config.strategy\n\t\t\t\t\toperationType, // Pass operation type for coordinator awareness\n\t\t\t\t\ttargetRegion: config.targetRegion,\n\t\t\t\t\tshardLocations: config.shardLocations\n\t\t\t\t})\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = (await response.json()) as { shard: string };\n\t\t\t\tselectedShard = result.shard;\n\t\t\t} else {\n\t\t\t\t// Fallback to simple round-robin\n\t\t\t\tselectedShard = availableShards[Math.floor(Math.random() * availableShards.length)]!;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('Coordinator allocation failed, falling back to local strategy:', error);\n\t\t\tselectedShard = availableShards[Math.floor(Math.random() * availableShards.length)]!;\n\t\t}\n\t} else {\n\t\t// Simple allocation strategy without coordinator\n\t\tswitch (effectiveStrategy) {\n\t\t\tcase 'hash':\n\t\t\t\tlet hash = 0;\n\t\t\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\t\t\thash = (hash << 5) - hash + char;\n\t\t\t\t\thash = hash & hash;\n\t\t\t\t}\n\t\t\t\tconst index = Math.abs(hash) % availableShards.length;\n\t\t\t\tselectedShard = availableShards[index] || availableShards[0]!;\n\t\t\t\tbreak;\n\t\t\tcase 'location':\n\t\t\t\tif (!config.targetRegion) {\n\t\t\t\t\tconsole.warn('Location strategy requires targetRegion in config, falling back to hash');\n\t\t\t\t\t// Fallback to hash\n\t\t\t\t\tlet fallbackHash = 0;\n\t\t\t\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\t\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\t\t\t\tfallbackHash = (fallbackHash << 5) - fallbackHash + char;\n\t\t\t\t\t\tfallbackHash = fallbackHash & fallbackHash;\n\t\t\t\t\t}\n\t\t\t\t\tconst fallbackIndex = Math.abs(fallbackHash) % availableShards.length;\n\t\t\t\t\tselectedShard = availableShards[fallbackIndex] || availableShards[0]!;\n\t\t\t\t} else {\n\t\t\t\t\tselectedShard = selectShardByLocation(config.targetRegion, availableShards, config.shardLocations || {}, primaryKey);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'random':\n\t\t\t\tselectedShard = availableShards[Math.floor(Math.random() * availableShards.length)] || availableShards[0]!;\n\t\t\t\tbreak;\n\t\t\tdefault: // round-robin\n\t\t\t\tselectedShard = availableShards[0]!; // Simplified without state\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Store the mapping\n\tawait mapper.setShardMapping(primaryKey, selectedShard);\n\treturn selectedShard;\n}\n\n/**\n * Gets the D1 database instance for a primary key with operation-specific routing\n *\n * Resolves the primary key to its assigned shard and returns the corresponding\n * D1 database instance. This function handles the complete routing process\n * from primary key to database connection, with support for different strategies\n * based on operation type.\n *\n * @private\n * @param primaryKey - The primary key to route\n * @param operationType - The type of operation (read/write) for mixed strategy support\n * @returns Promise resolving to the D1 database instance\n * @throws {Error} If shard routing fails or database instance not found\n */\nasync function getDatabase(primaryKey: string, operationType: OperationType = 'write'): Promise<D1Database> {\n\tconst config = getConfig();\n\tconst shard = await getShardForKey(primaryKey, operationType);\n\tconst database = config.shards[shard];\n\n\tif (!database) {\n\t\tthrow new CollegeDBError(`Shard ${shard} not found in configuration`, 'SHARD_NOT_FOUND');\n\t}\n\n\treturn database;\n}\n\n/**\n * Creates the database schema in the specified D1 database\n *\n * @param d1 - The D1 database instance to create schema in\n * @param schema - The SQL schema definition to execute\n * @returns Promise that resolves when schema creation is complete\n * @throws {Error} If schema creation fails\n * @example\n * ```typescript\n * const userSchema = `\n * CREATE TABLE users (\n * id TEXT PRIMARY KEY,\n * name TEXT NOT NULL,\n * email TEXT UNIQUE\n * );\n * `;\n * await createSchema(env.DB_NEW_SHARD, userSchema);\n * ```\n */\nexport async function createSchema(d1: D1Database, schema: string): Promise<void> {\n\tconst { createSchema: createSchemaImpl } = await import('./migrations.js');\n\tawait createSchemaImpl(d1, schema);\n}\n\n/**\n * Prepares a SQL statement for execution with operation-aware routing.\n *\n * @param key - The primary key to route the query\n * @param sql - The SQL statement to prepare\n * @returns Promise that resolves to a prepared statement\n * @throws {Error} If preparation fails\n */\nexport async function prepare(key: string, sql: string): Promise<D1PreparedStatement> {\n\tconst operationType = getOperationType(sql);\n\tconst db = await getDatabase(key, operationType);\n\tconst result = db.prepare(sql);\n\treturn result;\n}\n\n/**\n * Executes a statement on the appropriate shard based on the primary key.\n * The primary key is used to determine which shard should store the record,\n * ensuring consistent routing for future queries.\n *\n * @template T - Type of the result records\n * @param key - Primary key to route the query (should match the record's primary key)\n * @param sql - SQL statement with parameter placeholders\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise that resolves when the statement is complete\n * @throws {Error} If statement fails or routing fails\n * @example\n * ```typescript\n * // Insert a new user\n * await run('user-123',\n * 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',\n * ['user-123', 'John Doe', 'john@example.com']\n * );\n *\n * // Insert a post linked to a user\n * await run('post-456',\n * 'INSERT INTO posts (id, user_id, title, content) VALUES (?, ?, ?, ?)',\n * ['post-456', 'user-123', 'Hello World', 'My first post!']\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Update user information\n * await run('user-123',\n * 'UPDATE users SET name = ?, email = ? WHERE id = ?',\n * ['John Smith', 'johnsmith@example.com', 'user-123']\n * );\n *\n * // Update post content\n * await run('post-456',\n * 'UPDATE posts SET title = ?, content = ?, updated_at = strftime(\"%s\", \"now\") WHERE id = ?',\n * ['Updated Title', 'Updated content here', 'post-456']\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Delete a specific user\n * await run('user-123',\n * 'DELETE FROM users WHERE id = ?',\n * ['user-123']\n * );\n *\n * // Delete user's posts (cascade delete)\n * await run('user-123',\n * 'DELETE FROM posts WHERE user_id = ?',\n * ['user-123']\n * );\n *\n * // Delete with conditions\n * await run('user-123',\n * 'DELETE FROM posts WHERE user_id = ? AND created_at < ?',\n * ['user-123', Date.now() - 86400000] // Posts older than 1 day\n * );\n * ```\n */\nexport async function run<T = Record<string, unknown>>(key: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst prepared = await prepare(key, sql);\n\tconst result = await prepared.bind(...bindings).run<T>();\n\n\tif (!result.success) {\n\t\tthrow new CollegeDBError(`Query failed: ${result.error || 'Unknown error'}`, 'QUERY_FAILED');\n\t}\n\n\treturn result;\n}\n\n/**\n * Retrieves all records matching the query for a given primary key.\n *\n * This function is useful for fetching multiple records based on a primary key.\n * It automatically routes the query to the correct shard based on the provided\n * primary key, ensuring consistent data access.\n * @param key - Primary key to route the query\n * @param sql - The SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise that resolves to the result of the update operation\n * @throws {Error} If update fails or routing fails\n *\n * @example\n * ```typescript\n * type Post = {\n * id: string;\n * user_id: string;\n * title: string;\n * content: string;\n * };\n *\n *\n * // Get user's posts\n * const postsResult = await all<Post>('user-123',\n * 'SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC',\n * ['user-123']\n * );\n *\n * console.log(`User has ${postsResult.meta.count} posts`);\n * ```\n */\nexport async function all<T = Record<string, unknown>>(key: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst prepared = await prepare(key, sql);\n\tconst result = await prepared.bind(...bindings).all<T>();\n\n\tif (!result.success) {\n\t\tthrow new CollegeDBError(`Query failed: ${result.error || 'Unknown error'}`, 'QUERY_FAILED');\n\t}\n\n\treturn result;\n}\n\n/**\n * Retrieves the first record matching the query for a given primary key.\n *\n * This function is useful for fetching a single record based on a primary key.\n * It automatically routes the query to the correct shard based on the provided\n * primary key, ensuring consistent data access.\n *\n * @template T - Type of the result record\n * @param key - Primary key to route the query\n * @param sql - SQL statement with parameter placeholders\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise that resolves to the first matching record, or null if not found\n * @throws {Error} If query fails or routing fails\n *\n * @example\n * ```typescript\n * type User = {\n * id: string;\n * name: string;\n * email: string;\n * };\n * // Get a specific user\n * const userResult = await first<User>('user-123',\n * 'SELECT * FROM users WHERE id = ?',\n * ['user-123']\n * );\n *\n * if (userResult) {\n * console.log(`Found user: ${userResult.name}`);\n * }\n */\nexport async function first<T = Record<string, unknown>>(key: string, sql: string, bindings: any[] = []): Promise<T | null> {\n\tconst prepared = await prepare(key, sql);\n\tconst result = await prepared.bind(...bindings).first<T>();\n\treturn result;\n}\n\n/**\n * Reassigns a primary key to a different shard\n *\n * Moves a primary key and its associated data from one shard to another. This\n * operation is useful for load balancing, shard maintenance, or geographic\n * redistribution of data.\n *\n * The reassignment process:\n * 1. Validates the target shard exists in configuration\n * 2. Checks that a mapping exists for the primary key\n * 3. If target shard differs from current, migrates the data\n * 4. Updates the KV mapping to point to the new shard\n *\n * **Note**: This operation involves data migration and should be used\n * carefully in production environments. Consider the impact on ongoing queries.\n *\n * @param primaryKey - Primary key to reassign to a different shard\n * @param newBinding - New shard binding name where the data should be moved\n * @param tableName - Name of the table containing the record to migrate\n * @returns Promise that resolves when reassignment and migration are complete\n * @throws {Error} If target shard not found, mapping doesn't exist, or migration fails\n * @example\n * ```typescript\n * // Move a user from east to west coast for better latency\n * try {\n * await reassignShard('user-california-123', 'db-west', 'users');\n * console.log('User successfully moved to west coast shard');\n * } catch (error) {\n * console.error('Reassignment failed:', error.message);\n * }\n *\n * // Load balancing: move high-activity user to dedicated shard\n * await reassignShard('user-enterprise-456', 'db-dedicated', 'users');\n * ```\n */\nexport async function reassignShard(primaryKey: string, newBinding: string, tableName: string): Promise<void> {\n\tconst config = getConfig();\n\n\tif (!config.shards[newBinding]) {\n\t\tthrow new CollegeDBError(`Shard ${newBinding} not found in configuration`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\tconst currentMapping = await mapper.getShardMapping(primaryKey);\n\n\tif (!currentMapping) {\n\t\tthrow new CollegeDBError(`No existing mapping found for primary key: ${primaryKey}`, 'MAPPING_NOT_FOUND');\n\t}\n\n\t// Migrate data if different shard\n\tif (currentMapping.shard !== newBinding) {\n\t\tconst { migrateRecord } = await import('./migrations.js');\n\t\tconst sourceDb = config.shards[currentMapping.shard];\n\t\tconst targetDb = config.shards[newBinding];\n\n\t\tif (!sourceDb || !targetDb) {\n\t\t\tthrow new CollegeDBError('Source or target shard not available', 'SHARD_UNAVAILABLE');\n\t\t}\n\n\t\tawait migrateRecord(sourceDb, targetDb, primaryKey, tableName);\n\t}\n\n\t// Update mapping\n\tawait mapper.updateShardMapping(primaryKey, newBinding);\n}\n\n/**\n * Lists all known shards\n *\n * Returns an array of all shard binding names known to the system. First\n * attempts to get the list from the Durable Object coordinator for the most\n * up-to-date information, then falls back to the configured shards if the\n * coordinator is unavailable.\n *\n * @returns Promise resolving to array of shard binding names\n * @example\n * ```typescript\n * const shards = await listKnownShards();\n * console.log('Available shards:', shards);\n * // Output: ['db-east', 'db-west', 'db-central']\n *\n * // Check if a specific shard is available\n * if (shards.includes('db-asia')) {\n * console.log('Asia region shard is available');\n * }\n * ```\n */\nexport async function listKnownShards(): Promise<string[]> {\n\tconst config = getConfig();\n\n\t// Try to get from coordinator first\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tconst response = await coordinator.fetch('http://coordinator/shards');\n\t\t\tif (response.ok) {\n\t\t\t\treturn await response.json();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('Failed to get shards from coordinator:', error);\n\t\t}\n\t}\n\n\t// Fallback to configured shards\n\treturn Object.keys(config.shards);\n}\n\n/**\n * Gets statistics for all shards\n *\n * Returns usage statistics for all known shards, including key counts and\n * last updated timestamps. First attempts to get real-time statistics from\n * the Durable Object coordinator, then falls back to KV-based counting.\n *\n * This information is useful for:\n * - Load balancing decisions\n * - Monitoring shard utilization\n * - Capacity planning\n * - Performance analysis\n *\n * @returns Promise resolving to array of shard statistics\n * @example\n * ```typescript\n * const stats = await getShardStats();\n * stats.forEach(shard => {\n * console.log(`${shard.binding}: ${shard.count} keys`);\n * if (shard.lastUpdated) {\n * console.log(` Last updated: ${new Date(shard.lastUpdated)}`);\n * }\n * });\n *\n * // Find most loaded shard\n * const mostLoaded = stats.reduce((prev, current) =>\n * (prev.count > current.count) ? prev : current\n * );\n * console.log(`Most loaded shard: ${mostLoaded.binding} (${mostLoaded.count} keys)`);\n * ```\n */\nexport async function getShardStats(): Promise<ShardStats[]> {\n\tconst config = getConfig();\n\n\t// Try to get from coordinator first\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tconst response = await coordinator.fetch('http://coordinator/stats');\n\t\t\tif (response.ok) {\n\t\t\t\treturn await response.json();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('Failed to get stats from coordinator:', error);\n\t\t}\n\t}\n\n\t// Fallback to KV-based counting\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\tconst counts = await mapper.getShardKeyCounts();\n\n\treturn Object.entries(config.shards).map(([binding, _]) => ({\n\t\tbinding,\n\t\tcount: counts[binding] || 0\n\t}));\n}\n\n/**\n * Bypasses the normal routing logic to execute a query directly on a specified\n * shard. This is useful for administrative operations, cross-shard queries,\n * or when you need to query data that doesn't follow the primary key routing pattern.\n *\n * **Use with caution**: This function bypasses routing safeguards and should\n * be used only when you specifically need to target a particular shard.\n *\n * @param shardBinding - The shard binding name to execute the query on\n * @param sql - SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise resolving to the result of the query execution\n * @throws {Error} If shard not found or query fails\n * @example\n * ```typescript\n * // Administrative query: insert a new user directly into a specific shard\n * const result = await runShard('db-east',\n * 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',\n * ['user-789', 'Alice', 'alice@example.com']\n * );\n * console.log(`Inserted user with ID: ${result.lastInsertId}`);\n * ```\n */\nexport async function runShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst config = getConfig();\n\tconst db = config.shards[shardBinding];\n\n\tif (!db) {\n\t\tthrow new CollegeDBError(`Shard ${shardBinding} not found`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst result = await db\n\t\t.prepare(sql)\n\t\t.bind(...bindings)\n\t\t.run<T>();\n\n\tif (!result.success) {\n\t\tthrow new CollegeDBError(`Query failed: ${result.error || 'Unknown error'}`, 'QUERY_FAILED');\n\t}\n\n\treturn result;\n}\n\n/**\n * Bypasses the normal routing logic to execute a query directly on a specified\n * shard. This is useful for administrative operations, cross-shard queries,\n * or when you need to query data that doesn't follow the primary key routing pattern.\n *\n * **Use with caution**: This function bypasses routing safeguards and should\n * be used only when you specifically need to target a particular shard.\n *\n * @param shardBinding - The shard binding name to execute the query on\n * @param sql - SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise resolving to structured query results\n * @throws {Error} If shard not found or query fails\n * @example\n * ```typescript\n * // Administrative query: count all users across a specific shard\n * const eastCoastStats = await allShard('db-east',\n * 'SELECT COUNT(*) as user_count FROM users'\n * );\n * console.log(`East coast users: ${eastCoastStats.results[0].user_count}`);\n *\n * // Cross-shard analytics: get recent posts from a specific region\n * const recentPosts = await allShard('db-west',\n * 'SELECT id, title, created_at FROM posts WHERE created_at > ? ORDER BY created_at DESC LIMIT ?',\n * [Date.now() - 86400000, 10] // Last 24 hours, limit 10\n * );\n *\n * // Schema inspection on specific shard\n * const tables = await allShard('db-central',\n * \"SELECT name FROM sqlite_master WHERE type='table'\"\n * );\n * ```\n */\nexport async function allShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst config = getConfig();\n\tconst db = config.shards[shardBinding];\n\n\tif (!db) {\n\t\tthrow new CollegeDBError(`Shard ${shardBinding} not found`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst result = await db\n\t\t.prepare(sql)\n\t\t.bind(...bindings)\n\t\t.all<T>();\n\n\treturn result;\n}\n\n/**\n * Bypasses the normal routing logic to execute a query directly on a specified\n * shard. This is useful for administrative operations, cross-shard queries,\n * or when you need to query data that doesn't follow the primary key routing pattern.\n *\n * **Use with caution**: This function bypasses routing safeguards and should\n * be used only when you specifically need to target a particular shard.\n *\n * @param shardBinding - The shard binding name to execute the query on\n * @param sql - SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise resolving to the first matching record, or null if not found\n * @throws {Error} If shard not found or query fails\n * @example\n * ```typescript\n * // Administrative query: get a specific user from a shard\n * const user = await firstShard('db-east',\n * 'SELECT * FROM users WHERE id = ?',\n * ['user-123']);\n * if (user) {\n * console.log(`Found user: ${user.name}`);\n * } else {\n * console.log('User not found in east shard');\n * }\n * ```\n */\nexport async function firstShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings: any[] = []): Promise<T | null> {\n\tconst config = getConfig();\n\tconst db = config.shards[shardBinding];\n\n\tif (!db) {\n\t\tthrow new CollegeDBError(`Shard ${shardBinding} not found`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst result = await db\n\t\t.prepare(sql)\n\t\t.bind(...bindings)\n\t\t.first<T>();\n\n\treturn result;\n}\n\n/**\n * Executes a query on all shards and returns the results from each shard.\n *\n * This function is useful for scenarios where you need to aggregate data\n * from multiple shards, such as running analytics or cross-shard queries.\n * It executes the same SQL statement on each shard and collects the results.\n * @param sql - The SQL statement to execute on each shard\n * @param bindings - Parameter values to bind to the SQL statement\n * @param batchSize - Number of concurrent queries to run at once (default: 50)\n * @returns Promise resolving to an array of results from each shard\n * @since 1.0.4\n */\nexport async function runAllShards<T = Record<string, unknown>>(\n\tsql: string,\n\tbindings: any[] = [],\n\tbatchSize: number = 50\n): Promise<D1Result<T>[]> {\n\tconst config = getConfig();\n\tconst results: Promise<D1Result<T>>[] = [];\n\n\tfor (const [binding, db] of Object.entries(config.shards)) {\n\t\ttry {\n\t\t\tconst result = db\n\t\t\t\t.prepare(sql)\n\t\t\t\t.bind(...bindings)\n\t\t\t\t.all<T>()\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(`Error executing query on shard ${binding}:`, error);\n\t\t\t\t\treturn { success: false, results: [], meta: { count: 0, duration: 0 } } as unknown as D1Result<T>;\n\t\t\t\t});\n\t\t\tresults.push(result);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error running on shard ${binding}:`, error);\n\t\t}\n\t}\n\n\tconst batch: D1Result<T>[] = [];\n\tfor (let i = 0; i < results.length; i += batchSize) {\n\t\tbatch.push(...(await Promise.all(results.slice(i, i + batchSize))));\n\t}\n\n\treturn batch;\n}\n\n/**\n * Executes a query on all shards and returns all matching records from each shard.\n *\n * This function is useful for scenarios where you need to retrieve all records\n * matching a query across multiple shards, such as aggregating data or running\n * cross-shard analytics.\n * @param sql - The SQL statement to execute on each shard\n * @param bindings - Parameter values to bind to the SQL statement\n * @param batchSize - Number of concurrent queries to run at once (default: 50)\n * @returns Promise resolving to an array of results from each shard\n * @since 1.0.4\n */\nexport async function allAllShards<T = Record<string, unknown>>(\n\tsql: string,\n\tbindings: any[] = [],\n\tbatchSize: number = 50\n): Promise<D1Result<T>[]> {\n\tconst config = getConfig();\n\tconst results: Promise<D1Result<T>>[] = [];\n\n\tfor (const [binding, db] of Object.entries(config.shards)) {\n\t\ttry {\n\t\t\tconst result = db\n\t\t\t\t.prepare(sql)\n\t\t\t\t.bind(...bindings)\n\t\t\t\t.all<T>()\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(`Error executing query on shard ${binding}:`, error);\n\t\t\t\t\treturn { success: false, results: [], meta: { count: 0, duration: 0 } } as unknown as D1Result<T>;\n\t\t\t\t});\n\t\t\tresults.push(result);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error running on shard ${binding}:`, error);\n\t\t}\n\t}\n\n\tconst batch: D1Result<T>[] = [];\n\tfor (let i = 0; i < results.length; i += batchSize) {\n\t\tbatch.push(...(await Promise.all(results.slice(i, i + batchSize))));\n\t}\n\n\treturn batch;\n}\n\n/**\n * Executes a query on all shards and returns the first matching record from each shard.\n *\n * This function is useful for scenarios where you need to retrieve a single record\n * from each shard, such as fetching the latest entry or a specific item that may\n * exist on multiple shards.\n * @param sql - The SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @param batchSize - Number of concurrent queries to run at once (default: 50)\n * @returns Promise resolving to an array of first matching records from each shard\n * @since 1.0.4\n */\nexport async function firstAllShards<T = Record<string, unknown>>(\n\tsql: string,\n\tbindings: any[] = [],\n\tbatchSize: number = 50\n): Promise<(T | null)[]> {\n\tconst config = getConfig();\n\tconst results: Promise<T | null>[] = [];\n\n\tfor (const [binding, db] of Object.entries(config.shards)) {\n\t\ttry {\n\t\t\tconst result = db\n\t\t\t\t.prepare(sql)\n\t\t\t\t.bind(...bindings)\n\t\t\t\t.first<T>()\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(`Error executing query on shard ${binding}:`, error);\n\t\t\t\t\treturn null;\n\t\t\t\t});\n\t\t\t3;\n\t\t\tresults.push(result);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error running on shard ${binding}:`, error);\n\t\t}\n\t}\n\n\tconst batch: (T | null)[] = [];\n\tfor (let i = 0; i < results.length; i += batchSize) {\n\t\tbatch.push(...(await Promise.all(results.slice(i, i + batchSize))));\n\t}\n\n\treturn batch;\n}\n\n/**\n * Flushes all shard mappings (development only)\n *\n * Completely clears all primary key to shard mappings from both KV storage\n * and the Durable Object coordinator. This operation resets the entire\n * routing system to a clean state.\n *\n * **DANGER**: This operation is destructive and irreversible. After flushing,\n * all existing primary keys will be treated as new and may be assigned to\n * different shards than before, causing data routing issues.\n *\n * **Use only for**:\n * - Development and testing environments\n * - Complete system resets\n * - Emergency recovery scenarios\n *\n * @returns Promise that resolves when all mappings are cleared\n * @example\n * ```typescript\n * // Only use in development!\n * if (process.env.NODE_ENV === 'development') {\n * await flush();\n * console.log('All shard mappings cleared for testing');\n *\n * // Now all keys will be reassigned on next access\n * await run('user-123', 'INSERT INTO users (id, name) VALUES (?, ?)',\n * ['user-123', 'Test User']);\n * }\n * ```\n */\nexport async function flush(): Promise<void> {\n\tconst config = getConfig();\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\tawait mapper.clearAllMappings();\n\n\t// Also flush coordinator if available\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tawait coordinator.fetch('http://coordinator/flush', { method: 'POST' });\n\t\t} catch (error) {\n\t\t\tconsole.warn('Failed to flush coordinator:', error);\n\t\t}\n\t}\n}\n",
|
|
7
|
+
"/**\n * @fileoverview Database schema management and data migration utilities for CollegeDB\n *\n * This module provides utilities for managing database schemas across multiple D1 shards\n * and migrating data between shards. It includes default schema definitions, schema\n * validation, and data migration functions that ensure consistency across the distributed\n * database system.\n *\n * Key features:\n * - Default schema creation for typical use cases\n * - Schema validation and existence checking\n * - Data migration between D1 database instances\n * - Batch schema operations across multiple shards\n * - Table discovery and management utilities\n *\n * @example\n * ```typescript\n * import { createSchema, migrateRecord, schemaExists } from './migrations.js';\n *\n * // Create schema on a new shard\n * await createSchema(env.DB_EAST);\n *\n * // Check if schema exists\n * const hasSchema = await schemaExists(env.DB_WEST);\n *\n * // Migrate a user from one shard to another\n * await migrateRecord(env.DB_EAST, env.DB_WEST, 'user-123', 'users');\n * ```\n *\n * @author Gregory Mitchell\n * @since 1.0.0\n */\n\nimport type { D1Database } from '@cloudflare/workers-types';\nimport { CollegeDBError } from './errors.js';\nimport type { KVShardMapper } from './kvmap.js';\nimport type { CollegeDBConfig, ShardingStrategy } from './types.js';\n\n/**\n * Cache for migration status to avoid repeated checks\n * @private\n */\nconst migrationStatusCache = new Map<string, boolean>();\n\n/**\n * Executes SQL statements to create the default table structure and indexes\n * in the specified D1 database. Supports custom schemas and handles SQL\n * statement parsing with comment filtering.\n *\n * The function:\n * 1. Splits the schema into individual SQL statements\n * 2. Filters out comments and empty statements\n * 3. Executes each statement using prepared statements\n * 4. Provides detailed error reporting on failures\n *\n * @param d1 - The D1 database instance to create schema in\n * @param schema - Schema SQL to use\n * @returns Promise that resolves when all schema statements are executed\n * @throws {Error} If any schema statement fails with detailed error information\n * @example\n * ```typescript\n * const sql = `\n * CREATE TABLE products (\n * id TEXT PRIMARY KEY,\n * name TEXT NOT NULL,\n * price REAL\n * );\n * `;\n * await createSchema(env.DB_PRODUCTS, sql);\n * ```\n */\nexport async function createSchema(d1: D1Database, schema: string): Promise<void> {\n\tconst statements = schema\n\t\t.split(';')\n\t\t.map((stmt) => stmt.trim())\n\t\t.filter((stmt) => stmt.length > 0 && !stmt.startsWith('--')); // Filter out comments\n\n\tfor (const statement of statements) {\n\t\ttry {\n\t\t\tawait d1.prepare(statement).run();\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to execute schema statement:', statement, error);\n\t\t\tthrow new CollegeDBError(`Schema migration failed: ${error}`, 'SCHEMA_MIGRATION_FAILED');\n\t\t}\n\t}\n}\n\n/**\n * Applies the schema to all provided D1 database instances in parallel.\n * This is useful for initializing a complete sharded database system\n * where all shards need the same table structure.\n *\n * The function executes schema creation on all shards concurrently for\n * performance, but provides detailed error reporting that identifies\n * which specific shard failed if any errors occur.\n *\n * @param shards - Record mapping shard names to D1 database instances\n * @param schema - Schema SQL to use\n * @returns Promise that resolves when schema is created on all shards\n * @throws {Error} If schema creation fails on any shard, with shard identification\n * @example\n * ```typescript\n * const shards = {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST,\n * 'db-central': env.DB_CENTRAL\n * };\n *\n * try {\n * await createSchemaAcrossShards(shards);\n * console.log('Schema created on all shards successfully');\n * } catch (error) {\n * console.error('Schema creation failed:', error.message);\n * // Error will specify which shard failed\n * }\n * ```\n */\nexport async function createSchemaAcrossShards(shards: Record<string, D1Database>, schema: string): Promise<void> {\n\tconst promises = Object.entries(shards).map(([shardName, db]) => {\n\t\treturn createSchema(db, schema).catch((error) => {\n\t\t\tthrow new CollegeDBError(`Failed to create schema on shard ${shardName}: ${error.message}`, 'SCHEMA_CREATION_FAILED');\n\t\t});\n\t});\n\n\tawait Promise.all(promises);\n}\n\n/**\n * Performs a lightweight check to determine if the expected schema is present\n * in the database.\n *\n * @param d1 - The D1 database instance to check\n * @param table - The name of the table to check\n * @returns Promise resolving to true if schema tables exist, false otherwise\n * @example\n * ```typescript\n * const hasSchema = await schemaExists(env.DB_NEW_SHARD, \"users\");\n * if (!hasSchema) {\n * console.log('Creating schema on new shard...');\n * await createSchema(env.DB_NEW_SHARD, usersSchema);\n * }\n * ```\n */\nexport async function schemaExists(d1: D1Database, table: string): Promise<boolean> {\n\ttry {\n\t\tconst result = await d1.prepare(\"SELECT name FROM sqlite_master WHERE type='table' AND name=?\").bind(table).first();\n\t\treturn result !== null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Removes all tables that are part of the default CollegeDB schema from\n * the specified database. This is a destructive operation that cannot be undone.\n *\n * **DANGER**: This operation permanently deletes all data in the affected\n * tables. Only use during development, testing, or complete system resets.\n *\n * @param d1 - The D1 database instance to drop tables from\n * @param tables - The table schemas to drop\n * @returns Promise that resolves when all tables are dropped\n * @example\n * ```typescript\n * // Only use in development/testing environments!\n * if (process.env.NODE_ENV === 'development') {\n * await dropSchema(env.DB_TEST);\n * console.log('Test database reset completed');\n * }\n * ```\n */\nexport async function dropSchema(d1: D1Database, ...tables: string[]): Promise<void> {\n\tfor (const table of tables) {\n\t\ttry {\n\t\t\tawait d1.prepare(`DROP TABLE IF EXISTS ${table}`).run();\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to drop table ${table}:`, error);\n\t\t}\n\t}\n}\n\n/**\n * Queries the SQLite system catalog to retrieve all user-created tables\n * in the database. This is useful for schema inspection, validation,\n * and debugging purposes.\n *\n * @param d1 - The D1 database instance to inspect\n * @returns Promise resolving to array of table names, sorted alphabetically\n * @throws Returns empty array if query fails or database is inaccessible\n * @example\n * ```typescript\n * const tables = await listTables(env.DB_EAST);\n * console.log('Available tables:', tables);\n * // Output: ['posts', 'shard_mappings', 'users']\n *\n * // Check for specific table\n * if (tables.includes('users')) {\n * console.log('Users table exists');\n * }\n * ```\n */\nexport async function listTables(d1: D1Database): Promise<string[]> {\n\ttry {\n\t\tconst result = await d1.prepare(\"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name\").all();\n\t\treturn result.results.map((row: any) => row.name as string);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Moves a single record from a source D1 database to a target D1 database.\n * This is typically used during shard rebalancing operations when data needs\n * to be redistributed across shards for load balancing.\n *\n * The migration process:\n * 1. Retrieves the complete record from the source database\n * 2. Ensures the target database has the required schema\n * 3. Inserts the record into the target database (using REPLACE for safety)\n * 4. Deletes the record from the source database\n *\n * The operation is atomic from the perspective of each database, but not\n * across databases. If the operation fails partway through, manual cleanup\n * may be required.\n *\n * @param source - Source D1 database containing the record\n * @param target - Target D1 database to receive the record\n * @param primaryKey - Primary key of the record to migrate\n * @param tableName - Name of the table containing the record\n * @returns Promise that resolves when migration is complete\n * @throws {Error} If source record not found, schema creation fails, or database operations fail\n * @example\n * ```typescript\n * // Migrate a user from east to west shard\n * try {\n * await migrateRecord(env.DB_EAST, env.DB_WEST, 'user-123', 'users');\n * console.log('User migration completed successfully');\n * } catch (error) {\n * console.error('Migration failed:', error.message);\n * // May need manual cleanup depending on where it failed\n * }\n *\n * // Migrate a post between shards\n * await migrateRecord(source, target, 'post-456', 'posts');\n * ```\n */\nexport async function migrateRecord(source: D1Database, target: D1Database, primaryKey: string, tableName: string): Promise<void> {\n\tconst sourceRecord = await source.prepare(`SELECT * FROM ${tableName} WHERE id = ?`).bind(primaryKey).first();\n\n\tif (!sourceRecord) {\n\t\tthrow new CollegeDBError(`Record with primary key ${primaryKey} not found in source database`, 'RECORD_NOT_FOUND');\n\t}\n\n\t// Create schema if it doesn't exist in target\n\tif (!(await schemaExists(target, tableName))) {\n\t\tawait createSchema(target, tableName);\n\t}\n\n\t// Get column names\n\tconst columns = Object.keys(sourceRecord);\n\tconst placeholders = columns.map(() => '?').join(', ');\n\tconst values = columns.map((col) => sourceRecord[col as keyof typeof sourceRecord]);\n\n\t// Insert into target database\n\tconst insertSQL = `INSERT OR REPLACE INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;\n\tawait target\n\t\t.prepare(insertSQL)\n\t\t.bind(...values)\n\t\t.run();\n\n\t// Delete from source database\n\tawait source.prepare(`DELETE FROM ${tableName} WHERE id = ?`).bind(primaryKey).run();\n}\n\n/**\n * Scans an existing table to find all primary keys that need to be mapped to shards.\n * This is useful when integrating CollegeDB with an existing database that already\n * contains data. The function assumes the table has an 'id' column as the primary key.\n *\n * @param d1 - The D1 database instance to scan\n * @param tableName - Name of the table to discover primary keys from\n * @param primaryKeyColumn - Name of the primary key column (defaults to 'id')\n * @returns Promise resolving to array of primary key values\n * @throws {Error} If table doesn't exist or database query fails\n * @example\n * ```typescript\n * // Discover all user IDs in an existing users table\n * const userIds = await discoverExistingPrimaryKeys(env.DB_EXISTING, 'users');\n * console.log(`Found ${userIds.length} existing users`);\n *\n * // Discover with custom primary key column\n * const orderIds = await discoverExistingPrimaryKeys(env.DB_ORDERS, 'orders', 'order_id');\n * ```\n */\nexport async function discoverExistingPrimaryKeys(d1: D1Database, tableName: string, primaryKeyColumn: string = 'id'): Promise<string[]> {\n\ttry {\n\t\tconst result = await d1.prepare(`SELECT ${primaryKeyColumn} FROM ${tableName}`).all();\n\t\treturn result.results.map((row: any) => String(row[primaryKeyColumn]));\n\t} catch (error) {\n\t\tthrow new CollegeDBError(`Failed to discover primary keys in table ${tableName}: ${error}`, 'DISCOVERY_FAILED');\n\t}\n}\n\n/**\n * Discovers existing records with additional columns for multi-key mapping support.\n * Scans a table to find primary keys along with username, email, and name columns\n * when they exist, allowing these additional columns to be used as lookup keys.\n *\n * @param d1 - The D1 database instance to scan\n * @param tableName - Name of the table to discover records from\n * @param primaryKeyColumn - Name of the primary key column (defaults to 'id')\n * @returns Promise resolving to array of record data with available columns\n * @throws {Error} If table doesn't exist or database query fails\n * @example\n * ```typescript\n * // Discover all user records with available lookup columns\n * const records = await discoverExistingRecordsWithColumns(env.DB_EXISTING, 'users');\n * console.log(`Found ${records.length} user records`);\n * records.forEach(record => {\n * console.log(`ID: ${record.id}, Email: ${record.email || 'N/A'}`);\n * });\n * ```\n * @since 1.0.4\n */\nexport async function discoverExistingRecordsWithColumns(\n\td1: D1Database,\n\ttableName: string,\n\tprimaryKeyColumn: string = 'id'\n): Promise<Array<{ [key: string]: any }>> {\n\ttry {\n\t\t// First, discover what columns exist in the table\n\t\tconst columnInfo = await d1.prepare(`PRAGMA table_info(${tableName})`).all();\n\t\tconst availableColumns = (columnInfo.results as any[]).map((col) => col.name as string);\n\n\t\t// Build SELECT statement with available columns\n\t\tconst columnsToSelect = [primaryKeyColumn];\n\n\t\t// Add optional columns if they exist\n\t\tif (availableColumns.includes('username')) {\n\t\t\tcolumnsToSelect.push('username');\n\t\t}\n\t\tif (availableColumns.includes('email')) {\n\t\t\tcolumnsToSelect.push('email');\n\t\t}\n\t\tif (availableColumns.includes('name')) {\n\t\t\tcolumnsToSelect.push('name');\n\t\t}\n\n\t\tconst selectQuery = `SELECT ${columnsToSelect.join(', ')} FROM ${tableName}`;\n\t\tconst result = await d1.prepare(selectQuery).all();\n\n\t\treturn result.results as Array<{ [key: string]: any }>;\n\t} catch (error) {\n\t\tthrow new CollegeDBError(`Failed to discover records with columns in table ${tableName}: ${error}`, 'DISCOVERY_FAILED');\n\t}\n}\n\n/**\n * Takes a list of existing primary keys and creates shard mappings for them using\n * the specified allocation strategy. This allows existing data to be integrated\n * into the CollegeDB sharding system without data migration.\n *\n * @param primaryKeys - Array of primary key values to create mappings for\n * @param shardBindings - Array of available shard binding names\n * @param strategy - Allocation strategy to use ('hash', 'round-robin', or 'random')\n * @param mapper - KVShardMapper instance for storing mappings\n * @returns Promise that resolves when all mappings are created\n * @throws {Error} If mapping creation fails\n * @example\n * ```typescript\n * import { KVShardMapper } from './kvmap.js';\n *\n * const mapper = new KVShardMapper(env.KV);\n * const existingIds = await discoverExistingPrimaryKeys(env.DB_EXISTING, 'users');\n * const shards = ['db-east', 'db-west', 'db-central'];\n *\n * await createMappingsForExistingKeys(existingIds, shards, 'hash', mapper);\n * console.log('All existing users mapped to shards');\n * ```\n */\nexport async function createMappingsForExistingKeys(\n\tprimaryKeys: string[],\n\tshardBindings: string[],\n\tstrategy: 'hash' | 'round-robin' | 'random',\n\tmapper: any // KVShardMapper instance\n): Promise<void> {\n\tconst totalShards = shardBindings.length;\n\n\tfor (let i = 0; i < primaryKeys.length; i++) {\n\t\tconst primaryKey = primaryKeys[i]!;\n\t\tlet selectedShard: string;\n\n\t\tswitch (strategy) {\n\t\t\tcase 'hash':\n\t\t\t\tlet hash = 0;\n\t\t\t\tfor (let j = 0; j < primaryKey.length; j++) {\n\t\t\t\t\tconst char = primaryKey.charCodeAt(j);\n\t\t\t\t\thash = (hash << 5) - hash + char;\n\t\t\t\t\thash = hash & hash;\n\t\t\t\t}\n\t\t\t\tconst hashIndex = Math.abs(hash) % totalShards;\n\t\t\t\tselectedShard = shardBindings[hashIndex]!;\n\t\t\t\tbreak;\n\t\t\tcase 'random':\n\t\t\t\tselectedShard = shardBindings[Math.floor(Math.random() * totalShards)]!;\n\t\t\t\tbreak;\n\t\t\tdefault: // round-robin\n\t\t\t\tselectedShard = shardBindings[i % totalShards]!;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tawait mapper.setShardMapping(primaryKey, selectedShard);\n\t}\n}\n\n/**\n * Represents the result of validating a table for sharding.\n * Contains information about the table structure, primary key, record count,\n * and any issues encountered during validation.\n */\nexport type ValidationResult = {\n\tisValid: boolean;\n\ttableName: string;\n\tprimaryKeyColumn: string;\n\trecordCount: number;\n\tissues: string[];\n};\n\n/**\n * Checks if a table exists and has a primary key column that can be used\n * for sharding. Returns information about the table structure and primary key.\n *\n * @param d1 - The D1 database instance to check\n * @param tableName - Name of the table to validate\n * @param primaryKeyColumn - Expected primary key column name (defaults to 'id')\n * @returns Promise resolving to validation result with table info\n * @throws {Error} If table doesn't exist or validation fails\n * @example\n * ```typescript\n * const validation = await validateTableForSharding(env.DB_EXISTING, 'users');\n * if (validation.isValid) {\n * console.log(`Table ${validation.tableName} is ready for sharding`);\n * console.log(`Primary key: ${validation.primaryKeyColumn}`);\n * console.log(`Record count: ${validation.recordCount}`);\n * } else {\n * console.error('Table validation failed:', validation.issues);\n * }\n * ```\n */\nexport async function validateTableForSharding(d1: D1Database, tableName: string, primaryKeyColumn: string): Promise<ValidationResult> {\n\tconst issues: string[] = [];\n\tlet recordCount = 0;\n\n\ttry {\n\t\t// Check if table exists\n\t\tconst tableCheck = await d1.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).bind(tableName).first();\n\n\t\tif (!tableCheck) {\n\t\t\tissues.push(`Table '${tableName}' does not exist`);\n\t\t\treturn {\n\t\t\t\tisValid: false,\n\t\t\t\ttableName,\n\t\t\t\tprimaryKeyColumn,\n\t\t\t\trecordCount: 0,\n\t\t\t\tissues\n\t\t\t} satisfies ValidationResult;\n\t\t}\n\n\t\t// Check if primary key column exists\n\t\tconst columnCheck = await d1.prepare(`PRAGMA table_info(${tableName})`).all();\n\t\tconst hasIdColumn = columnCheck.results.some((col: any) => col.name === primaryKeyColumn && col.pk === 1);\n\n\t\tif (!hasIdColumn) {\n\t\t\tissues.push(`Primary key column '${primaryKeyColumn}' not found or not set as primary key`);\n\t\t}\n\n\t\t// Get record count\n\t\tconst countResult = await d1.prepare(`SELECT COUNT(*) as count FROM ${tableName}`).first();\n\t\trecordCount = (countResult as any)?.count || 0;\n\n\t\tif (recordCount === 0) {\n\t\t\tissues.push(`Table '${tableName}' is empty`);\n\t\t}\n\t} catch (error) {\n\t\tissues.push(`Database validation error: ${error}`);\n\t}\n\n\treturn {\n\t\tisValid: issues.length === 0,\n\t\ttableName,\n\t\tprimaryKeyColumn,\n\t\trecordCount,\n\t\tissues\n\t} satisfies ValidationResult;\n}\n\n/**\n * Configuration options for integrating an existing database with CollegeDB.\n * Allows customization of which tables to process, primary key column,\n * sharding strategy, and whether to add the shard_mappings table.\n */\nexport type IntegrationOptions = {\n\ttables?: string[];\n\tprimaryKeyColumn?: string;\n\tstrategy?: ShardingStrategy;\n\taddShardMappingsTable?: boolean;\n\tdryRun?: boolean;\n\tmigrateOtherColumns?: boolean;\n};\n\n/**\n * Represents the result of integrating an existing database with CollegeDB.\n * Contains information about the success of the integration, shard name,\n * number of tables processed, total records integrated, mappings created,\n * and any issues encountered during the process.\n */\nexport type IntegrationResult = {\n\tsuccess: boolean;\n\tshardName: string;\n\ttablesProcessed: number;\n\ttotalRecords: number;\n\tmappingsCreated: number;\n\tissues: string[];\n};\n\n/**\n * Performs a complete drop-in integration of an existing database.\n * This is the main function for integrating CollegeDB with an existing database\n * that already contains data. It discovers tables, validates them, creates shard\n * mappings, and optionally adds the shard_mappings table if needed.\n *\n * When `migrateOtherColumns` is enabled, the function will also create additional\n * lookup keys for username, email, and name columns if they exist in the table.\n * This allows these fields to be used as lookup keys in addition to the primary key.\n *\n * @param d1 - The existing D1 database to integrate\n * @param shardName - The shard binding name for this database\n * @param mapper - KVShardMapper instance for storing mappings\n * @param options - Configuration options for the integration\n * @param options.migrateOtherColumns - When true, creates additional lookup keys for username, email, and name columns\n * @returns Promise resolving to integration summary\n * @throws {Error} If integration fails\n * @example\n * ```typescript\n * import { KVShardMapper } from './kvmap.js';\n *\n * const mapper = new KVShardMapper(env.KV);\n * const result = await integrateExistingDatabase(env.DB_EXISTING, 'db-existing', mapper, {\n * tables: ['users', 'posts'],\n * strategy: 'hash',\n * addShardMappingsTable: true,\n * migrateOtherColumns: true // Creates additional lookup keys\n * });\n *\n * console.log(`Integrated ${result.totalRecords} records from ${result.tablesProcessed} tables`);\n * // Now users can be looked up by username:john, email:john@example.com, or name:John Doe\n * ```\n */\nexport async function integrateExistingDatabase(\n\td1: D1Database,\n\tshardName: string,\n\tmapper: KVShardMapper,\n\toptions: IntegrationOptions = {}\n): Promise<IntegrationResult> {\n\tconst {\n\t\ttables,\n\t\tprimaryKeyColumn = 'id',\n\t\tstrategy = 'hash',\n\t\taddShardMappingsTable = true,\n\t\tdryRun = false,\n\t\tmigrateOtherColumns = false\n\t} = options;\n\n\tconst issues: string[] = [];\n\tlet tablesProcessed = 0;\n\tlet totalRecords = 0;\n\tlet mappingsCreated = 0;\n\n\ttry {\n\t\t// Discover tables if not specified\n\t\tconst tablesToProcess = tables || (await listTables(d1));\n\n\t\t// Filter out the shard_mappings table if it already exists\n\t\tconst dataTableNames = tablesToProcess.filter((table) => table !== 'shard_mappings');\n\n\t\tfor (const tableName of dataTableNames) {\n\t\t\ttry {\n\t\t\t\t// Validate table\n\t\t\t\tconst validation = await validateTableForSharding(d1, tableName, primaryKeyColumn);\n\n\t\t\t\tif (!validation.isValid) {\n\t\t\t\t\tissues.push(`Table ${tableName}: ${validation.issues.join(', ')}`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (migrateOtherColumns) {\n\t\t\t\t\t// Use the new function to get records with additional columns\n\t\t\t\t\tconst records = await discoverExistingRecordsWithColumns(d1, tableName, primaryKeyColumn);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tissues.push(`Table ${tableName} has no records to process`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!dryRun) {\n\t\t\t\t\t\tfor (const record of records) {\n\t\t\t\t\t\t\tconst primaryKey = String(record[primaryKeyColumn]);\n\n\t\t\t\t\t\t\t// Create additional lookup keys based on available columns\n\t\t\t\t\t\t\tconst additionalKeys: string[] = [];\n\n\t\t\t\t\t\t\tif (record.username && typeof record.username === 'string') {\n\t\t\t\t\t\t\t\tadditionalKeys.push(`username:${record.username}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (record.email && typeof record.email === 'string') {\n\t\t\t\t\t\t\t\tadditionalKeys.push(`email:${record.email}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (record.name && typeof record.name === 'string') {\n\t\t\t\t\t\t\t\tadditionalKeys.push(`name:${record.name}`);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Set the primary mapping with additional lookup keys\n\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName, additionalKeys);\n\t\t\t\t\t\t\tmappingsCreated++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttotalRecords += records.length;\n\t\t\t\t} else {\n\t\t\t\t\t// Original behavior: only use primary keys\n\t\t\t\t\tconst primaryKeys = await discoverExistingPrimaryKeys(d1, tableName, primaryKeyColumn);\n\t\t\t\t\tif (primaryKeys.length === 0) {\n\t\t\t\t\t\tissues.push(`Table ${tableName} has no records to process`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!dryRun) {\n\t\t\t\t\t\tfor (const primaryKey of primaryKeys) {\n\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName);\n\t\t\t\t\t\t\tmappingsCreated++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\ttotalRecords += primaryKeys.length;\n\t\t\t\t}\n\n\t\t\t\ttablesProcessed++;\n\t\t\t} catch (error) {\n\t\t\t\tissues.push(`Failed to process table ${tableName}: ${error}`);\n\t\t\t}\n\t\t}\n\n\t\tif (addShardMappingsTable && !dryRun) {\n\t\t\tconst hasMappingsTable = (await listTables(d1)).includes('shard_mappings');\n\t\t\tif (!hasMappingsTable) {\n\t\t\t\tawait d1\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`\n\t\t\t\t\tCREATE TABLE IF NOT EXISTS shard_mappings (\n\t\t\t\t\t\tprimary_key TEXT PRIMARY KEY,\n\t\t\t\t\t\tshard_name TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t\t);`.trim()\n\t\t\t\t\t)\n\t\t\t\t\t.run();\n\t\t\t}\n\t\t}\n\n\t\t// Add this shard to known shards list\n\t\tif (!dryRun) {\n\t\t\tawait mapper.addKnownShard(shardName);\n\t\t}\n\t} catch (error) {\n\t\tissues.push(`Integration failed: ${error}`);\n\t}\n\n\treturn {\n\t\tsuccess: issues.length === 0 || (issues.length > 0 && tablesProcessed > 0),\n\t\tshardName,\n\t\ttablesProcessed,\n\t\ttotalRecords,\n\t\tmappingsCreated,\n\t\tissues\n\t};\n}\n\n/**\n * Automatically detects if a database needs migration and performs it\n *\n * This function is called automatically by CollegeDB operations to detect\n * existing databases that contain data but haven't been integrated into the\n * sharding system. It performs seamless migration without user intervention.\n *\n * The detection process:\n * 1. Checks if the database has data tables with primary keys\n * 2. Verifies if primary key mappings exist in KV\n * 3. If unmapped data is found, performs automatic integration\n * 4. Caches results to avoid repeated checks\n *\n * When `migrateOtherColumns` is enabled, additional lookup keys will be created\n * for username, email, and name columns if they exist in the tables.\n *\n * @param d1 - The D1 database instance to check and potentially migrate\n * @param shardName - The shard binding name for this database\n * @param config - CollegeDB configuration containing KV and strategy\n * @param options - Optional migration configuration\n * @param options.migrateOtherColumns - When true, creates additional lookup keys for username, email, and name columns\n * @returns Promise resolving to migration result summary\n * @example\n * ```typescript\n * // Called automatically by CollegeDB operations\n * const result = await autoDetectAndMigrate(env.DB_EXISTING, 'db-existing', config, {\n * migrateOtherColumns: true\n * });\n * if (result.migrationPerformed) {\n * console.log(`Auto-migrated ${result.recordsMigrated} records`);\n * }\n * ```\n */\nexport async function autoDetectAndMigrate(\n\td1: D1Database,\n\tshardName: string,\n\tconfig: CollegeDBConfig,\n\toptions: {\n\t\tprimaryKeyColumn?: string;\n\t\ttablesToCheck?: string[];\n\t\tskipCache?: boolean;\n\t\tmaxRecordsToCheck?: number;\n\t\tmigrateOtherColumns?: boolean;\n\t} = {}\n): Promise<{\n\tmigrationNeeded: boolean;\n\tmigrationPerformed: boolean;\n\trecordsMigrated: number;\n\ttablesProcessed: number;\n\tissues: string[];\n}> {\n\tconst { primaryKeyColumn = 'id', tablesToCheck, skipCache = false, maxRecordsToCheck = 1000, migrateOtherColumns = false } = options;\n\n\tconst cacheKey = `${shardName}_migration_check`;\n\n\t// Check cache to avoid repeated migration checks\n\tif (!skipCache && migrationStatusCache.has(cacheKey)) {\n\t\treturn {\n\t\t\tmigrationNeeded: false,\n\t\t\tmigrationPerformed: false,\n\t\t\trecordsMigrated: 0,\n\t\t\ttablesProcessed: 0,\n\t\t\tissues: []\n\t\t};\n\t}\n\n\tconst issues: string[] = [];\n\tlet recordsMigrated = 0;\n\tlet tablesProcessed = 0;\n\tlet migrationNeeded = false;\n\tlet migrationPerformed = false;\n\n\ttry {\n\t\tconst { KVShardMapper } = await import('./kvmap.js');\n\t\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\t\t// Discover tables to check\n\t\tconst allTables = await listTables(d1);\n\t\tconst dataTableNames =\n\t\t\ttablesToCheck ||\n\t\t\tallTables.filter((table) => table !== 'shard_mappings' && !table.startsWith('sqlite_') && table !== 'sqlite_sequence');\n\n\t\tif (dataTableNames.length === 0) {\n\t\t\t// No data tables found, mark as migrated\n\t\t\tmigrationStatusCache.set(cacheKey, true);\n\t\t\treturn {\n\t\t\t\tmigrationNeeded: false,\n\t\t\t\tmigrationPerformed: false,\n\t\t\t\trecordsMigrated: 0,\n\t\t\t\ttablesProcessed: 0,\n\t\t\t\tissues: []\n\t\t\t};\n\t\t}\n\n\t\t// Check each table for unmapped data\n\t\tfor (const tableName of dataTableNames) {\n\t\t\ttry {\n\t\t\t\t// Quick validation\n\t\t\t\tconst validation = await validateTableForSharding(d1, tableName, primaryKeyColumn);\n\t\t\t\tif (!validation.isValid || validation.recordCount === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Sample some primary keys to check if they're mapped\n\t\t\t\tconst sampleSize = Math.min(maxRecordsToCheck, validation.recordCount);\n\t\t\t\tconst sampleKeys = await d1\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`\n\t\t\t\t\tSELECT ${primaryKeyColumn} FROM ${tableName}\n\t\t\t\t\tORDER BY ${primaryKeyColumn}\n\t\t\t\t\tLIMIT ?`.trim()\n\t\t\t\t\t)\n\t\t\t\t\t.bind(sampleSize)\n\t\t\t\t\t.all();\n\n\t\t\t\tlet unmappedCount = 0;\n\t\t\t\tconst keysToCheck = sampleKeys.results.slice(0, 10); // Check first 10 as sample\n\n\t\t\t\tfor (const row of keysToCheck) {\n\t\t\t\t\tconst primaryKey = String((row as any)[primaryKeyColumn]);\n\t\t\t\t\tconst mapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\tif (!mapping) {\n\t\t\t\t\t\tunmappedCount++;\n\t\t\t\t\t\tmigrationNeeded = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (unmappedCount > 0) {\n\t\t\t\t\tif (config.debug) console.log(`Auto-migrating table ${tableName} in shard ${shardName} (${validation.recordCount} records)`);\n\n\t\t\t\t\tif (migrateOtherColumns) {\n\t\t\t\t\t\t// Use multi-column discovery for migration with additional lookup keys\n\t\t\t\t\t\tconst allRecords = await discoverExistingRecordsWithColumns(d1, tableName, primaryKeyColumn);\n\n\t\t\t\t\t\t// Create mappings for all unmapped keys with additional lookup keys\n\t\t\t\t\t\tlet newMappings = 0;\n\t\t\t\t\t\tfor (const record of allRecords) {\n\t\t\t\t\t\t\tconst primaryKey = String(record[primaryKeyColumn]);\n\t\t\t\t\t\t\tconst existingMapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\t\t\tif (!existingMapping) {\n\t\t\t\t\t\t\t\t// Create additional lookup keys based on available columns\n\t\t\t\t\t\t\t\tconst additionalKeys: string[] = [];\n\n\t\t\t\t\t\t\t\tif (record.username && typeof record.username === 'string') {\n\t\t\t\t\t\t\t\t\tadditionalKeys.push(`username:${record.username}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (record.email && typeof record.email === 'string') {\n\t\t\t\t\t\t\t\t\tadditionalKeys.push(`email:${record.email}`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (record.name && typeof record.name === 'string') {\n\t\t\t\t\t\t\t\t\tadditionalKeys.push(`name:${record.name}`);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// Set the primary mapping with additional lookup keys\n\t\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName, additionalKeys);\n\t\t\t\t\t\t\t\tnewMappings++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trecordsMigrated += newMappings;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Original behavior: only use primary keys\n\t\t\t\t\t\tconst allPrimaryKeys = await discoverExistingPrimaryKeys(d1, tableName, primaryKeyColumn);\n\n\t\t\t\t\t\t// Create mappings for all unmapped keys\n\t\t\t\t\t\tlet newMappings = 0;\n\t\t\t\t\t\tfor (const primaryKey of allPrimaryKeys) {\n\t\t\t\t\t\t\tconst existingMapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\t\t\tif (!existingMapping) {\n\t\t\t\t\t\t\t\tawait mapper.setShardMapping(primaryKey, shardName);\n\t\t\t\t\t\t\t\tnewMappings++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trecordsMigrated += newMappings;\n\t\t\t\t\t}\n\n\t\t\t\t\ttablesProcessed++;\n\t\t\t\t\tmigrationPerformed = true;\n\n\t\t\t\t\tif (config.debug) console.log(`Auto-migrated ${recordsMigrated} records from table ${tableName}`);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tissues.push(`Auto-migration failed for table ${tableName}: ${error}`);\n\t\t\t}\n\t\t}\n\n\t\t// Add shard to known shards if migration was performed\n\t\tif (migrationPerformed) {\n\t\t\tawait mapper.addKnownShard(shardName);\n\n\t\t\t// Add shard_mappings table if it doesn't exist\n\t\t\tconst hasMappingsTable = allTables.includes('shard_mappings');\n\t\t\tif (!hasMappingsTable) {\n\t\t\t\tawait d1\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`CREATE TABLE IF NOT EXISTS shard_mappings (\n\t\t\t\t\t\tprimary_key TEXT PRIMARY KEY,\n\t\t\t\t\t\tshard_name TEXT NOT NULL,\n\t\t\t\t\t\tcreated_at INTEGER NOT NULL,\n\t\t\t\t\t\tupdated_at INTEGER NOT NULL\n\t\t\t\t\t);\n\t\t\t\t`\n\t\t\t\t\t)\n\t\t\t\t\t.run();\n\t\t\t}\n\t\t}\n\n\t\t// Cache the result to avoid repeated checks\n\t\tmigrationStatusCache.set(cacheKey, true);\n\n\t\tif (migrationPerformed && config.debug) {\n\t\t\tconsole.log(`Auto-migration completed for shard ${shardName}: ${recordsMigrated} records from ${tablesProcessed} tables`);\n\t\t}\n\t} catch (error) {\n\t\tissues.push(`Auto-migration error: ${error}`);\n\t}\n\n\treturn {\n\t\tmigrationNeeded,\n\t\tmigrationPerformed,\n\t\trecordsMigrated,\n\t\ttablesProcessed,\n\t\tissues\n\t};\n}\n\n/**\n * Performs a lightweight check to determine if a database contains\n * existing data that hasn't been mapped to the sharding system.\n * This is used internally to trigger automatic migration.\n *\n * @param d1 - The D1 database instance to check\n * @param shardName - The shard binding name\n * @param config - CollegeDB configuration\n * @returns Promise resolving to true if migration is needed\n * @example\n * ```typescript\n * const needsMigration = await checkMigrationNeeded(env.DB, 'db-main', config);\n * if (needsMigration) {\n * console.log('Database contains unmapped data');\n * }\n * ```\n */\nexport async function checkMigrationNeeded(d1: D1Database, shardName: string, config: CollegeDBConfig): Promise<boolean> {\n\tconst cacheKey = `${shardName}_migration_check`;\n\n\t// Check cache first (but not during tests with skip cache)\n\tif (migrationStatusCache.has(cacheKey)) {\n\t\treturn false; // Already checked/migrated\n\t}\n\n\ttry {\n\t\t// Check if shard_mappings table exists as indicator of previous migration\n\t\tconst tables = await listTables(d1);\n\t\tconst hasShardMappingsTable = tables.includes('shard_mappings');\n\n\t\tif (hasShardMappingsTable) {\n\t\t\t// If shard_mappings table exists, this database has been processed before\n\t\t\tmigrationStatusCache.set(cacheKey, true);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst { KVShardMapper } = await import('./kvmap.js');\n\t\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\t\t// Quick check: look for any table with data\n\t\tconst dataTableNames = tables.filter(\n\t\t\t(table) => table !== 'shard_mappings' && !table.startsWith('sqlite_') && table !== 'sqlite_sequence'\n\t\t);\n\n\t\tfor (const tableName of dataTableNames.slice(0, 3)) {\n\t\t\t// Check first 3 tables only\n\t\t\ttry {\n\t\t\t\t// Check if table has records\n\t\t\t\tconst countResult = await d1.prepare(`SELECT COUNT(*) as count FROM ${tableName} LIMIT 1`).first();\n\t\t\t\tconst recordCount = (countResult as any)?.count || 0;\n\n\t\t\t\tif (recordCount > 0) {\n\t\t\t\t\t// Sample one record to see if it's mapped\n\t\t\t\t\tconst sampleRecord = await d1.prepare(`SELECT id FROM ${tableName} LIMIT 1`).first();\n\t\t\t\t\tif (sampleRecord) {\n\t\t\t\t\t\tconst primaryKey = String((sampleRecord as any).id);\n\t\t\t\t\t\tconst mapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\t\t\tif (!mapping) {\n\t\t\t\t\t\t\treturn true; // Found unmapped data\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Skip tables that don't have 'id' column or have other issues\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t} catch {\n\t\treturn false; // Assume no migration needed if check fails\n\t}\n}\n\n/**\n * Clears the migration status cache\n *\n * Resets the internal cache used to track which databases have been\n * checked for migration. Useful for testing or forcing re-checks.\n *\n * @example\n * ```typescript\n * // Force re-check of all databases\n * clearMigrationCache();\n * ```\n */\nexport function clearMigrationCache(): void {\n\tmigrationStatusCache.clear();\n}\n\n/**\n * Clears a specific migration cache entry\n *\n * Resets the cache for a specific shard, forcing re-check on next\n * migration detection call.\n *\n * @param shardName - The shard name to clear from cache\n * @example\n * ```typescript\n * // Force re-check of specific shard\n * clearShardMigrationCache('db-auto');\n * ```\n */\nexport function clearShardMigrationCache(shardName: string): void {\n\tconst cacheKey = `${shardName}_migration_check`;\n\tmigrationStatusCache.delete(cacheKey);\n}\n",
|
|
8
|
+
"/**\n * @fileoverview Main routing and query distribution logic for CollegeDB\n *\n * This module provides the core functionality for routing database queries to the\n * appropriate D1 shard based on primary key mappings. It handles shard selection,\n * database routing, and provides a unified API for CRUD operations across multiple\n * distributed D1 databases.\n *\n * Key responsibilities:\n * - Initialize and manage the global CollegeDB configuration\n * - Route queries to appropriate shards based on primary key mappings\n * - Implement shard allocation strategies (round-robin, random, hash-based)\n * - Provide unified CRUD operations across distributed shards\n * - Coordinate with Durable Objects for centralized shard management\n * - Handle shard rebalancing and data migration\n *\n * @example\n * ```typescript\n * import { initialize, insert, first, run } from 'collegedb';\n *\n * // Initialize the system\n * initialize({\n * kv: env.KV,\n * coordinator: env.ShardCoordinator,\n * shards: {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST\n * },\n * strategy: 'hash'\n * });\n *\n * // Insert a record (automatically routed to appropriate shard)\n * await run('user-123', 'INSERT INTO users (id, name) VALUES (?, ?)', ['user-123', 'John']);\n *\n * // Query the record (routed to same shard)\n * const result = await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);\n * ```\n *\n * @author CollegeDB Team\n * @since 1.0.0\n */\n\nimport type { D1Database, D1PreparedStatement, D1Result, Request } from '@cloudflare/workers-types';\nimport { CollegeDBError } from './errors.js';\nimport { KVShardMapper } from './kvmap.js';\nimport type { CollegeDBConfig, D1Region, OperationType, ShardLocation, ShardStats, ShardingStrategy } from './types.js';\n\n/**\n * Global configuration for the collegedb instance\n *\n * Stores the system-wide configuration including KV namespace, available shards,\n * coordinator settings, and allocation strategy. Must be initialized before\n * any routing operations can be performed.\n *\n * @private\n */\nlet globalConfig: CollegeDBConfig | null = null;\n\n/**\n * Sets up the global configuration for the CollegeDB system. This must be called\n * before any other operations can be performed. The configuration includes KV\n * storage, available D1 shards, optional coordinator, and allocation strategy.\n *\n * This will also automatically detect and migrate existing databases without requiring\n * additional setup. If shards contain existing data with primary keys, CollegeDB\n * will automatically create the necessary mappings for seamless operation.\n *\n * @param config - Configuration object containing all necessary bindings and settings\n * @throws {Error} If configuration is invalid or required bindings are missing\n * @example\n * ```typescript\n * // Basic setup with multiple shards - auto-migration happens automatically\n * initialize({\n * kv: env.KV,\n * shards: {\n * 'db-primary': env.DB_PRIMARY, // Existing DB with data\n * 'db-secondary': env.DB_SECONDARY // Another existing DB\n * },\n * strategy: 'round-robin'\n * });\n * // Existing data is now automatically accessible via CollegeDB!\n *\n * // Advanced setup with coordinator\n * initialize({\n * kv: env.KV,\n * coordinator: env.ShardCoordinator,\n * shards: {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST,\n * 'db-central': env.DB_CENTRAL\n * },\n * strategy: 'hash'\n * });\n * ```\n */\nexport function initialize(config: CollegeDBConfig) {\n\tglobalConfig = config;\n\n\tif (config.shards && Object.keys(config.shards).length > 0 && !config.disableAutoMigration) {\n\t\tperformAutoMigration(config).catch((error) => {\n\t\t\tconsole.warn('Background auto-migration failed:', error);\n\t\t});\n\t}\n}\n\n/**\n * Sets up the global configuration for the CollegeDB system asynchronously.\n * This must be called before any other operations can be performed. The\n * configuration includes KVstorage, available D1 shards, optional coordinator,\n * and allocation strategy.\n *\n * This will also automatically detect and migrate existing databases without requiring\n * additional setup. If shards contain existing data with primary keys, CollegeDB\n * will automatically create the necessary mappings for seamless operation.\n *\n * Compared to `initialize`, this method waits for the background check to finish.\n *\n * @param config - Configuration object containing all necessary bindings and settings\n * @throws {Error} If configuration is invalid or required bindings are missing\n * @example\n * ```typescript\n * // Basic setup with multiple shards - auto-migration happens automatically\n * initializeAsync({\n * kv: env.KV,\n * shards: {\n * 'db-primary': env.DB_PRIMARY, // Existing DB with data\n * 'db-secondary': env.DB_SECONDARY // Another existing DB\n * },\n * strategy: 'round-robin'\n * });\n * // Existing data is now automatically accessible via CollegeDB!\n *\n * // Advanced setup with coordinator\n * initializeAsync({\n * kv: env.KV,\n * coordinator: env.ShardCoordinator,\n * shards: {\n * 'db-east': env.DB_EAST,\n * 'db-west': env.DB_WEST,\n * 'db-central': env.DB_CENTRAL\n * },\n * strategy: 'hash'\n * });\n * ```\n */\nexport async function initializeAsync(config: CollegeDBConfig) {\n\tglobalConfig = config;\n\n\tif (config.shards && Object.keys(config.shards).length > 0 && !config.disableAutoMigration)\n\t\ttry {\n\t\t\tawait performAutoMigration(config);\n\t\t} catch (error) {\n\t\t\tconsole.warn('Auto migration failed:', error);\n\t\t}\n}\n\n/**\n * Initializes the configuration and then performs a callback once the configuration\n * has finished initializing.\n *\n * @param config - CollegeDB Configuration\n * @param callback - The callback to perform after the initialization\n * @returns The result of the callback\n * @example\n * ```\n * import { collegedb, first } from 'collegedb'\n *\n * const result = collegedb({\n * kv: env.KV,\n * shards: {\n * 'db-primary': env.DB_PRIMARY, // Existing DB with data\n * 'db-secondary': env.DB_SECONDARY // Another existing DB\n * },\n * strategy: 'hash'\n * }, async () => {\n * return await first('user-123', 'SELECT * FROM users WHERE id = ?', ['user-123']);\n * });\n * ```\n */\nexport async function collegedb<T>(config: CollegeDBConfig, callback: () => T) {\n\tawait initializeAsync(config);\n\treturn await callback();\n}\n\n/**\n * Performs automatic migration detection for all shards in the background\n *\n * This function runs asynchronously after initialization to check all configured\n * shards for existing data that needs migration. It's designed to be non-blocking\n * and won't interfere with immediate database operations.\n *\n * @private\n * @param config - CollegeDB configuration\n */\nasync function performAutoMigration(config: CollegeDBConfig): Promise<void> {\n\ttry {\n\t\tconst { autoDetectAndMigrate } = await import('./migrations.js');\n\t\tconst shardNames = Object.keys(config.shards);\n\n\t\tconsole.log(`🔍 Checking ${shardNames.length} shards for existing data...`);\n\n\t\t// Check each shard for migration needs\n\t\tconst migrationPromises = shardNames.map(async (shardName) => {\n\t\t\tconst database = config.shards[shardName];\n\t\t\tif (!database) return null;\n\n\t\t\ttry {\n\t\t\t\tconst result = await autoDetectAndMigrate(database, shardName, config, {\n\t\t\t\t\tmaxRecordsToCheck: 1000\n\t\t\t\t});\n\n\t\t\t\treturn {\n\t\t\t\t\tshardName,\n\t\t\t\t\t...result\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(`Auto-migration failed for shard ${shardName}:`, error);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\n\t\tconst results = await Promise.all(migrationPromises);\n\t\tconst successfulMigrations = results.filter((r) => r?.migrationPerformed);\n\n\t\tif (config.debug) {\n\t\t\tif (successfulMigrations.length > 0) {\n\t\t\t\tconst totalRecords = successfulMigrations.reduce((sum, r) => sum + (r?.recordsMigrated || 0), 0);\n\t\t\t\tconsole.log(`🎉 Auto-migration completed! Migrated ${totalRecords} records across ${successfulMigrations.length} shards`);\n\t\t\t\tsuccessfulMigrations.forEach((result) => {\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\tconsole.log(` ✅ ${result.shardName}: ${result.recordsMigrated} records from ${result.tablesProcessed} tables`);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconsole.log('✅ All shards ready - no migration needed');\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.warn('Background auto-migration setup failed:', error);\n\t}\n}\n\n/**\n * Resets the global configuration (for testing purposes only)\n *\n * @private\n * @internal\n */\nexport function resetConfig(): void {\n\tglobalConfig = null;\n}\n\n/**\n * Gets the global configuration, throwing an error if not initialized\n *\n * Internal utility function that retrieves the global configuration and\n * ensures the system has been properly initialized before performing\n * any operations.\n *\n * @private\n * @returns The global CollegeDB configuration\n * @throws {Error} If initialize() has not been called yet\n */\nfunction getConfig(): CollegeDBConfig {\n\tif (!globalConfig) {\n\t\tthrow new CollegeDBError('CollegeDB not initialized. Call initialize() first.', 'NOT_INITIALIZED');\n\t}\n\treturn globalConfig;\n}\n\n/**\n * Determines the operation type from a SQL statement\n * @private\n * @param sql - The SQL statement to analyze\n * @returns The operation type ('read' for SELECT, 'write' for INSERT/UPDATE/DELETE)\n */\nfunction getOperationType(sql: string): OperationType {\n\tconst sql0 = sql.trim().toUpperCase();\n\n\tif (\n\t\tsql0.startsWith('SELECT') ||\n\t\tsql0.startsWith('VALUES') ||\n\t\tsql0.startsWith('TABLE') ||\n\t\tsql0.startsWith('PRAGMA') ||\n\t\tsql0.startsWith('EXPLAIN') ||\n\t\tsql0.startsWith('WITH') ||\n\t\tsql0.startsWith('SHOW')\n\t) {\n\t\treturn 'read';\n\t}\n\n\t// All other operations (INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.) are considered writes\n\treturn 'write';\n}\n\n/**\n * Resolves the effective sharding strategy based on configuration and operation type\n * @private\n * @param config - CollegeDB configuration\n * @param type - The type of operation being performed\n * @returns The effective sharding strategy to use\n */\nfunction resolveStrategy(config: CollegeDBConfig, type: OperationType): ShardingStrategy {\n\tconst strategy = config.strategy || 'hash';\n\n\tif (typeof strategy === 'string') {\n\t\treturn strategy;\n\t}\n\n\treturn strategy[type];\n}\n\n/**\n * Calculates the relative distance between two D1 regions for location-based sharding.\n * Lower values indicate closer regions with better expected latency.\n *\n * @private\n * @param from - Source region\n * @param to - Target region\n * @returns Relative distance score (lower is better)\n */\nfunction calculateRegionDistance(from: D1Region, to: D1Region): number {\n\t// Same region = optimal\n\tif (from === to) return 0;\n\n\t// Define region coordinates (approximate)\n\tconst regionCoords: Record<D1Region, { lat: number; lon: number }> = {\n\t\twnam: { lat: 37.7749, lon: -122.4194 }, // San Francisco\n\t\tenam: { lat: 40.7128, lon: -74.006 }, // New York\n\t\tweur: { lat: 51.5074, lon: -0.1278 }, // London\n\t\teeur: { lat: 52.52, lon: 13.405 }, // Berlin\n\t\tapac: { lat: 35.6762, lon: 139.6503 }, // Tokyo\n\t\toc: { lat: -33.8688, lon: 151.2093 }, // Sydney\n\t\tme: { lat: 25.2048, lon: 55.2708 }, // Dubai\n\t\taf: { lat: -26.2041, lon: 28.0473 } // Johannesburg\n\t};\n\n\tconst fromCoord = regionCoords[from];\n\tconst toCoord = regionCoords[to];\n\n\t// Simple Euclidean distance calculation\n\tconst latDiff = fromCoord.lat - toCoord.lat;\n\tconst lonDiff = fromCoord.lon - toCoord.lon;\n\treturn Math.sqrt(latDiff * latDiff + lonDiff * lonDiff);\n}\n\n/**\n * Determines the closest D1 region based on an IP address.\n * Uses IP geolocation to estimate the user's location and find the nearest D1 region.\n *\n * This function uses Cloudflare's CF object which provides geolocation data\n * in Cloudflare Workers environment. Falls back to 'wnam' if geolocation fails.\n *\n * @param request - The incoming Request object (contains CF geolocation data in Cloudflare Workers)\n * @returns The closest D1Region based on IP geolocation\n * @example\n * ```typescript\n * // In a Cloudflare Worker\n * export default {\n * async fetch(request: Request, env: Env) {\n * const userRegion = getClosestRegionFromIP(request);\n *\n * initialize({\n * kv: env.KV,\n * strategy: 'location',\n * targetRegion: userRegion, // Automatically optimized for user location\n * shardLocations: { ... },\n * shards: { ... }\n * });\n * }\n * };\n * ```\n */\nexport function getClosestRegionFromIP(request: Request): D1Region {\n\tconst cf = request.cf;\n\n\tif (!cf || !cf.country) {\n\t\treturn 'wnam';\n\t}\n\n\tconst country = cf.country as string;\n\tconst continent = cf.continent as string;\n\n\t// Western North America\n\tif (['US', 'CA', 'MX'].includes(country)) {\n\t\t// Further refine by region/state if available\n\t\tconst region = (cf.region || cf.regionCode || '') as string;\n\t\tconst timezone = (cf.timezone || '') as string;\n\n\t\t// West Coast indicators\n\t\tif (\n\t\t\tregion.includes('CA') ||\n\t\t\tregion.includes('WA') ||\n\t\t\tregion.includes('OR') ||\n\t\t\tregion.includes('NV') ||\n\t\t\tregion.includes('AZ') ||\n\t\t\tregion.includes('UT') ||\n\t\t\ttimezone.includes('Pacific') ||\n\t\t\ttimezone.includes('America/Los_Angeles')\n\t\t) {\n\t\t\treturn 'wnam';\n\t\t}\n\n\t\t// East Coast and Central - default to Eastern North America\n\t\treturn 'enam';\n\t}\n\n\t// Eastern North America (broader North America)\n\tif (['GL', 'PM', 'BM'].includes(country)) {\n\t\treturn 'enam';\n\t}\n\n\t// Western Europe\n\tif (['GB', 'IE', 'FR', 'ES', 'PT', 'NL', 'BE', 'LU', 'CH', 'AT', 'IT'].includes(country)) {\n\t\treturn 'weur';\n\t}\n\n\t// Eastern Europe\n\tif (\n\t\t[\n\t\t\t'DE',\n\t\t\t'PL',\n\t\t\t'CZ',\n\t\t\t'SK',\n\t\t\t'HU',\n\t\t\t'SI',\n\t\t\t'HR',\n\t\t\t'BA',\n\t\t\t'RS',\n\t\t\t'ME',\n\t\t\t'MK',\n\t\t\t'AL',\n\t\t\t'BG',\n\t\t\t'RO',\n\t\t\t'MD',\n\t\t\t'UA',\n\t\t\t'BY',\n\t\t\t'LT',\n\t\t\t'LV',\n\t\t\t'EE',\n\t\t\t'FI',\n\t\t\t'SE',\n\t\t\t'NO',\n\t\t\t'DK',\n\t\t\t'IS'\n\t\t].includes(country)\n\t) {\n\t\treturn 'eeur';\n\t}\n\n\t// Russia - closer to Eastern Europe for most population centers\n\tif (country === 'RU') {\n\t\treturn 'eeur';\n\t}\n\n\t// Asia Pacific\n\tif (['JP', 'KR', 'CN', 'HK', 'TW', 'MO', 'MN', 'KP'].includes(country)) {\n\t\treturn 'apac';\n\t}\n\n\t// Southeast Asia and South Asia -> APAC\n\tif (\n\t\t['TH', 'VN', 'SG', 'MY', 'ID', 'PH', 'BN', 'KH', 'LA', 'MM', 'TL', 'IN', 'PK', 'BD', 'LK', 'NP', 'BT', 'MV', 'AF'].includes(country)\n\t) {\n\t\treturn 'apac';\n\t}\n\n\t// Oceania\n\tif (['AU', 'NZ', 'PG', 'FJ', 'NC', 'VU', 'SB', 'WS', 'TO', 'KI', 'NR', 'PW', 'FM', 'MH', 'TV'].includes(country)) {\n\t\treturn 'oc';\n\t}\n\n\t// Middle East\n\tif (['AE', 'SA', 'QA', 'KW', 'BH', 'OM', 'YE', 'IQ', 'IR', 'SY', 'LB', 'JO', 'IL', 'PS', 'TR', 'CY'].includes(country)) {\n\t\treturn 'me';\n\t}\n\n\t// Africa\n\tif (continent === 'AF' || ['EG', 'LY', 'TN', 'DZ', 'MA', 'SD', 'SS', 'ET', 'ER', 'DJ', 'SO'].includes(country)) {\n\t\treturn 'af';\n\t}\n\n\t// Central Asia -> closer to Eastern Europe\n\tif (['KZ', 'UZ', 'TM', 'TJ', 'KG'].includes(country)) {\n\t\treturn 'eeur';\n\t}\n\n\t// South America -> geographically closer to Eastern North America\n\tif (continent === 'SA' || ['BR', 'AR', 'CL', 'PE', 'CO', 'VE', 'EC', 'BO', 'PY', 'UY', 'GY', 'SR', 'GF'].includes(country)) {\n\t\treturn 'enam';\n\t}\n\n\t// Central America and Caribbean -> Eastern North America\n\tif (\n\t\t['GT', 'BZ', 'SV', 'HN', 'NI', 'CR', 'PA', 'CU', 'JM', 'HT', 'DO', 'PR', 'TT', 'BB', 'GD', 'VC', 'LC', 'DM', 'AG', 'KN'].includes(\n\t\t\tcountry\n\t\t)\n\t) {\n\t\treturn 'enam';\n\t}\n\n\t// Default fallback - Western North America (major Cloudflare hub)\n\treturn 'wnam';\n}\n\n/**\n * Selects the optimal shard for location-based allocation strategy.\n * Prioritizes shards in the target region, then nearby regions by distance.\n *\n * @private\n * @param targetRegion - The preferred region for allocation\n * @param availableShards - List of available shard names\n * @param shardLocations - Geographic locations of each shard\n * @param primaryKey - The primary key being allocated (for consistent tiebreaking)\n * @returns Selected shard name\n */\nfunction selectShardByLocation(\n\ttargetRegion: D1Region,\n\tavailableShards: string[],\n\tshardLocations: Record<string, ShardLocation>,\n\tprimaryKey: string\n): string {\n\t// Filter shards that have location information\n\tconst locatedShards = availableShards.filter((shard) => shardLocations[shard]);\n\n\tif (locatedShards.length === 0) {\n\t\t// Fallback to hash if no location info available\n\t\tlet hash = 0;\n\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\thash = (hash << 5) - hash + char;\n\t\t\thash = hash & hash;\n\t\t}\n\t\tconst index = Math.abs(hash) % availableShards.length;\n\t\treturn availableShards[index]!;\n\t}\n\n\t// Calculate distances and priorities\n\tconst shardScores = locatedShards.map((shard) => {\n\t\tconst location = shardLocations[shard]!;\n\t\tconst distance = calculateRegionDistance(targetRegion, location.region);\n\t\tconst priority = location.priority || 1;\n\n\t\t// Lower score is better (distance penalty, priority bonus)\n\t\tconst score = distance - priority * 0.1;\n\n\t\treturn { shard, score, distance, priority };\n\t});\n\n\t// Sort by score (lower is better)\n\tshardScores.sort((a, b) => a.score - b.score);\n\n\t// For ties in the best score range, use consistent hashing\n\tconst bestScore = shardScores[0]!.score;\n\tconst bestShards = shardScores.filter((s) => Math.abs(s.score - bestScore) < 0.01);\n\n\tif (bestShards.length === 1) {\n\t\treturn bestShards[0]!.shard;\n\t}\n\n\t// Consistent selection among best candidates\n\tlet hash = 0;\n\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\tconst char = primaryKey.charCodeAt(i);\n\t\thash = (hash << 5) - hash + char;\n\t\thash = hash & hash;\n\t}\n\tconst index = Math.abs(hash) % bestShards.length;\n\treturn bestShards[index]!.shard;\n}\n\n/**\n * Gets or allocates a shard for a primary key with operation-specific strategy\n *\n * This is the core routing function that determines which shard should handle\n * a given primary key. If a mapping already exists, it returns the existing\n * shard. If not, it allocates a new shard using the configured strategy.\n *\n * Allocation strategies:\n * - **round-robin**: Cycles through shards in order (with coordinator)\n * - **random**: Randomly selects from available shards\n * - **hash**: Uses consistent hashing for deterministic assignment\n * - **location**: Selects shards based on geographic proximity to target region\n *\n * The function prefers using the Durable Object coordinator when available\n * for centralized allocation decisions, falling back to local strategies\n * when the coordinator is unavailable.\n *\n * @private\n * @param primaryKey - The primary key to route\n * @param operationType - The type of operation (read/write) for mixed strategy support\n * @returns Promise resolving to the shard binding name\n * @throws {Error} If no shards are configured or allocation fails\n * @example\n * ```typescript\n * // This function is called internally by CRUD operations\n * const readShard = await getShardForKey('user-123', 'read');\n * const writeShard = await getShardForKey('user-123', 'write');\n * console.log(`User 123 reads from: ${readShard}, writes to: ${writeShard}`);\n * ```\n */\nasync function getShardForKey(primaryKey: string, operationType: OperationType = 'write'): Promise<string> {\n\tconst config = getConfig();\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\t// Check if mapping already exists\n\tconst existingMapping = await mapper.getShardMapping(primaryKey);\n\tif (existingMapping) {\n\t\treturn existingMapping.shard;\n\t}\n\n\t// Before allocating a new shard, check if any existing shards contain this key\n\t// and perform automatic migration if needed\n\tconst availableShards = Object.keys(config.shards);\n\tif (availableShards.length === 0) {\n\t\tthrow new CollegeDBError('No shards configured', 'NO_SHARDS');\n\t}\n\n\t// Check existing shards for unmapped data containing this primary key\n\tfor (const shardName of availableShards) {\n\t\tconst database = config.shards[shardName];\n\t\tif (!database) continue;\n\n\t\ttry {\n\t\t\t// Quick check if this primary key exists in any table in this shard\n\t\t\tconst { autoDetectAndMigrate } = await import('./migrations.js');\n\t\t\tconst migrationResult = await autoDetectAndMigrate(database, shardName, config, {\n\t\t\t\tmaxRecordsToCheck: 100 // Limit check for performance\n\t\t\t});\n\n\t\t\tif (migrationResult.migrationPerformed) {\n\t\t\t\t// Re-check mapping after migration\n\t\t\t\tconst newMapping = await mapper.getShardMapping(primaryKey);\n\t\t\t\tif (newMapping) {\n\t\t\t\t\treturn newMapping.shard;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Don't fail the operation if auto-migration fails\n\t\t\tconsole.warn(`Auto-migration check failed for shard ${shardName}:`, error);\n\t\t}\n\t}\n\n\t// If no existing mapping found after auto-migration, allocate a new shard\n\tlet selectedShard: string;\n\n\t// Resolve the effective strategy for this operation type\n\tconst effectiveStrategy = resolveStrategy(config, operationType);\n\n\t// Use coordinator if available for allocation\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tconst response = await coordinator.fetch('http://coordinator/allocate', {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tprimaryKey,\n\t\t\t\t\tstrategy: effectiveStrategy, // Use resolved strategy instead of config.strategy\n\t\t\t\t\toperationType, // Pass operation type for coordinator awareness\n\t\t\t\t\ttargetRegion: config.targetRegion,\n\t\t\t\t\tshardLocations: config.shardLocations\n\t\t\t\t})\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst result = (await response.json()) as { shard: string };\n\t\t\t\tselectedShard = result.shard;\n\t\t\t} else {\n\t\t\t\t// Fallback to simple round-robin\n\t\t\t\tselectedShard = availableShards[Math.floor(Math.random() * availableShards.length)]!;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('Coordinator allocation failed, falling back to local strategy:', error);\n\t\t\tselectedShard = availableShards[Math.floor(Math.random() * availableShards.length)]!;\n\t\t}\n\t} else {\n\t\t// Simple allocation strategy without coordinator\n\t\tswitch (effectiveStrategy) {\n\t\t\tcase 'hash':\n\t\t\t\tlet hash = 0;\n\t\t\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\t\t\thash = (hash << 5) - hash + char;\n\t\t\t\t\thash = hash & hash;\n\t\t\t\t}\n\t\t\t\tconst index = Math.abs(hash) % availableShards.length;\n\t\t\t\tselectedShard = availableShards[index] || availableShards[0]!;\n\t\t\t\tbreak;\n\t\t\tcase 'location':\n\t\t\t\tif (!config.targetRegion) {\n\t\t\t\t\tconsole.warn('Location strategy requires targetRegion in config, falling back to hash');\n\t\t\t\t\t// Fallback to hash\n\t\t\t\t\tlet fallbackHash = 0;\n\t\t\t\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\t\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\t\t\t\tfallbackHash = (fallbackHash << 5) - fallbackHash + char;\n\t\t\t\t\t\tfallbackHash = fallbackHash & fallbackHash;\n\t\t\t\t\t}\n\t\t\t\t\tconst fallbackIndex = Math.abs(fallbackHash) % availableShards.length;\n\t\t\t\t\tselectedShard = availableShards[fallbackIndex] || availableShards[0]!;\n\t\t\t\t} else {\n\t\t\t\t\tselectedShard = selectShardByLocation(config.targetRegion, availableShards, config.shardLocations || {}, primaryKey);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'random':\n\t\t\t\tselectedShard = availableShards[Math.floor(Math.random() * availableShards.length)] || availableShards[0]!;\n\t\t\t\tbreak;\n\t\t\tdefault: // round-robin\n\t\t\t\tselectedShard = availableShards[0]!; // Simplified without state\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Store the mapping\n\tawait mapper.setShardMapping(primaryKey, selectedShard);\n\treturn selectedShard;\n}\n\n/**\n * Gets the D1 database instance for a primary key with operation-specific routing\n *\n * Resolves the primary key to its assigned shard and returns the corresponding\n * D1 database instance. This function handles the complete routing process\n * from primary key to database connection, with support for different strategies\n * based on operation type.\n *\n * @private\n * @param primaryKey - The primary key to route\n * @param operationType - The type of operation (read/write) for mixed strategy support\n * @returns Promise resolving to the D1 database instance\n * @throws {Error} If shard routing fails or database instance not found\n */\nasync function getDatabase(primaryKey: string, operationType: OperationType = 'write'): Promise<D1Database> {\n\tconst config = getConfig();\n\tconst shard = await getShardForKey(primaryKey, operationType);\n\tconst database = config.shards[shard];\n\n\tif (!database) {\n\t\tthrow new CollegeDBError(`Shard ${shard} not found in configuration`, 'SHARD_NOT_FOUND');\n\t}\n\n\treturn database;\n}\n\n/**\n * Creates the database schema in the specified D1 database\n *\n * @param d1 - The D1 database instance to create schema in\n * @param schema - The SQL schema definition to execute\n * @returns Promise that resolves when schema creation is complete\n * @throws {Error} If schema creation fails\n * @example\n * ```typescript\n * const userSchema = `\n * CREATE TABLE users (\n * id TEXT PRIMARY KEY,\n * name TEXT NOT NULL,\n * email TEXT UNIQUE\n * );\n * `;\n * await createSchema(env.DB_NEW_SHARD, userSchema);\n * ```\n */\nexport async function createSchema(d1: D1Database, schema: string): Promise<void> {\n\tconst { createSchema: createSchemaImpl } = await import('./migrations.js');\n\tawait createSchemaImpl(d1, schema);\n}\n\n/**\n * Prepares a SQL statement for execution with operation-aware routing.\n *\n * @param key - The primary key to route the query\n * @param sql - The SQL statement to prepare\n * @returns Promise that resolves to a prepared statement\n * @throws {Error} If preparation fails\n */\nexport async function prepare(key: string, sql: string): Promise<D1PreparedStatement> {\n\tconst operationType = getOperationType(sql);\n\tconst db = await getDatabase(key, operationType);\n\tconst result = db.prepare(sql);\n\treturn result;\n}\n\n/**\n * Executes a statement on the appropriate shard based on the primary key.\n * The primary key is used to determine which shard should store the record,\n * ensuring consistent routing for future queries.\n *\n * @template T - Type of the result records\n * @param key - Primary key to route the query (should match the record's primary key)\n * @param sql - SQL statement with parameter placeholders\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise that resolves when the statement is complete\n * @throws {Error} If statement fails or routing fails\n * @example\n * ```typescript\n * // Insert a new user\n * await run('user-123',\n * 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',\n * ['user-123', 'John Doe', 'john@example.com']\n * );\n *\n * // Insert a post linked to a user\n * await run('post-456',\n * 'INSERT INTO posts (id, user_id, title, content) VALUES (?, ?, ?, ?)',\n * ['post-456', 'user-123', 'Hello World', 'My first post!']\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Update user information\n * await run('user-123',\n * 'UPDATE users SET name = ?, email = ? WHERE id = ?',\n * ['John Smith', 'johnsmith@example.com', 'user-123']\n * );\n *\n * // Update post content\n * await run('post-456',\n * 'UPDATE posts SET title = ?, content = ?, updated_at = strftime(\"%s\", \"now\") WHERE id = ?',\n * ['Updated Title', 'Updated content here', 'post-456']\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Delete a specific user\n * await run('user-123',\n * 'DELETE FROM users WHERE id = ?',\n * ['user-123']\n * );\n *\n * // Delete user's posts (cascade delete)\n * await run('user-123',\n * 'DELETE FROM posts WHERE user_id = ?',\n * ['user-123']\n * );\n *\n * // Delete with conditions\n * await run('user-123',\n * 'DELETE FROM posts WHERE user_id = ? AND created_at < ?',\n * ['user-123', Date.now() - 86400000] // Posts older than 1 day\n * );\n * ```\n */\nexport async function run<T = Record<string, unknown>>(key: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst prepared = await prepare(key, sql);\n\tconst result = await prepared.bind(...bindings).run<T>();\n\n\tif (!result.success) {\n\t\tthrow new CollegeDBError(`Query failed: ${result.error || 'Unknown error'}`, 'QUERY_FAILED');\n\t}\n\n\treturn result;\n}\n\n/**\n * Retrieves all records matching the query for a given primary key.\n *\n * This function is useful for fetching multiple records based on a primary key.\n * It automatically routes the query to the correct shard based on the provided\n * primary key, ensuring consistent data access.\n * @param key - Primary key to route the query\n * @param sql - The SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise that resolves to the result of the update operation\n * @throws {Error} If update fails or routing fails\n *\n * @example\n * ```typescript\n * type Post = {\n * id: string;\n * user_id: string;\n * title: string;\n * content: string;\n * };\n *\n *\n * // Get user's posts\n * const postsResult = await all<Post>('user-123',\n * 'SELECT * FROM posts WHERE user_id = ? ORDER BY created_at DESC',\n * ['user-123']\n * );\n *\n * console.log(`User has ${postsResult.meta.count} posts`);\n * ```\n */\nexport async function all<T = Record<string, unknown>>(key: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst prepared = await prepare(key, sql);\n\tconst result = await prepared.bind(...bindings).all<T>();\n\n\tif (!result.success) {\n\t\tthrow new CollegeDBError(`Query failed: ${result.error || 'Unknown error'}`, 'QUERY_FAILED');\n\t}\n\n\treturn result;\n}\n\n/**\n * Retrieves the first record matching the query for a given primary key.\n *\n * This function is useful for fetching a single record based on a primary key.\n * It automatically routes the query to the correct shard based on the provided\n * primary key, ensuring consistent data access.\n *\n * @template T - Type of the result record\n * @param key - Primary key to route the query\n * @param sql - SQL statement with parameter placeholders\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise that resolves to the first matching record, or null if not found\n * @throws {Error} If query fails or routing fails\n *\n * @example\n * ```typescript\n * type User = {\n * id: string;\n * name: string;\n * email: string;\n * };\n * // Get a specific user\n * const userResult = await first<User>('user-123',\n * 'SELECT * FROM users WHERE id = ?',\n * ['user-123']\n * );\n *\n * if (userResult) {\n * console.log(`Found user: ${userResult.name}`);\n * }\n */\nexport async function first<T = Record<string, unknown>>(key: string, sql: string, bindings: any[] = []): Promise<T | null> {\n\tconst prepared = await prepare(key, sql);\n\tconst result = await prepared.bind(...bindings).first<T>();\n\treturn result;\n}\n\n/**\n * Reassigns a primary key to a different shard\n *\n * Moves a primary key and its associated data from one shard to another. This\n * operation is useful for load balancing, shard maintenance, or geographic\n * redistribution of data.\n *\n * The reassignment process:\n * 1. Validates the target shard exists in configuration\n * 2. Checks that a mapping exists for the primary key\n * 3. If target shard differs from current, migrates the data\n * 4. Updates the KV mapping to point to the new shard\n *\n * **Note**: This operation involves data migration and should be used\n * carefully in production environments. Consider the impact on ongoing queries.\n *\n * @param primaryKey - Primary key to reassign to a different shard\n * @param newBinding - New shard binding name where the data should be moved\n * @param tableName - Name of the table containing the record to migrate\n * @returns Promise that resolves when reassignment and migration are complete\n * @throws {Error} If target shard not found, mapping doesn't exist, or migration fails\n * @example\n * ```typescript\n * // Move a user from east to west coast for better latency\n * try {\n * await reassignShard('user-california-123', 'db-west', 'users');\n * console.log('User successfully moved to west coast shard');\n * } catch (error) {\n * console.error('Reassignment failed:', error.message);\n * }\n *\n * // Load balancing: move high-activity user to dedicated shard\n * await reassignShard('user-enterprise-456', 'db-dedicated', 'users');\n * ```\n */\nexport async function reassignShard(primaryKey: string, newBinding: string, tableName: string): Promise<void> {\n\tconst config = getConfig();\n\n\tif (!config.shards[newBinding]) {\n\t\tthrow new CollegeDBError(`Shard ${newBinding} not found in configuration`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\tconst currentMapping = await mapper.getShardMapping(primaryKey);\n\n\tif (!currentMapping) {\n\t\tthrow new CollegeDBError(`No existing mapping found for primary key: ${primaryKey}`, 'MAPPING_NOT_FOUND');\n\t}\n\n\t// Migrate data if different shard\n\tif (currentMapping.shard !== newBinding) {\n\t\tconst { migrateRecord } = await import('./migrations.js');\n\t\tconst sourceDb = config.shards[currentMapping.shard];\n\t\tconst targetDb = config.shards[newBinding];\n\n\t\tif (!sourceDb || !targetDb) {\n\t\t\tthrow new CollegeDBError('Source or target shard not available', 'SHARD_UNAVAILABLE');\n\t\t}\n\n\t\tawait migrateRecord(sourceDb, targetDb, primaryKey, tableName);\n\t}\n\n\t// Update mapping\n\tawait mapper.updateShardMapping(primaryKey, newBinding);\n}\n\n/**\n * Lists all known shards\n *\n * Returns an array of all shard binding names known to the system. First\n * attempts to get the list from the Durable Object coordinator for the most\n * up-to-date information, then falls back to the configured shards if the\n * coordinator is unavailable.\n *\n * @returns Promise resolving to array of shard binding names\n * @example\n * ```typescript\n * const shards = await listKnownShards();\n * console.log('Available shards:', shards);\n * // Output: ['db-east', 'db-west', 'db-central']\n *\n * // Check if a specific shard is available\n * if (shards.includes('db-asia')) {\n * console.log('Asia region shard is available');\n * }\n * ```\n */\nexport async function listKnownShards(): Promise<string[]> {\n\tconst config = getConfig();\n\n\t// Try to get from coordinator first\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tconst response = await coordinator.fetch('http://coordinator/shards');\n\t\t\tif (response.ok) {\n\t\t\t\treturn await response.json();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('Failed to get shards from coordinator:', error);\n\t\t}\n\t}\n\n\t// Fallback to configured shards\n\treturn Object.keys(config.shards);\n}\n\n/**\n * Gets statistics for all shards\n *\n * Returns usage statistics for all known shards, including key counts and\n * last updated timestamps. First attempts to get real-time statistics from\n * the Durable Object coordinator, then falls back to KV-based counting.\n *\n * This information is useful for:\n * - Load balancing decisions\n * - Monitoring shard utilization\n * - Capacity planning\n * - Performance analysis\n *\n * @returns Promise resolving to array of shard statistics\n * @example\n * ```typescript\n * const stats = await getShardStats();\n * stats.forEach(shard => {\n * console.log(`${shard.binding}: ${shard.count} keys`);\n * if (shard.lastUpdated) {\n * console.log(` Last updated: ${new Date(shard.lastUpdated)}`);\n * }\n * });\n *\n * // Find most loaded shard\n * const mostLoaded = stats.reduce((prev, current) =>\n * (prev.count > current.count) ? prev : current\n * );\n * console.log(`Most loaded shard: ${mostLoaded.binding} (${mostLoaded.count} keys)`);\n * ```\n */\nexport async function getShardStats(): Promise<ShardStats[]> {\n\tconst config = getConfig();\n\n\t// Try to get from coordinator first\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tconst response = await coordinator.fetch('http://coordinator/stats');\n\t\t\tif (response.ok) {\n\t\t\t\treturn await response.json();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.warn('Failed to get stats from coordinator:', error);\n\t\t}\n\t}\n\n\t// Fallback to KV-based counting\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\tconst counts = await mapper.getShardKeyCounts();\n\n\treturn Object.entries(config.shards).map(([binding, _]) => ({\n\t\tbinding,\n\t\tcount: counts[binding] || 0\n\t}));\n}\n\n/**\n * Bypasses the normal routing logic to execute a query directly on a specified\n * shard. This is useful for administrative operations, cross-shard queries,\n * or when you need to query data that doesn't follow the primary key routing pattern.\n *\n * **Use with caution**: This function bypasses routing safeguards and should\n * be used only when you specifically need to target a particular shard.\n *\n * @param shardBinding - The shard binding name to execute the query on\n * @param sql - SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise resolving to the result of the query execution\n * @throws {Error} If shard not found or query fails\n * @example\n * ```typescript\n * // Administrative query: insert a new user directly into a specific shard\n * const result = await runShard('db-east',\n * 'INSERT INTO users (id, name, email) VALUES (?, ?, ?)',\n * ['user-789', 'Alice', 'alice@example.com']\n * );\n * console.log(`Inserted user with ID: ${result.lastInsertId}`);\n * ```\n */\nexport async function runShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst config = getConfig();\n\tconst db = config.shards[shardBinding];\n\n\tif (!db) {\n\t\tthrow new CollegeDBError(`Shard ${shardBinding} not found`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst result = await db\n\t\t.prepare(sql)\n\t\t.bind(...bindings)\n\t\t.run<T>();\n\n\tif (!result.success) {\n\t\tthrow new CollegeDBError(`Query failed: ${result.error || 'Unknown error'}`, 'QUERY_FAILED');\n\t}\n\n\treturn result;\n}\n\n/**\n * Bypasses the normal routing logic to execute a query directly on a specified\n * shard. This is useful for administrative operations, cross-shard queries,\n * or when you need to query data that doesn't follow the primary key routing pattern.\n *\n * **Use with caution**: This function bypasses routing safeguards and should\n * be used only when you specifically need to target a particular shard.\n *\n * @param shardBinding - The shard binding name to execute the query on\n * @param sql - SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise resolving to structured query results\n * @throws {Error} If shard not found or query fails\n * @example\n * ```typescript\n * // Administrative query: count all users across a specific shard\n * const eastCoastStats = await allShard('db-east',\n * 'SELECT COUNT(*) as user_count FROM users'\n * );\n * console.log(`East coast users: ${eastCoastStats.results[0].user_count}`);\n *\n * // Cross-shard analytics: get recent posts from a specific region\n * const recentPosts = await allShard('db-west',\n * 'SELECT id, title, created_at FROM posts WHERE created_at > ? ORDER BY created_at DESC LIMIT ?',\n * [Date.now() - 86400000, 10] // Last 24 hours, limit 10\n * );\n *\n * // Schema inspection on specific shard\n * const tables = await allShard('db-central',\n * \"SELECT name FROM sqlite_master WHERE type='table'\"\n * );\n * ```\n */\nexport async function allShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings: any[] = []): Promise<D1Result<T>> {\n\tconst config = getConfig();\n\tconst db = config.shards[shardBinding];\n\n\tif (!db) {\n\t\tthrow new CollegeDBError(`Shard ${shardBinding} not found`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst result = await db\n\t\t.prepare(sql)\n\t\t.bind(...bindings)\n\t\t.all<T>();\n\n\treturn result;\n}\n\n/**\n * Bypasses the normal routing logic to execute a query directly on a specified\n * shard. This is useful for administrative operations, cross-shard queries,\n * or when you need to query data that doesn't follow the primary key routing pattern.\n *\n * **Use with caution**: This function bypasses routing safeguards and should\n * be used only when you specifically need to target a particular shard.\n *\n * @param shardBinding - The shard binding name to execute the query on\n * @param sql - SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @returns Promise resolving to the first matching record, or null if not found\n * @throws {Error} If shard not found or query fails\n * @example\n * ```typescript\n * // Administrative query: get a specific user from a shard\n * const user = await firstShard('db-east',\n * 'SELECT * FROM users WHERE id = ?',\n * ['user-123']);\n * if (user) {\n * console.log(`Found user: ${user.name}`);\n * } else {\n * console.log('User not found in east shard');\n * }\n * ```\n */\nexport async function firstShard<T = Record<string, unknown>>(shardBinding: string, sql: string, bindings: any[] = []): Promise<T | null> {\n\tconst config = getConfig();\n\tconst db = config.shards[shardBinding];\n\n\tif (!db) {\n\t\tthrow new CollegeDBError(`Shard ${shardBinding} not found`, 'SHARD_NOT_FOUND');\n\t}\n\n\tconst result = await db\n\t\t.prepare(sql)\n\t\t.bind(...bindings)\n\t\t.first<T>();\n\n\treturn result;\n}\n\n/**\n * Executes a query on all shards and returns the results from each shard.\n *\n * This function is useful for scenarios where you need to aggregate data\n * from multiple shards, such as running analytics or cross-shard queries.\n * It executes the same SQL statement on each shard and collects the results.\n * @param sql - The SQL statement to execute on each shard\n * @param bindings - Parameter values to bind to the SQL statement\n * @param batchSize - Number of concurrent queries to run at once (default: 50)\n * @returns Promise resolving to an array of results from each shard\n * @since 1.0.4\n */\nexport async function runAllShards<T = Record<string, unknown>>(\n\tsql: string,\n\tbindings: any[] = [],\n\tbatchSize: number = 50\n): Promise<D1Result<T>[]> {\n\tconst config = getConfig();\n\tconst results: Promise<D1Result<T>>[] = [];\n\n\tfor (const [binding, db] of Object.entries(config.shards)) {\n\t\ttry {\n\t\t\tconst result = db\n\t\t\t\t.prepare(sql)\n\t\t\t\t.bind(...bindings)\n\t\t\t\t.all<T>()\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(`Error executing query on shard ${binding}:`, error);\n\t\t\t\t\treturn { success: false, results: [], meta: { count: 0, duration: 0 } } as unknown as D1Result<T>;\n\t\t\t\t});\n\t\t\tresults.push(result);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error running on shard ${binding}:`, error);\n\t\t}\n\t}\n\n\tconst batch: D1Result<T>[] = [];\n\tfor (let i = 0; i < results.length; i += batchSize) {\n\t\tbatch.push(...(await Promise.all(results.slice(i, i + batchSize))));\n\t}\n\n\treturn batch;\n}\n\n/**\n * Executes a query on all shards and returns all matching records from each shard.\n *\n * This function is useful for scenarios where you need to retrieve all records\n * matching a query across multiple shards, such as aggregating data or running\n * cross-shard analytics.\n * @param sql - The SQL statement to execute on each shard\n * @param bindings - Parameter values to bind to the SQL statement\n * @param batchSize - Number of concurrent queries to run at once (default: 50)\n * @returns Promise resolving to an array of results from each shard\n * @since 1.0.4\n */\nexport async function allAllShards<T = Record<string, unknown>>(\n\tsql: string,\n\tbindings: any[] = [],\n\tbatchSize: number = 50\n): Promise<D1Result<T>[]> {\n\tconst config = getConfig();\n\tconst results: Promise<D1Result<T>>[] = [];\n\n\tfor (const [binding, db] of Object.entries(config.shards)) {\n\t\ttry {\n\t\t\tconst result = db\n\t\t\t\t.prepare(sql)\n\t\t\t\t.bind(...bindings)\n\t\t\t\t.all<T>()\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(`Error executing query on shard ${binding}:`, error);\n\t\t\t\t\treturn { success: false, results: [], meta: { count: 0, duration: 0 } } as unknown as D1Result<T>;\n\t\t\t\t});\n\t\t\tresults.push(result);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error running on shard ${binding}:`, error);\n\t\t}\n\t}\n\n\tconst batch: D1Result<T>[] = [];\n\tfor (let i = 0; i < results.length; i += batchSize) {\n\t\tbatch.push(...(await Promise.all(results.slice(i, i + batchSize))));\n\t}\n\n\treturn batch;\n}\n\n/**\n * Executes a query on all shards and returns the first matching record from each shard.\n *\n * This function is useful for scenarios where you need to retrieve a single record\n * from each shard, such as fetching the latest entry or a specific item that may\n * exist on multiple shards.\n * @param sql - The SQL statement to execute\n * @param bindings - Parameter values to bind to the SQL statement\n * @param batchSize - Number of concurrent queries to run at once (default: 50)\n * @returns Promise resolving to an array of first matching records from each shard\n * @since 1.0.4\n */\nexport async function firstAllShards<T = Record<string, unknown>>(\n\tsql: string,\n\tbindings: any[] = [],\n\tbatchSize: number = 50\n): Promise<(T | null)[]> {\n\tconst config = getConfig();\n\tconst results: Promise<T | null>[] = [];\n\n\tfor (const [binding, db] of Object.entries(config.shards)) {\n\t\ttry {\n\t\t\tconst result = db\n\t\t\t\t.prepare(sql)\n\t\t\t\t.bind(...bindings)\n\t\t\t\t.first<T>()\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(`Error executing query on shard ${binding}:`, error);\n\t\t\t\t\treturn null;\n\t\t\t\t});\n\t\t\t3;\n\t\t\tresults.push(result);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error running on shard ${binding}:`, error);\n\t\t}\n\t}\n\n\tconst batch: (T | null)[] = [];\n\tfor (let i = 0; i < results.length; i += batchSize) {\n\t\tbatch.push(...(await Promise.all(results.slice(i, i + batchSize))));\n\t}\n\n\treturn batch;\n}\n\n/**\n * Flushes all shard mappings (development only)\n *\n * Completely clears all primary key to shard mappings from both KV storage\n * and the Durable Object coordinator. This operation resets the entire\n * routing system to a clean state.\n *\n * **DANGER**: This operation is destructive and irreversible. After flushing,\n * all existing primary keys will be treated as new and may be assigned to\n * different shards than before, causing data routing issues.\n *\n * **Use only for**:\n * - Development and testing environments\n * - Complete system resets\n * - Emergency recovery scenarios\n *\n * @returns Promise that resolves when all mappings are cleared\n * @example\n * ```typescript\n * // Only use in development!\n * if (process.env.NODE_ENV === 'development') {\n * await flush();\n * console.log('All shard mappings cleared for testing');\n *\n * // Now all keys will be reassigned on next access\n * await run('user-123', 'INSERT INTO users (id, name) VALUES (?, ?)',\n * ['user-123', 'Test User']);\n * }\n * ```\n */\nexport async function flush(): Promise<void> {\n\tconst config = getConfig();\n\tconst mapper = new KVShardMapper(config.kv, { hashShardMappings: config.hashShardMappings });\n\n\tawait mapper.clearAllMappings();\n\n\t// Also flush coordinator if available\n\tif (config.coordinator) {\n\t\ttry {\n\t\t\tconst coordinatorId = config.coordinator.idFromName('default');\n\t\t\tconst coordinator = config.coordinator.get(coordinatorId);\n\n\t\t\tawait coordinator.fetch('http://coordinator/flush', { method: 'POST' });\n\t\t} catch (error) {\n\t\t\tconsole.warn('Failed to flush coordinator:', error);\n\t\t}\n\t}\n}\n",
|
|
9
9
|
"/**\n * @fileoverview Durable Object for coordinating shard allocation and maintaining statistics\n *\n * This module provides the ShardCoordinator Durable Object that manages shard allocation\n * strategies and maintains real-time statistics about shard utilization. It provides\n * an HTTP API for other parts of the system to interact with the coordinator.\n *\n * @example\n * ```typescript\n * // In wrangler.toml:\n * [[durable_objects.bindings]]\n * name = \"ShardCoordinator\"\n * class_name = \"ShardCoordinator\"\n *\n * // Usage in a Worker:\n * const coordinatorId = env.ShardCoordinator.idFromName('default');\n * const coordinator = env.ShardCoordinator.get(coordinatorId);\n * const response = await coordinator.fetch('http://coordinator/allocate', {\n * method: 'POST',\n * body: JSON.stringify({ primaryKey: 'user-123', strategy: 'hash' })\n * });\n * ```\n *\n * @author CollegeDB Team\n * @since 1.0.0\n */\n\nimport type { DurableObjectState } from '@cloudflare/workers-types';\nimport { CollegeDBError } from './errors.js';\nimport type { MixedShardingStrategy, OperationType, ShardCoordinatorState, ShardingStrategy } from './types.js';\n\n/**\n * Durable Object for coordinating shard allocation and maintaining statistics\n *\n * The ShardCoordinator is a Cloudflare Durable Object that provides centralized\n * coordination for shard allocation across multiple D1 databases. It maintains\n * state about available shards, allocation strategies, and usage statistics.\n *\n * Key responsibilities:\n * - Track available D1 shards and their current load\n * - Implement allocation strategies (round-robin, random, hash-based)\n * - Provide HTTP API for shard allocation and management\n * - Maintain persistent state using Durable Object storage\n *\n * @example\n * ```typescript\n * // Allocate a shard for a new primary key\n * const response = await coordinator.fetch('http://coordinator/allocate', {\n * method: 'POST',\n * body: JSON.stringify({ primaryKey: 'user-456', strategy: 'hash' })\n * });\n * const { shard } = await response.json();\n * ```\n */\nexport class ShardCoordinator {\n\t/**\n\t * Durable Object state handle for persistent storage\n\t * @private\n\t */\n\tprivate state: DurableObjectState;\n\n\t/**\n\t * Creates a new ShardCoordinator instance\n\t * @param state - Durable Object state provided by Cloudflare runtime\n\t */\n\tconstructor(state: DurableObjectState) {\n\t\tthis.state = state;\n\t}\n\n\t/**\n\t * Gets the current coordinator state from persistent storage\n\t *\n\t * Retrieves the coordinator state from Durable Object storage, returning\n\t * a default state if none exists. The state includes known shards, statistics,\n\t * allocation strategy, and round-robin counter.\n\t *\n\t * @private\n\t * @returns Promise resolving to the current coordinator state\n\t * @throws {Error} If storage access fails\n\t */\n\tprivate async getState(): Promise<ShardCoordinatorState> {\n\t\tconst state = await this.state.storage.get<ShardCoordinatorState>('coordinator_state');\n\t\treturn (\n\t\t\tstate || {\n\t\t\t\tknownShards: [],\n\t\t\t\tshardStats: {},\n\t\t\t\tstrategy: 'round-robin',\n\t\t\t\troundRobinIndex: 0\n\t\t\t}\n\t\t);\n\t}\n\n\t/**\n\t * Saves the coordinator state to persistent storage\n\t *\n\t * Persists the coordinator state to Durable Object storage. This includes\n\t * all shard information, statistics, and configuration.\n\t *\n\t * @private\n\t * @param state - The coordinator state to persist\n\t * @returns Promise that resolves when state is saved\n\t * @throws {Error} If storage write fails\n\t */\n\tprivate async saveState(state: ShardCoordinatorState): Promise<void> {\n\t\tawait this.state.storage.put('coordinator_state', state);\n\t}\n\n\t/**\n\t * Handles HTTP requests to the Durable Object\n\t *\n\t * Main entry point for all HTTP requests to the ShardCoordinator. Routes\n\t * requests based on method and path to appropriate handler functions.\n\t *\n\t * Supported endpoints:\n\t * - GET /shards - List all known shards\n\t * - POST /shards - Add a new shard\n\t * - DELETE /shards - Remove a shard\n\t * - GET /stats - Get shard statistics\n\t * - POST /stats - Update shard statistics\n\t * - POST /allocate - Allocate a shard for a primary key\n\t * - POST /flush - Clear all coordinator state (development only)\n\t * - GET /health - Health check endpoint\n\t *\n\t * @param request - The incoming HTTP request\n\t * @returns Promise resolving to HTTP response\n\t * @example\n\t * ```typescript\n\t * // Allocate a shard\n\t * const response = await coordinator.fetch('http://coordinator/allocate', {\n\t * method: 'POST',\n\t * headers: { 'Content-Type': 'application/json' },\n\t * body: JSON.stringify({ primaryKey: 'user-123' })\n\t * });\n\t * ```\n\t */\n\tasync fetch(request: Request): Promise<Response> {\n\t\tconst url = new URL(request.url);\n\t\tconst path = url.pathname;\n\t\tconst method = request.method;\n\n\t\ttry {\n\t\t\tswitch (`${method} ${path}`) {\n\t\t\t\tcase 'GET /shards':\n\t\t\t\t\treturn this.handleListShards();\n\t\t\t\tcase 'POST /shards':\n\t\t\t\t\treturn this.handleAddShard(request);\n\t\t\t\tcase 'DELETE /shards':\n\t\t\t\t\treturn this.handleRemoveShard(request);\n\t\t\t\tcase 'GET /stats':\n\t\t\t\t\treturn this.handleGetStats();\n\t\t\t\tcase 'POST /stats':\n\t\t\t\t\treturn this.handleUpdateStats(request);\n\t\t\t\tcase 'POST /allocate':\n\t\t\t\t\treturn this.handleAllocateShard(request);\n\t\t\t\tcase 'POST /flush':\n\t\t\t\t\treturn this.handleFlush();\n\t\t\t\tcase 'GET /health':\n\t\t\t\t\treturn new Response('OK', { status: 200 });\n\t\t\t\tdefault:\n\t\t\t\t\treturn new Response('Not Found', { status: 404 });\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error('ShardCoordinator error:', error);\n\t\t\treturn new Response('Internal Server Error', { status: 500 });\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves all known shards as a JSON array of all D1 binding names that have been registered\n\t * with the coordinator.\n\t * @private\n\t * @returns Promise resolving to HTTP response with shard list\n\t * @example Response body: `[\"db-east\", \"db-west\", \"db-central\"]`\n\t */\n\tprivate async handleListShards(): Promise<Response> {\n\t\tconst state = await this.getState();\n\t\treturn new Response(JSON.stringify(state.knownShards), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Registers a new D1 database binding with the coordinator. If the shard\n\t * is already known, this operation is idempotent. Initializes statistics\n\t * for the new shard.\n\t * @private\n\t * @param request - HTTP request containing shard binding name in JSON body\n\t * @returns Promise resolving to HTTP response indicating success\n\t * @throws {Error} If request body is invalid JSON\n\t * @example Request body: `{\"shard\": \"db-new-region\"}`\n\t */\n\tprivate async handleAddShard(request: Request): Promise<Response> {\n\t\tconst { shard } = (await request.json()) as { shard: string };\n\n\t\t// Validate required parameters\n\t\tif (!shard || typeof shard !== 'string') {\n\t\t\treturn new Response(JSON.stringify({ error: 'Missing or invalid shard parameter' }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\n\t\tconst state = await this.getState();\n\n\t\tif (!state.knownShards.includes(shard)) {\n\t\t\tstate.knownShards.push(shard);\n\t\t\tstate.shardStats[shard] = {\n\t\t\t\tbinding: shard,\n\t\t\t\tcount: 0,\n\t\t\t\tlastUpdated: Date.now()\n\t\t\t};\n\t\t\tawait this.saveState(state);\n\t\t}\n\n\t\treturn new Response(JSON.stringify({ success: true }), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Unregisters a D1 database binding from the coordinator. Removes the shard\n\t * from the known shards list and deletes its statistics. Adjusts the round-robin\n\t * index if necessary to prevent out-of-bounds access.\n\t * @private\n\t * @param request - HTTP request containing shard binding name in JSON body\n\t * @returns Promise resolving to HTTP response indicating success\n\t * @throws {Error} If request body is invalid JSON\n\t * @example Request body: `{\"shard\": \"db-old-region\"}`\n\t */\n\tprivate async handleRemoveShard(request: Request): Promise<Response> {\n\t\tconst { shard } = (await request.json()) as { shard: string };\n\n\t\t// Validate required parameters\n\t\tif (!shard || typeof shard !== 'string') {\n\t\t\treturn new Response(JSON.stringify({ error: 'Missing or invalid shard parameter' }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\n\t\tconst state = await this.getState();\n\n\t\tconst index = state.knownShards.indexOf(shard);\n\t\tif (index > -1) {\n\t\t\tstate.knownShards.splice(index, 1);\n\t\t\tdelete state.shardStats[shard];\n\t\t\t// Adjust round-robin index if necessary\n\t\t\tif (state.roundRobinIndex >= state.knownShards.length) {\n\t\t\t\tstate.roundRobinIndex = 0;\n\t\t\t}\n\t\t\tawait this.saveState(state);\n\t\t}\n\n\t\treturn new Response(JSON.stringify({ success: true }), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Returns an array of statistics for all known shards, including\n\t * binding names, key counts, and last updated timestamps.\n\t * @private\n\t * @returns Promise resolving to HTTP response with statistics array\n\t * @example Response body: `[{\"binding\": \"db-east\", \"count\": 1234, \"lastUpdated\": 1672531200000}]`\n\t */\n\tprivate async handleGetStats(): Promise<Response> {\n\t\tconst state = await this.getState();\n\t\tconst stats = Object.values(state.shardStats);\n\t\treturn new Response(JSON.stringify(stats), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Updates the key count and last updated timestamp for a specific shard.\n\t * Used by other parts of the system to report changes in shard utilization.\n\t * @private\n\t * @param request - HTTP request containing shard name and count in JSON body\n\t * @returns Promise resolving to HTTP response indicating success\n\t * @throws {Error} If request body is invalid JSON or shard doesn't exist\n\t * @example Request body: `{\"shard\": \"db-east\", \"count\": 1500}`\n\t */\n\tprivate async handleUpdateStats(request: Request): Promise<Response> {\n\t\tconst { shard, count } = (await request.json()) as { shard: string; count: number };\n\n\t\t// Validate required parameters\n\t\tif (!shard || typeof shard !== 'string') {\n\t\t\treturn new Response(JSON.stringify({ error: 'Missing or invalid shard parameter' }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\n\t\tif (count === undefined || typeof count !== 'number') {\n\t\t\treturn new Response(JSON.stringify({ error: 'Missing or invalid count parameter' }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\n\t\tconst state = await this.getState();\n\n\t\tif (state.shardStats[shard]) {\n\t\t\tstate.shardStats[shard].count = count;\n\t\t\tstate.shardStats[shard].lastUpdated = Date.now();\n\t\t\tawait this.saveState(state);\n\t\t}\n\n\t\treturn new Response(JSON.stringify({ success: true }), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Selects an appropriate shard for a new primary key using the specified\n\t * allocation strategy. Updates internal state for round-robin allocation.\n\t *\n\t * Supported strategies:\n\t * - round-robin: Cycles through shards in order\n\t * - random: Selects a random shard\n\t * - hash: Uses consistent hashing based on primary key\n\t *\n\t * @private\n\t * @param request - HTTP request containing primary key and optional strategy\n\t * @returns Promise resolving to HTTP response with selected shard\n\t * @throws {Error} If no shards are available or request body is invalid\n\t * @example Request body: `{\"primaryKey\": \"user-123\", \"strategy\": \"hash\"}`\n\t * @example Response body: `{\"shard\": \"db-west\"}`\n\t */\n\tprivate async handleAllocateShard(request: Request): Promise<Response> {\n\t\tconst { primaryKey, strategy, operationType } = (await request.json()) as {\n\t\t\tprimaryKey: string;\n\t\t\tstrategy?: ShardingStrategy;\n\t\t\toperationType?: OperationType;\n\t\t};\n\n\t\t// Validate required parameters\n\t\tif (!primaryKey || typeof primaryKey !== 'string') {\n\t\t\treturn new Response(JSON.stringify({ error: 'Missing or invalid primaryKey parameter' }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\n\t\tconst state = await this.getState();\n\n\t\tif (state.knownShards.length === 0) {\n\t\t\treturn new Response(JSON.stringify({ error: 'No shards available' }), {\n\t\t\t\tstatus: 400,\n\t\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t\t});\n\t\t}\n\n\t\t// Resolve the effective strategy based on state strategy and operation type\n\t\tconst effectiveStrategy = this.resolveStrategy(state.strategy, strategy, operationType || 'write');\n\t\tconst selectedShard = this.selectShard(primaryKey, state, effectiveStrategy);\n\n\t\t// Update round-robin index for next allocation\n\t\tif (effectiveStrategy === 'round-robin') {\n\t\t\tstate.roundRobinIndex = (state.roundRobinIndex + 1) % state.knownShards.length;\n\t\t\tawait this.saveState(state);\n\t\t}\n\n\t\treturn new Response(JSON.stringify({ shard: selectedShard }), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Completely clears all coordinator state from Durable Object storage.\n\t * This removes all shard registrations, statistics, and configuration.\n\t *\n\t * **WARNING**: This operation is destructive and should only be used\n\t * in development environments or during testing.\n\t *\n\t * @private\n\t * @returns Promise resolving to HTTP response indicating success\n\t * @example Response body: `{\"success\": true}`\n\t */\n\tprivate async handleFlush(): Promise<Response> {\n\t\tawait this.state.storage.deleteAll();\n\t\treturn new Response(JSON.stringify({ success: true }), {\n\t\t\theaders: { 'Content-Type': 'application/json' }\n\t\t});\n\t}\n\n\t/**\n\t * Resolves the effective sharding strategy for a given operation type\n\t *\n\t * @private\n\t * @param configStrategy - The strategy from state configuration\n\t * @param requestStrategy - Optional strategy override from request\n\t * @param operationType - The type of operation (read/write)\n\t * @returns The effective sharding strategy to use\n\t */\n\tprivate resolveStrategy(\n\t\tconfigStrategy: ShardingStrategy | MixedShardingStrategy,\n\t\trequestStrategy?: ShardingStrategy,\n\t\toperationType: OperationType = 'write'\n\t): ShardingStrategy {\n\t\t// Request strategy overrides everything\n\t\tif (requestStrategy) {\n\t\t\treturn requestStrategy;\n\t\t}\n\n\t\t// If config strategy is a string, use it for all operations\n\t\tif (typeof configStrategy === 'string') {\n\t\t\treturn configStrategy;\n\t\t}\n\n\t\t// If config strategy is a mixed strategy object, use the appropriate strategy for the operation type\n\t\treturn configStrategy[operationType];\n\t}\n\n\t/**\n\t * Implements the core shard selection logic for different allocation strategies.\n\t * Uses consistent algorithms to ensure predictable shard assignment.\n\t *\n\t * Strategy details:\n\t * - round-robin: Uses roundRobinIndex to cycle through shards\n\t * - random: Uses Math.random() for uniform distribution\n\t * - hash: Uses string hash function for consistent assignment\n\t *\n\t * @private\n\t * @param primaryKey - The primary key to allocate a shard for\n\t * @param state - Current coordinator state containing available shards\n\t * @param strategy - The allocation strategy to use\n\t * @returns The selected shard binding name\n\t * @throws {CollegeDBError} If no shards are available\n\t * @example\n\t * ```typescript\n\t * const shard = this.selectShard('user-123', state, 'hash');\n\t * // Returns: \"db-west\" (consistent for this key)\n\t * ```\n\t */\n\tprivate selectShard(primaryKey: string, state: ShardCoordinatorState, strategy: ShardingStrategy): string {\n\t\tconst shards = state.knownShards;\n\n\t\tif (shards.length === 0) {\n\t\t\tthrow new CollegeDBError('No shards available', 'NO_SHARDS');\n\t\t}\n\n\t\tswitch (strategy) {\n\t\t\tcase 'round-robin':\n\t\t\t\treturn shards[state.roundRobinIndex] ?? shards[0]!;\n\n\t\t\tcase 'random':\n\t\t\t\treturn shards[Math.floor(Math.random() * shards.length)]!;\n\n\t\t\tcase 'hash':\n\t\t\t\t// Simple hash function for consistent shard selection\n\t\t\t\tlet hash = 0;\n\t\t\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\t\t\thash = (hash << 5) - hash + char;\n\t\t\t\t\thash = hash & hash; // Convert to 32-bit integer\n\t\t\t\t}\n\t\t\t\tconst index = Math.abs(hash) % shards.length;\n\t\t\t\treturn shards[index]!;\n\n\t\t\tcase 'location':\n\t\t\t\t// For location strategy in coordinator, fallback to hash\n\t\t\t\t// The actual location logic is handled in router.ts\n\t\t\t\tlet locationHash = 0;\n\t\t\t\tfor (let i = 0; i < primaryKey.length; i++) {\n\t\t\t\t\tconst char = primaryKey.charCodeAt(i);\n\t\t\t\t\tlocationHash = (locationHash << 5) - locationHash + char;\n\t\t\t\t\tlocationHash = locationHash & locationHash;\n\t\t\t\t}\n\t\t\t\tconst locationIndex = Math.abs(locationHash) % shards.length;\n\t\t\t\treturn shards[locationIndex]!;\n\n\t\t\tdefault:\n\t\t\t\treturn shards[0]!;\n\t\t}\n\t}\n\n\t/**\n\t * Atomically increments the key count for a specific shard and updates\n\t * the last modified timestamp. Used when new primary keys are assigned\n\t * to a shard.\n\t * @param shard - The shard binding name to increment\n\t * @returns Promise that resolves when the count is updated\n\t * @throws {Error} If the shard is not known to the coordinator\n\t * @example\n\t * ```typescript\n\t * await coordinator.incrementShardCount('db-east');\n\t * ```\n\t */\n\tasync incrementShardCount(shard: string): Promise<void> {\n\t\tconst state = await this.getState();\n\t\tif (state.shardStats[shard]) {\n\t\t\tstate.shardStats[shard].count++;\n\t\t\tstate.shardStats[shard].lastUpdated = Date.now();\n\t\t\tawait this.saveState(state);\n\t\t}\n\t}\n\n\t/**\n\t * Atomically decrements the key count for a specific shard and updates\n\t * the last modified timestamp. Used when primary keys are removed or\n\t * moved from a shard. Prevents negative counts.\n\t * @param shard - The shard binding name to decrement\n\t * @returns Promise that resolves when the count is updated\n\t * @throws {CollegeDBError} If the shard is not known to the coordinator\n\t * @example\n\t * ```typescript\n\t * await coordinator.decrementShardCount('db-west');\n\t * ```\n\t */\n\tasync decrementShardCount(shard: string): Promise<void> {\n\t\tconst state = await this.getState();\n\t\tif (state.shardStats[shard] && state.shardStats[shard].count > 0) {\n\t\t\tstate.shardStats[shard].count--;\n\t\t\tstate.shardStats[shard].lastUpdated = Date.now();\n\t\t\tawait this.saveState(state);\n\t\t}\n\t}\n}\n\n//#region Tests - Only run in test environment\nif (process.env.NODE_ENV === 'test' || typeof global !== 'undefined') {\n\t/**\n\t * Mock Durable Object Storage for testing\n\t */\n\tclass MockDurableObjectStorage {\n\t\tprivate data = new Map<string, any>();\n\n\t\tasync get<T = unknown>(key: string): Promise<T | undefined> {\n\t\t\treturn this.data.get(key);\n\t\t}\n\n\t\tasync put<T = unknown>(key: string, value: T): Promise<void> {\n\t\t\tthis.data.set(key, value);\n\t\t}\n\n\t\tasync delete(key: string): Promise<boolean> {\n\t\t\treturn this.data.delete(key);\n\t\t}\n\n\t\tasync deleteAll(): Promise<void> {\n\t\t\tthis.data.clear();\n\t\t}\n\n\t\tasync list(options?: { prefix?: string }): Promise<Map<string, any>> {\n\t\t\tif (!options?.prefix) return new Map(this.data);\n\n\t\t\tconst filtered = new Map();\n\t\t\tfor (const [key, value] of this.data.entries()) {\n\t\t\t\tif (key.startsWith(options.prefix)) {\n\t\t\t\t\tfiltered.set(key, value);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn filtered;\n\t\t}\n\t}\n\n\t/**\n\t * Mock Durable Object State for testing\n\t */\n\tclass MockDurableObjectState {\n\t\tstorage: MockDurableObjectStorage;\n\n\t\tconstructor() {\n\t\t\tthis.storage = new MockDurableObjectStorage();\n\t\t}\n\t}\n\n\t/**\n\t * Tests for ShardCoordinator class\n\t * These tests verify the core functionality of the ShardCoordinator\n\t * including state management, shard allocation, and HTTP API endpoints\n\t */\n\tclass ShardCoordinatorTests {\n\t\tprivate coordinator: ShardCoordinator;\n\t\tprivate mockState: MockDurableObjectState;\n\n\t\tconstructor() {\n\t\t\tthis.mockState = new MockDurableObjectState();\n\t\t\tthis.coordinator = new ShardCoordinator(this.mockState as any);\n\t\t}\n\n\t\t/**\n\t\t * Test shard allocation strategies\n\t\t */\n\t\tasync testShardAllocation() {\n\t\t\t// Add some shards first\n\t\t\tawait this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/shards', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ shard: 'db-east' })\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tawait this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/shards', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ shard: 'db-west' })\n\t\t\t\t})\n\t\t\t);\n\n\t\t\t// Test round-robin allocation\n\t\t\tconst allocation1 = await this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/allocate', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ primaryKey: 'user-1', strategy: 'round-robin' })\n\t\t\t\t})\n\t\t\t);\n\t\t\tconst result1 = (await allocation1.json()) as { shard: string };\n\n\t\t\tconst allocation2 = await this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/allocate', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ primaryKey: 'user-2', strategy: 'round-robin' })\n\t\t\t\t})\n\t\t\t);\n\t\t\tconst result2 = (await allocation2.json()) as { shard: string };\n\n\t\t\t// Should allocate to different shards with round-robin\n\t\t\tconsole.assert(result1.shard !== result2.shard, 'Round-robin should alternate shards');\n\n\t\t\t// Test hash allocation (should be consistent)\n\t\t\tconst hashAllocation1 = await this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/allocate', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ primaryKey: 'consistent-key', strategy: 'hash' })\n\t\t\t\t})\n\t\t\t);\n\t\t\tconst hashResult1 = (await hashAllocation1.json()) as { shard: string };\n\n\t\t\tconst hashAllocation2 = await this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/allocate', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ primaryKey: 'consistent-key', strategy: 'hash' })\n\t\t\t\t})\n\t\t\t);\n\t\t\tconst hashResult2 = (await hashAllocation2.json()) as { shard: string };\n\n\t\t\t// Hash should be consistent for same key\n\t\t\tconsole.assert(hashResult1.shard === hashResult2.shard, 'Hash allocation should be consistent');\n\n\t\t\tconsole.log('✅ Shard allocation tests passed');\n\t\t}\n\n\t\t/**\n\t\t * Test shard statistics management\n\t\t */\n\t\tasync testShardStats() {\n\t\t\t// Clear state\n\t\t\tawait this.coordinator.fetch(new Request('http://test/flush', { method: 'POST' }));\n\n\t\t\t// Add shard\n\t\t\tawait this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/shards', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ shard: 'db-stats-test' })\n\t\t\t\t})\n\t\t\t);\n\n\t\t\t// Update stats\n\t\t\tawait this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/stats', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ shard: 'db-stats-test', count: 42 })\n\t\t\t\t})\n\t\t\t);\n\n\t\t\t// Get stats\n\t\t\tconst statsResponse = await this.coordinator.fetch(new Request('http://test/stats', { method: 'GET' }));\n\t\t\tconst stats = (await statsResponse.json()) as Array<{ binding: string; count: number }>;\n\n\t\t\tconsole.assert(stats.length === 1, 'Should have one shard stat');\n\t\t\tconsole.assert(stats[0]?.binding === 'db-stats-test', 'Should have correct binding name');\n\t\t\tconsole.assert(stats[0]?.count === 42, 'Should have correct count');\n\n\t\t\tconsole.log('✅ Shard stats tests passed');\n\t\t}\n\n\t\t/**\n\t\t * Test error handling\n\t\t */\n\t\tasync testErrorHandling() {\n\t\t\t// Clear state\n\t\t\tawait this.coordinator.fetch(new Request('http://test/flush', { method: 'POST' }));\n\n\t\t\t// Try to allocate with no shards\n\t\t\tconst emptyAllocation = await this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/allocate', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ primaryKey: 'test-key' })\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tconsole.assert(emptyAllocation.status === 400, 'Should return 400 for no shards available');\n\n\t\t\t// Test invalid endpoint\n\t\t\tconst invalidEndpoint = await this.coordinator.fetch(new Request('http://test/invalid', { method: 'GET' }));\n\t\t\tconsole.assert(invalidEndpoint.status === 404, 'Should return 404 for invalid endpoint');\n\n\t\t\tconsole.log('✅ Error handling tests passed');\n\t\t}\n\n\t\t/**\n\t\t * Test the increment/decrement functionality\n\t\t */\n\t\tasync testCountManagement() {\n\t\t\t// Add a shard\n\t\t\tawait this.coordinator.fetch(\n\t\t\t\tnew Request('http://test/shards', {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\tbody: JSON.stringify({ shard: 'db-count-test' })\n\t\t\t\t})\n\t\t\t);\n\n\t\t\t// Increment count\n\t\t\tawait this.coordinator.incrementShardCount('db-count-test');\n\t\t\tawait this.coordinator.incrementShardCount('db-count-test');\n\n\t\t\t// Check stats\n\t\t\tlet statsResponse = await this.coordinator.fetch(new Request('http://test/stats', { method: 'GET' }));\n\t\t\tlet stats = (await statsResponse.json()) as Array<{ binding: string; count: number }>;\n\n\t\t\tconst shard = stats.find((s) => s.binding === 'db-count-test');\n\t\t\tconsole.assert(shard?.count === 2, 'Count should be 2 after two increments');\n\n\t\t\t// Decrement count\n\t\t\tawait this.coordinator.decrementShardCount('db-count-test');\n\n\t\t\tstatsResponse = await this.coordinator.fetch(new Request('http://test/stats', { method: 'GET' }));\n\t\t\tstats = (await statsResponse.json()) as Array<{ binding: string; count: number }>;\n\n\t\t\tconst updatedShard = stats.find((s) => s.binding === 'db-count-test');\n\t\t\tconsole.assert(updatedShard?.count === 1, 'Count should be 1 after decrement');\n\n\t\t\tconsole.log('✅ Count management tests passed');\n\t\t}\n\n\t\t/**\n\t\t * Run all tests\n\t\t */\n\t\tasync runAllTests() {\n\t\t\tconsole.log('🧪 Running ShardCoordinator tests...');\n\n\t\t\ttry {\n\t\t\t\tawait this.testShardAllocation();\n\t\t\t\tawait this.testShardStats();\n\t\t\t\tawait this.testErrorHandling();\n\t\t\t\tawait this.testCountManagement();\n\n\t\t\t\tconsole.log('🎉 All ShardCoordinator tests passed!');\n\t\t\t\treturn true;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('❌ ShardCoordinator tests failed:', error);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Auto-run tests if this file is executed directly\n\tif (typeof require !== 'undefined' && require.main === module) {\n\t\tconst tests = new ShardCoordinatorTests();\n\t\ttests.runAllTests().then((success) => {\n\t\t\tprocess.exit(success ? 0 : 1);\n\t\t});\n\t}\n\n\t// Export for external testing\n\t(globalThis as any).testShardCoordinator = () => new ShardCoordinatorTests();\n}\n//#endregion\n",
|
|
10
|
-
"/**\n * CollegeDB - Cloudflare D1 Sharding Router\n *\n * A TypeScript library for horizontal scaling of SQLite-style databases on Cloudflare\n * using D1 and KV. Routes queries to the correct D1 database instance using primary\n * key mappings stored in Cloudflare KV.\n *\n * @author Gregory Mitchell\n * @license MIT\n */\n\n// Export main API functions\nexport {\n\tall,\n\tallShard,\n\tcollegedb,\n\tcreateSchema,\n\tfirst,\n\tfirstShard,\n\tflush,\n\tgetClosestRegionFromIP,\n\tgetShardStats,\n\tinitialize,\n\tinitializeAsync,\n\tlistKnownShards,\n\tprepare,\n\treassignShard,\n\tresetConfig,\n\trun,\n\trunShard\n} from './router.js';\n\n// Export utility classes\nexport { ShardCoordinator } from './durable.js';\nexport { CollegeDBError } from './errors.js';\nexport { KVShardMapper } from './kvmap.js';\n\n// Export migration functions\nexport {\n\tautoDetectAndMigrate,\n\tcheckMigrationNeeded,\n\tclearMigrationCache,\n\tclearShardMigrationCache,\n\tcreateMappingsForExistingKeys,\n\tcreateSchemaAcrossShards,\n\tdiscoverExistingPrimaryKeys,\n\tdropSchema,\n\tintegrateExistingDatabase,\n\tlistTables,\n\tmigrateRecord,\n\tschemaExists,\n\tvalidateTableForSharding,\n\ttype IntegrationOptions,\n\ttype IntegrationResult,\n\ttype ValidationResult\n} from './migrations.js';\n\n// Export types\nexport type {\n\tCollegeDBConfig,\n\tD1Region,\n\tEnv,\n\tMixedShardingStrategy,\n\tOperationType,\n\tShardCoordinatorState,\n\tShardLocation,\n\tShardMapping,\n\tShardStats,\n\tShardingStrategy\n} from './types.js';\n"
|
|
10
|
+
"/**\n * CollegeDB - Cloudflare D1 Sharding Router\n *\n * A TypeScript library for horizontal scaling of SQLite-style databases on Cloudflare\n * using D1 and KV. Routes queries to the correct D1 database instance using primary\n * key mappings stored in Cloudflare KV.\n *\n * @author Gregory Mitchell\n * @license MIT\n */\n\n// Export main API functions\nexport {\n\tall,\n\tallAllShards,\n\tallShard,\n\tcollegedb,\n\tcreateSchema,\n\tfirst,\n\tfirstAllShards,\n\tfirstShard,\n\tflush,\n\tgetClosestRegionFromIP,\n\tgetShardStats,\n\tinitialize,\n\tinitializeAsync,\n\tlistKnownShards,\n\tprepare,\n\treassignShard,\n\tresetConfig,\n\trun,\n\trunAllShards,\n\trunShard\n} from './router.js';\n\n// Export utility classes\nexport { ShardCoordinator } from './durable.js';\nexport { CollegeDBError } from './errors.js';\nexport { KVShardMapper } from './kvmap.js';\n\n// Export migration functions\nexport {\n\tautoDetectAndMigrate,\n\tcheckMigrationNeeded,\n\tclearMigrationCache,\n\tclearShardMigrationCache,\n\tcreateMappingsForExistingKeys,\n\tcreateSchemaAcrossShards,\n\tdiscoverExistingPrimaryKeys,\n\tdiscoverExistingRecordsWithColumns,\n\tdropSchema,\n\tintegrateExistingDatabase,\n\tlistTables,\n\tmigrateRecord,\n\tschemaExists,\n\tvalidateTableForSharding,\n\ttype IntegrationOptions,\n\ttype IntegrationResult,\n\ttype ValidationResult\n} from './migrations.js';\n\n// Export types\nexport type {\n\tCollegeDBConfig,\n\tD1Region,\n\tEnv,\n\tMixedShardingStrategy,\n\tOperationType,\n\tShardCoordinatorState,\n\tShardLocation,\n\tShardMapping,\n\tShardStats,\n\tShardingStrategy\n} from './types.js';\n"
|
|
11
11
|
],
|
|
12
|
-
"mappings": "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",
|
|
13
|
-
"debugId": "
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "AE70033A5549504A64756E2164756E21",
|
|
14
14
|
"names": []
|
|
15
15
|
}
|
package/dist/migrations.d.ts
CHANGED
|
@@ -227,6 +227,7 @@ export declare function discoverExistingPrimaryKeys(d1: D1Database, tableName: s
|
|
|
227
227
|
* console.log(`ID: ${record.id}, Email: ${record.email || 'N/A'}`);
|
|
228
228
|
* });
|
|
229
229
|
* ```
|
|
230
|
+
* @since 1.0.4
|
|
230
231
|
*/
|
|
231
232
|
export declare function discoverExistingRecordsWithColumns(d1: D1Database, tableName: string, primaryKeyColumn?: string): Promise<Array<{
|
|
232
233
|
[key: string]: any;
|
package/dist/migrations.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAQpE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAchF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhH;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQnF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BhI;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,2BAA2B,CAAC,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,gBAAgB,GAAE,MAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOvI;AAED
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAGpG,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAgC,UAAU,EAAoB,MAAM,YAAY,CAAC;AAaxH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,QAQjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,iBAS5D;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,CAAC,cAG5E;
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -102,6 +102,12 @@ export interface CollegeDBConfig {
|
|
|
102
102
|
* @since 1.0.3
|
|
103
103
|
*/
|
|
104
104
|
hashShardMappings?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Enable debug logging for development and troubleshooting
|
|
107
|
+
* @default false
|
|
108
|
+
* @since 1.0.6
|
|
109
|
+
*/
|
|
110
|
+
debug?: boolean;
|
|
105
111
|
}
|
|
106
112
|
/**
|
|
107
113
|
* Shard statistics for monitoring and load balancing
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +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;;GAEG;AACH,MAAM,MAAM,QAAQ,GACjB,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,IAAI,GACJ,IAAI,GACJ,IAAI,CAAC;AAER;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,gDAAgD;IAChD,MAAM,EAAE,QAAQ,CAAC;IACjB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE9E;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACrC,4CAA4C;IAC5C,IAAI,EAAE,gBAAgB,CAAC;IACvB,6DAA6D;IAC7D,KAAK,EAAE,gBAAgB,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7C;;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,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAAC;IACpD,gDAAgD;IAChD,YAAY,CAAC,EAAE,QAAQ,CAAC;IACxB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC/C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|