prisma-sharding 0.0.6 → 0.0.7

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.js CHANGED
@@ -131,6 +131,11 @@ var ShardRouter = class {
131
131
  if (this.strategy === "consistent-hash") {
132
132
  this.initializeConsistentHashRing();
133
133
  }
134
+ this.sortedRingHashes = Array.from(this.consistentHashRing.keys()).sort((a, b) => a - b);
135
+ this.randomShardWeights = this.shardIds.map(
136
+ (shardId) => config.shardWeights?.get(shardId) ?? 1
137
+ );
138
+ this.totalRandomWeight = this.randomShardWeights.reduce((sum, weight) => sum + weight, 0);
134
139
  }
135
140
  initializeConsistentHashRing() {
136
141
  for (const shardId of this.shardIds) {
@@ -155,25 +160,49 @@ var ShardRouter = class {
155
160
  return hash % shardCount;
156
161
  }
157
162
  getIndexConsistentHash(key) {
163
+ return this.shardIds.indexOf(this.getConsistentHashShardId(key));
164
+ }
165
+ getConsistentHashShardId(key) {
158
166
  const hash = hashString(key);
159
- const sortedHashes = Array.from(this.consistentHashRing.keys()).sort((a, b) => a - b);
160
- for (const ringHash of sortedHashes) {
161
- if (hash <= ringHash) {
162
- const shardId = this.consistentHashRing.get(ringHash);
163
- const match2 = shardId.match(/shard_(\d+)/);
164
- return match2 ? parseInt(match2[1], 10) - 1 : 0;
167
+ let low = 0;
168
+ let high = this.sortedRingHashes.length - 1;
169
+ let matchIndex = 0;
170
+ while (low <= high) {
171
+ const middle = Math.floor((low + high) / 2);
172
+ if (this.sortedRingHashes[middle] >= hash) {
173
+ matchIndex = middle;
174
+ high = middle - 1;
175
+ } else {
176
+ low = middle + 1;
165
177
  }
166
178
  }
167
- const firstShardId = this.consistentHashRing.get(sortedHashes[0]);
168
- const match = firstShardId.match(/shard_(\d+)/);
169
- return match ? parseInt(match[1], 10) - 1 : 0;
179
+ if (low >= this.sortedRingHashes.length) {
180
+ matchIndex = 0;
181
+ }
182
+ return this.consistentHashRing.get(this.sortedRingHashes[matchIndex]);
170
183
  }
171
184
  getShardId(key) {
185
+ if (this.shardIds.length === 0) {
186
+ throw new RoutingError("No shards available");
187
+ }
188
+ if (this.strategy === "consistent-hash") {
189
+ return this.getConsistentHashShardId(key);
190
+ }
172
191
  const index = this.getShardIndex(key);
173
192
  return this.shardIds[index] || `shard_${index + 1}`;
174
193
  }
175
194
  getRandomShardIndex() {
176
- return Math.floor(Math.random() * this.shardIds.length);
195
+ if (this.shardIds.length === 0) {
196
+ throw new RoutingError("No shards available");
197
+ }
198
+ let selection = Math.random() * this.totalRandomWeight;
199
+ for (let index = 0; index < this.randomShardWeights.length; index++) {
200
+ selection -= this.randomShardWeights[index];
201
+ if (selection < 0) {
202
+ return index;
203
+ }
204
+ }
205
+ return this.shardIds.length - 1;
177
206
  }
178
207
  getRandomShardId() {
179
208
  const index = this.getRandomShardIndex();
@@ -181,18 +210,235 @@ var ShardRouter = class {
181
210
  }
182
211
  };
183
212
 
213
+ // src/constants/internal.ts
214
+ var INTERNAL_DEFAULTS = {
215
+ HEALTH_CHECK_TIMEOUT_MS: 5e3,
216
+ CROSS_SHARD_CONCURRENCY: 4,
217
+ CROSS_SHARD_TIMEOUT_MS: 1e4,
218
+ CLI_COMMAND_TIMEOUT_MS: 12e4,
219
+ CLI_FORCE_KILL_GRACE_MS: 5e3,
220
+ CLI_MAX_OUTPUT_LENGTH: 1e6,
221
+ CLI_TEST_TCP_TIMEOUT_MS: 5e3,
222
+ CLI_TEST_COMMAND_TIMEOUT_MS: 15e3,
223
+ STUDIO_BASE_PORT: 51212,
224
+ STUDIO_REUSE_EXISTING: true,
225
+ STUDIO_STRICT_PORT_CHECK: false,
226
+ STUDIO_START_TIMEOUT_MS: 15e3,
227
+ STUDIO_STABILITY_MS: 500,
228
+ STUDIO_SHUTDOWN_TIMEOUT_MS: 5e3
229
+ };
230
+
231
+ // src/utils/sanitize.ts
232
+ var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
233
+ var maskDatabaseUrl = (url) => {
234
+ try {
235
+ const parsed = new URL(url);
236
+ if (parsed.password) {
237
+ parsed.password = "***";
238
+ }
239
+ return parsed.toString();
240
+ } catch {
241
+ return url.replace(/:[^:@]+@/, ":***@");
242
+ }
243
+ };
244
+ var sanitizeDatabaseText = (output, databaseUrls) => {
245
+ let sanitized = output;
246
+ for (const url of databaseUrls) {
247
+ sanitized = sanitized.replace(new RegExp(escapeRegExp(url), "g"), maskDatabaseUrl(url));
248
+ try {
249
+ const password = new URL(url).password;
250
+ if (password.length >= 4) {
251
+ sanitized = sanitized.replace(
252
+ new RegExp(escapeRegExp(decodeURIComponent(password)), "g"),
253
+ "***"
254
+ );
255
+ }
256
+ } catch {
257
+ }
258
+ }
259
+ return sanitized.replace(
260
+ /(postgres(?:ql)?:\/\/[^:/\s]+:)[^@\s]+(@)/gi,
261
+ "$1***$2"
262
+ );
263
+ };
264
+
265
+ // src/core/executor.ts
266
+ var timeoutError = (shardId, timeoutMs) => new Error(`Shard ${shardId} operation timed out after ${timeoutMs}ms`);
267
+ var CrossShardExecutor = class {
268
+ constructor(config = {}) {
269
+ this.metrics = {
270
+ operationCount: 0,
271
+ timeoutCount: 0,
272
+ failuresByShard: /* @__PURE__ */ new Map()
273
+ };
274
+ this.concurrency = config.concurrency ?? INTERNAL_DEFAULTS.CROSS_SHARD_CONCURRENCY;
275
+ this.timeoutMs = config.timeoutMs ?? INTERNAL_DEFAULTS.CROSS_SHARD_TIMEOUT_MS;
276
+ }
277
+ orderedTargets(targets) {
278
+ return targets.map((target, index) => ({ target, index })).sort((left, right) => {
279
+ if (left.target.isHealthy !== right.target.isHealthy) {
280
+ return left.target.isHealthy ? -1 : 1;
281
+ }
282
+ const leftLatency = left.target.latencyMs >= 0 ? left.target.latencyMs : Number.POSITIVE_INFINITY;
283
+ const rightLatency = right.target.latencyMs >= 0 ? right.target.latencyMs : Number.POSITIVE_INFINITY;
284
+ return leftLatency - rightLatency || left.index - right.index;
285
+ }).map(({ target }) => target);
286
+ }
287
+ recordFailure(shardId, error) {
288
+ this.metrics.failuresByShard.set(
289
+ shardId,
290
+ (this.metrics.failuresByShard.get(shardId) ?? 0) + 1
291
+ );
292
+ if (error.message.includes("timed out")) {
293
+ this.metrics.timeoutCount++;
294
+ }
295
+ }
296
+ withTimeout(shardId, operation) {
297
+ return new Promise((resolve, reject) => {
298
+ let settled = false;
299
+ const finish = (callback) => {
300
+ if (settled) {
301
+ return;
302
+ }
303
+ settled = true;
304
+ clearTimeout(timer);
305
+ callback();
306
+ };
307
+ const timer = setTimeout(() => {
308
+ finish(() => reject(timeoutError(shardId, this.timeoutMs)));
309
+ }, this.timeoutMs);
310
+ Promise.resolve().then(operation).then(
311
+ (result) => finish(() => resolve(result)),
312
+ (error) => finish(() => reject(error))
313
+ );
314
+ });
315
+ }
316
+ async executeAll(targets, operation) {
317
+ this.metrics.operationCount++;
318
+ const originalIndexes = new Map(targets.map((target, index) => [target.shardId, index]));
319
+ const ordered = this.orderedTargets(targets);
320
+ const results = new Array(targets.length);
321
+ let nextIndex = 0;
322
+ const worker = async () => {
323
+ while (nextIndex < ordered.length) {
324
+ const target = ordered[nextIndex++];
325
+ const resultIndex = originalIndexes.get(target.shardId);
326
+ try {
327
+ const result = await this.withTimeout(
328
+ target.shardId,
329
+ () => operation(target.client, target.shardId)
330
+ );
331
+ results[resultIndex] = { shardId: target.shardId, result, error: void 0 };
332
+ } catch (error) {
333
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
334
+ this.recordFailure(target.shardId, normalizedError);
335
+ results[resultIndex] = {
336
+ shardId: target.shardId,
337
+ result: null,
338
+ error: normalizedError
339
+ };
340
+ }
341
+ }
342
+ };
343
+ const workerCount = Math.min(this.concurrency, ordered.length);
344
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
345
+ return results;
346
+ }
347
+ findFirst(targets, operation) {
348
+ this.metrics.operationCount++;
349
+ const ordered = this.orderedTargets(targets);
350
+ return new Promise((resolve) => {
351
+ if (ordered.length === 0) {
352
+ resolve({ result: null, shardId: null });
353
+ return;
354
+ }
355
+ let nextIndex = 0;
356
+ let active = 0;
357
+ let completed = 0;
358
+ let found = false;
359
+ const launch = () => {
360
+ while (!found && active < this.concurrency && nextIndex < ordered.length) {
361
+ const target = ordered[nextIndex++];
362
+ active++;
363
+ this.withTimeout(target.shardId, () => operation(target.client)).then((result) => {
364
+ if (!found && result !== null && result !== void 0) {
365
+ found = true;
366
+ resolve({ result, shardId: target.shardId });
367
+ }
368
+ }).catch((error) => {
369
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
370
+ this.recordFailure(target.shardId, normalizedError);
371
+ }).finally(() => {
372
+ active--;
373
+ completed++;
374
+ if (!found && completed === ordered.length) {
375
+ resolve({ result: null, shardId: null });
376
+ return;
377
+ }
378
+ launch();
379
+ });
380
+ }
381
+ };
382
+ launch();
383
+ });
384
+ }
385
+ };
386
+
184
387
  // src/core/manager.ts
388
+ var isObject = (value) => typeof value === "object" && value !== null;
389
+ var isConnectable = (client) => isObject(client) && typeof client.$connect === "function";
390
+ var isDisconnectable = (client) => isObject(client) && typeof client.$disconnect === "function";
391
+ var isQueryable = (client) => isObject(client) && typeof client.$queryRaw === "function";
392
+ var withTimeout = (operation, timeoutMs, timeoutMessage) => new Promise((resolve, reject) => {
393
+ let settled = false;
394
+ const timer = setTimeout(() => {
395
+ if (!settled) {
396
+ settled = true;
397
+ reject(new Error(timeoutMessage));
398
+ }
399
+ }, timeoutMs);
400
+ operation.then(
401
+ (result) => {
402
+ if (!settled) {
403
+ settled = true;
404
+ clearTimeout(timer);
405
+ resolve(result);
406
+ }
407
+ },
408
+ (error) => {
409
+ if (!settled) {
410
+ settled = true;
411
+ clearTimeout(timer);
412
+ reject(error);
413
+ }
414
+ }
415
+ );
416
+ });
185
417
  var ShardManager = class {
186
418
  constructor(config) {
187
419
  this.instances = /* @__PURE__ */ new Map();
420
+ this.unhealthyAccessWarnings = /* @__PURE__ */ new Set();
188
421
  this.healthCheckInterval = null;
422
+ this.healthCheckInFlight = false;
423
+ this.healthCheckPromise = null;
189
424
  this.config = config;
425
+ this.executor = new CrossShardExecutor();
190
426
  }
191
427
  async initialize() {
192
428
  this.config.logger.info(`Initializing ${this.config.shards.length} shard(s)...`);
193
- for (const shardConfig of this.config.shards) {
194
- this.initializeShard(shardConfig);
429
+ try {
430
+ for (const shardConfig of this.config.shards) {
431
+ this.initializeShard(shardConfig);
432
+ }
433
+ } catch (error) {
434
+ await this.disconnectInstances();
435
+ this.instances.clear();
436
+ throw error;
195
437
  }
438
+ void this.performHealthChecks(true).catch((error) => {
439
+ const message = error instanceof Error ? error.message : String(error);
440
+ this.config.logger.error(`Initial shard health verification failed: ${message}`);
441
+ });
196
442
  this.startHealthChecks();
197
443
  this.config.logger.info("All shards initialized successfully");
198
444
  }
@@ -214,7 +460,8 @@ var ShardManager = class {
214
460
  });
215
461
  this.config.logger.info(`Shard ${shardConfig.id} initialized`);
216
462
  } catch (error) {
217
- this.config.logger.error(`Failed to initialize shard ${shardConfig.id}: ${error}`);
463
+ const message = sanitizeDatabaseText(String(error), [shardConfig.url]);
464
+ this.config.logger.error(`Failed to initialize shard ${shardConfig.id}: ${message}`);
218
465
  throw new ConnectionError(`Failed to initialize shard ${shardConfig.id}`, shardConfig.id);
219
466
  }
220
467
  }
@@ -222,58 +469,108 @@ var ShardManager = class {
222
469
  if (this.healthCheckInterval) {
223
470
  clearInterval(this.healthCheckInterval);
224
471
  }
225
- this.healthCheckInterval = setInterval(async () => {
226
- await this.performHealthChecks();
472
+ this.healthCheckInterval = setInterval(() => {
473
+ if (!this.healthCheckInFlight) {
474
+ void this.performHealthChecks(false);
475
+ }
227
476
  }, this.config.healthCheckIntervalMs);
477
+ this.healthCheckInterval.unref?.();
228
478
  }
229
- async performHealthChecks() {
230
- const checks = Array.from(this.instances.values()).map(async (instance) => {
231
- const startTime = Date.now();
232
- try {
233
- const client = instance.client;
234
- if (typeof client.$queryRaw === "function") {
235
- await client.$queryRaw`SELECT 1`;
479
+ async verifyShard(instance, warmConnection) {
480
+ const startTime = Date.now();
481
+ const previousHealth = instance.health;
482
+ try {
483
+ const verification = async () => {
484
+ if (warmConnection && isConnectable(instance.client)) {
485
+ await instance.client.$connect();
236
486
  }
237
- const latencyMs = Date.now() - startTime;
238
- instance.health = {
239
- ...instance.health,
240
- isHealthy: true,
241
- latencyMs,
242
- lastChecked: /* @__PURE__ */ new Date(),
243
- consecutiveFailures: 0
244
- };
245
- } catch (error) {
246
- const consecutiveFailures = instance.health.consecutiveFailures + 1;
247
- const isHealthy = consecutiveFailures < this.config.circuitBreakerThreshold;
248
- instance.health = {
249
- ...instance.health,
250
- isHealthy,
251
- latencyMs: -1,
252
- lastChecked: /* @__PURE__ */ new Date(),
253
- errorCount: instance.health.errorCount + 1,
254
- consecutiveFailures
255
- };
256
- if (!isHealthy) {
257
- this.config.logger.error(
258
- `Shard ${instance.config.id} marked unhealthy after ${consecutiveFailures} consecutive failures`
259
- );
487
+ if (isQueryable(instance.client)) {
488
+ await instance.client.$queryRaw`SELECT 1`;
260
489
  }
490
+ };
491
+ await withTimeout(
492
+ verification(),
493
+ INTERNAL_DEFAULTS.HEALTH_CHECK_TIMEOUT_MS,
494
+ `Health check timed out for shard ${instance.config.id}`
495
+ );
496
+ instance.health = {
497
+ ...previousHealth,
498
+ isHealthy: true,
499
+ latencyMs: Date.now() - startTime,
500
+ lastChecked: /* @__PURE__ */ new Date(),
501
+ consecutiveFailures: 0
502
+ };
503
+ this.unhealthyAccessWarnings.delete(instance.config.id);
504
+ if (!previousHealth.isHealthy && previousHealth.consecutiveFailures > 0) {
505
+ this.config.logger.info(`Shard ${instance.config.id} recovered`);
261
506
  }
507
+ } catch (error) {
508
+ const consecutiveFailures = previousHealth.consecutiveFailures + 1;
509
+ const wasVerifiedHealthy = previousHealth.isHealthy;
510
+ const isHealthy = warmConnection ? false : wasVerifiedHealthy && consecutiveFailures < this.config.circuitBreakerThreshold;
511
+ instance.health = {
512
+ ...previousHealth,
513
+ isHealthy,
514
+ latencyMs: -1,
515
+ lastChecked: /* @__PURE__ */ new Date(),
516
+ errorCount: previousHealth.errorCount + 1,
517
+ consecutiveFailures
518
+ };
519
+ const message = sanitizeDatabaseText(
520
+ error instanceof Error ? error.message : String(error),
521
+ [instance.config.url]
522
+ );
523
+ const isFirstFailure = previousHealth.errorCount === 0;
524
+ const becameUnhealthy = previousHealth.isHealthy && !isHealthy;
525
+ if (isFirstFailure || becameUnhealthy) {
526
+ this.config.logger.error(
527
+ `Health check failed for ${instance.config.id}: ${message}${!isHealthy ? "; shard marked unhealthy" : ""}`
528
+ );
529
+ }
530
+ }
531
+ }
532
+ performHealthChecks(warmConnections) {
533
+ if (this.healthCheckInFlight) {
534
+ return this.healthCheckPromise ?? Promise.resolve();
535
+ }
536
+ this.healthCheckInFlight = true;
537
+ const operation = this.executor.executeAll(this.getExecutionTargets(), async (_client, shardId) => {
538
+ const instance = this.instances.get(shardId);
539
+ if (instance) {
540
+ await this.verifyShard(instance, warmConnections);
541
+ }
542
+ return null;
543
+ }).then(() => void 0).finally(() => {
544
+ this.healthCheckInFlight = false;
545
+ this.healthCheckPromise = null;
262
546
  });
263
- await Promise.allSettled(checks);
547
+ this.healthCheckPromise = operation;
548
+ return operation;
549
+ }
550
+ getExecutionTargets() {
551
+ return Array.from(this.instances.values()).map((instance) => ({
552
+ shardId: instance.config.id,
553
+ client: instance.client,
554
+ isHealthy: instance.health.isHealthy,
555
+ latencyMs: instance.health.latencyMs
556
+ }));
264
557
  }
265
558
  getClient(shardId) {
266
559
  const instance = this.instances.get(shardId);
267
560
  if (!instance) {
268
561
  throw new ConnectionError(`Shard ${shardId} not found`, shardId);
269
562
  }
270
- if (!instance.health.isHealthy) {
563
+ if (!instance.health.isHealthy && !this.unhealthyAccessWarnings.has(shardId)) {
564
+ this.unhealthyAccessWarnings.add(shardId);
271
565
  this.config.logger.warn(`Accessing unhealthy shard ${shardId}`);
272
566
  }
273
567
  return instance.client;
274
568
  }
275
569
  getClientByIndex(index) {
276
- const shardId = `shard_${index + 1}`;
570
+ const shardId = this.getShardIds()[index];
571
+ if (!shardId) {
572
+ throw new ConnectionError(`Shard index ${index} not found`, String(index));
573
+ }
277
574
  return this.getClient(shardId);
278
575
  }
279
576
  getAllClients() {
@@ -294,56 +591,38 @@ var ShardManager = class {
294
591
  getAllHealth() {
295
592
  return Array.from(this.instances.values()).map((instance) => instance.health);
296
593
  }
297
- async executeOnAll(operation) {
298
- const results = await Promise.allSettled(
299
- Array.from(this.instances.entries()).map(async ([shardId, instance]) => {
300
- try {
301
- const result = await operation(instance.client, shardId);
302
- return { shardId, result, error: void 0 };
303
- } catch (error) {
304
- return { shardId, result: null, error };
305
- }
306
- })
307
- );
308
- return results.map((result) => {
309
- if (result.status === "fulfilled") {
310
- return result.value;
311
- }
312
- return {
313
- shardId: "unknown",
314
- result: null,
315
- error: result.reason
316
- };
317
- });
594
+ executeOnAll(operation) {
595
+ return this.executor.executeAll(this.getExecutionTargets(), operation);
318
596
  }
319
- async findFirst(operation) {
320
- const results = await this.executeOnAll(operation);
321
- for (const res of results) {
322
- if (res.result !== null && res.result !== void 0) {
323
- return { result: res.result, shardId: res.shardId };
324
- }
325
- }
326
- return { result: null, shardId: null };
597
+ findFirst(operation) {
598
+ return this.executor.findFirst(this.getExecutionTargets(), operation);
327
599
  }
328
- async shutdown() {
329
- this.config.logger.info("Shutting down...");
330
- if (this.healthCheckInterval) {
331
- clearInterval(this.healthCheckInterval);
332
- this.healthCheckInterval = null;
333
- }
600
+ async disconnectInstances() {
334
601
  const disconnects = Array.from(this.instances.values()).map(async (instance) => {
335
602
  try {
336
- const client = instance.client;
337
- if (typeof client.$disconnect === "function") {
338
- await client.$disconnect();
603
+ if (isDisconnectable(instance.client)) {
604
+ await instance.client.$disconnect();
339
605
  }
340
606
  this.config.logger.info(`Shard ${instance.config.id} disconnected`);
341
607
  } catch (error) {
342
- this.config.logger.error(`Error disconnecting shard ${instance.config.id}: ${error}`);
608
+ const message = sanitizeDatabaseText(String(error), [instance.config.url]);
609
+ this.config.logger.error(`Error disconnecting shard ${instance.config.id}: ${message}`);
343
610
  }
344
611
  });
345
612
  await Promise.allSettled(disconnects);
613
+ }
614
+ async shutdown() {
615
+ this.config.logger.info("Shutting down...");
616
+ if (this.healthCheckInterval) {
617
+ clearInterval(this.healthCheckInterval);
618
+ this.healthCheckInterval = null;
619
+ }
620
+ if (this.healthCheckPromise) {
621
+ await this.healthCheckPromise;
622
+ }
623
+ await this.disconnectInstances();
346
624
  this.instances.clear();
625
+ this.unhealthyAccessWarnings.clear();
347
626
  this.config.logger.info("Shutdown complete");
348
627
  }
349
628
  };
@@ -362,17 +641,34 @@ var PrismaSharding = class {
362
641
  if (!config.shards || config.shards.length === 0) {
363
642
  throw new ConfigError(ERROR_MESSAGES.NO_SHARDS);
364
643
  }
365
- if (!config.createClient) {
644
+ if (typeof config.createClient !== "function") {
366
645
  throw new ConfigError(ERROR_MESSAGES.MISSING_CLIENT_FACTORY);
367
646
  }
647
+ const shardIds = /* @__PURE__ */ new Set();
368
648
  for (const shard of config.shards) {
649
+ if (!shard.id || shard.id.trim().length === 0) {
650
+ throw new ConfigError("Shard ID must not be empty");
651
+ }
652
+ if (shardIds.has(shard.id)) {
653
+ throw new ConfigError(`Duplicate shard ID: "${shard.id}"`);
654
+ }
655
+ shardIds.add(shard.id);
369
656
  if (!shard.url || !validateUrl(shard.url)) {
370
657
  throw new ConfigError(ERROR_MESSAGES.INVALID_SHARD_URL(shard.id));
371
658
  }
659
+ if (shard.weight !== void 0 && (!Number.isFinite(shard.weight) || shard.weight <= 0)) {
660
+ throw new ConfigError(`Weight for shard "${shard.id}" must be positive`);
661
+ }
372
662
  }
373
663
  if (config.strategy && config.strategy !== "modulo" && config.strategy !== "consistent-hash") {
374
664
  throw new ConfigError(ERROR_MESSAGES.INVALID_STRATEGY(config.strategy));
375
665
  }
666
+ if (config.healthCheckIntervalMs !== void 0 && (!Number.isFinite(config.healthCheckIntervalMs) || config.healthCheckIntervalMs <= 0)) {
667
+ throw new ConfigError("healthCheckIntervalMs must be positive");
668
+ }
669
+ if (config.circuitBreakerThreshold !== void 0 && (!Number.isFinite(config.circuitBreakerThreshold) || config.circuitBreakerThreshold <= 0)) {
670
+ throw new ConfigError("circuitBreakerThreshold must be positive");
671
+ }
376
672
  }
377
673
  async connect() {
378
674
  if (this.connected) {
@@ -386,10 +682,20 @@ var PrismaSharding = class {
386
682
  circuitBreakerThreshold: this.config.circuitBreakerThreshold ?? DEFAULTS.CIRCUIT_BREAKER_THRESHOLD,
387
683
  logger: this.logger
388
684
  });
389
- await this.manager.initialize();
685
+ try {
686
+ await this.manager.initialize();
687
+ } catch (error) {
688
+ this.manager = null;
689
+ this.router = null;
690
+ this.connected = false;
691
+ throw error;
692
+ }
390
693
  this.router = new ShardRouter({
391
694
  strategy: this.config.strategy ?? "modulo",
392
695
  shardIds: this.manager.getShardIds(),
696
+ shardWeights: new Map(
697
+ this.config.shards.map((shard) => [shard.id, shard.weight ?? 1])
698
+ ),
393
699
  logger: this.logger
394
700
  });
395
701
  this.connected = true;