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.mjs CHANGED
@@ -96,6 +96,11 @@ var ShardRouter = class {
96
96
  if (this.strategy === "consistent-hash") {
97
97
  this.initializeConsistentHashRing();
98
98
  }
99
+ this.sortedRingHashes = Array.from(this.consistentHashRing.keys()).sort((a, b) => a - b);
100
+ this.randomShardWeights = this.shardIds.map(
101
+ (shardId) => config.shardWeights?.get(shardId) ?? 1
102
+ );
103
+ this.totalRandomWeight = this.randomShardWeights.reduce((sum, weight) => sum + weight, 0);
99
104
  }
100
105
  initializeConsistentHashRing() {
101
106
  for (const shardId of this.shardIds) {
@@ -120,25 +125,49 @@ var ShardRouter = class {
120
125
  return hash % shardCount;
121
126
  }
122
127
  getIndexConsistentHash(key) {
128
+ return this.shardIds.indexOf(this.getConsistentHashShardId(key));
129
+ }
130
+ getConsistentHashShardId(key) {
123
131
  const hash = hashString(key);
124
- const sortedHashes = Array.from(this.consistentHashRing.keys()).sort((a, b) => a - b);
125
- for (const ringHash of sortedHashes) {
126
- if (hash <= ringHash) {
127
- const shardId = this.consistentHashRing.get(ringHash);
128
- const match2 = shardId.match(/shard_(\d+)/);
129
- return match2 ? parseInt(match2[1], 10) - 1 : 0;
132
+ let low = 0;
133
+ let high = this.sortedRingHashes.length - 1;
134
+ let matchIndex = 0;
135
+ while (low <= high) {
136
+ const middle = Math.floor((low + high) / 2);
137
+ if (this.sortedRingHashes[middle] >= hash) {
138
+ matchIndex = middle;
139
+ high = middle - 1;
140
+ } else {
141
+ low = middle + 1;
130
142
  }
131
143
  }
132
- const firstShardId = this.consistentHashRing.get(sortedHashes[0]);
133
- const match = firstShardId.match(/shard_(\d+)/);
134
- return match ? parseInt(match[1], 10) - 1 : 0;
144
+ if (low >= this.sortedRingHashes.length) {
145
+ matchIndex = 0;
146
+ }
147
+ return this.consistentHashRing.get(this.sortedRingHashes[matchIndex]);
135
148
  }
136
149
  getShardId(key) {
150
+ if (this.shardIds.length === 0) {
151
+ throw new RoutingError("No shards available");
152
+ }
153
+ if (this.strategy === "consistent-hash") {
154
+ return this.getConsistentHashShardId(key);
155
+ }
137
156
  const index = this.getShardIndex(key);
138
157
  return this.shardIds[index] || `shard_${index + 1}`;
139
158
  }
140
159
  getRandomShardIndex() {
141
- return Math.floor(Math.random() * this.shardIds.length);
160
+ if (this.shardIds.length === 0) {
161
+ throw new RoutingError("No shards available");
162
+ }
163
+ let selection = Math.random() * this.totalRandomWeight;
164
+ for (let index = 0; index < this.randomShardWeights.length; index++) {
165
+ selection -= this.randomShardWeights[index];
166
+ if (selection < 0) {
167
+ return index;
168
+ }
169
+ }
170
+ return this.shardIds.length - 1;
142
171
  }
143
172
  getRandomShardId() {
144
173
  const index = this.getRandomShardIndex();
@@ -146,18 +175,235 @@ var ShardRouter = class {
146
175
  }
147
176
  };
148
177
 
178
+ // src/constants/internal.ts
179
+ var INTERNAL_DEFAULTS = {
180
+ HEALTH_CHECK_TIMEOUT_MS: 5e3,
181
+ CROSS_SHARD_CONCURRENCY: 4,
182
+ CROSS_SHARD_TIMEOUT_MS: 1e4,
183
+ CLI_COMMAND_TIMEOUT_MS: 12e4,
184
+ CLI_FORCE_KILL_GRACE_MS: 5e3,
185
+ CLI_MAX_OUTPUT_LENGTH: 1e6,
186
+ CLI_TEST_TCP_TIMEOUT_MS: 5e3,
187
+ CLI_TEST_COMMAND_TIMEOUT_MS: 15e3,
188
+ STUDIO_BASE_PORT: 51212,
189
+ STUDIO_REUSE_EXISTING: true,
190
+ STUDIO_STRICT_PORT_CHECK: false,
191
+ STUDIO_START_TIMEOUT_MS: 15e3,
192
+ STUDIO_STABILITY_MS: 500,
193
+ STUDIO_SHUTDOWN_TIMEOUT_MS: 5e3
194
+ };
195
+
196
+ // src/utils/sanitize.ts
197
+ var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
198
+ var maskDatabaseUrl = (url) => {
199
+ try {
200
+ const parsed = new URL(url);
201
+ if (parsed.password) {
202
+ parsed.password = "***";
203
+ }
204
+ return parsed.toString();
205
+ } catch {
206
+ return url.replace(/:[^:@]+@/, ":***@");
207
+ }
208
+ };
209
+ var sanitizeDatabaseText = (output, databaseUrls) => {
210
+ let sanitized = output;
211
+ for (const url of databaseUrls) {
212
+ sanitized = sanitized.replace(new RegExp(escapeRegExp(url), "g"), maskDatabaseUrl(url));
213
+ try {
214
+ const password = new URL(url).password;
215
+ if (password.length >= 4) {
216
+ sanitized = sanitized.replace(
217
+ new RegExp(escapeRegExp(decodeURIComponent(password)), "g"),
218
+ "***"
219
+ );
220
+ }
221
+ } catch {
222
+ }
223
+ }
224
+ return sanitized.replace(
225
+ /(postgres(?:ql)?:\/\/[^:/\s]+:)[^@\s]+(@)/gi,
226
+ "$1***$2"
227
+ );
228
+ };
229
+
230
+ // src/core/executor.ts
231
+ var timeoutError = (shardId, timeoutMs) => new Error(`Shard ${shardId} operation timed out after ${timeoutMs}ms`);
232
+ var CrossShardExecutor = class {
233
+ constructor(config = {}) {
234
+ this.metrics = {
235
+ operationCount: 0,
236
+ timeoutCount: 0,
237
+ failuresByShard: /* @__PURE__ */ new Map()
238
+ };
239
+ this.concurrency = config.concurrency ?? INTERNAL_DEFAULTS.CROSS_SHARD_CONCURRENCY;
240
+ this.timeoutMs = config.timeoutMs ?? INTERNAL_DEFAULTS.CROSS_SHARD_TIMEOUT_MS;
241
+ }
242
+ orderedTargets(targets) {
243
+ return targets.map((target, index) => ({ target, index })).sort((left, right) => {
244
+ if (left.target.isHealthy !== right.target.isHealthy) {
245
+ return left.target.isHealthy ? -1 : 1;
246
+ }
247
+ const leftLatency = left.target.latencyMs >= 0 ? left.target.latencyMs : Number.POSITIVE_INFINITY;
248
+ const rightLatency = right.target.latencyMs >= 0 ? right.target.latencyMs : Number.POSITIVE_INFINITY;
249
+ return leftLatency - rightLatency || left.index - right.index;
250
+ }).map(({ target }) => target);
251
+ }
252
+ recordFailure(shardId, error) {
253
+ this.metrics.failuresByShard.set(
254
+ shardId,
255
+ (this.metrics.failuresByShard.get(shardId) ?? 0) + 1
256
+ );
257
+ if (error.message.includes("timed out")) {
258
+ this.metrics.timeoutCount++;
259
+ }
260
+ }
261
+ withTimeout(shardId, operation) {
262
+ return new Promise((resolve, reject) => {
263
+ let settled = false;
264
+ const finish = (callback) => {
265
+ if (settled) {
266
+ return;
267
+ }
268
+ settled = true;
269
+ clearTimeout(timer);
270
+ callback();
271
+ };
272
+ const timer = setTimeout(() => {
273
+ finish(() => reject(timeoutError(shardId, this.timeoutMs)));
274
+ }, this.timeoutMs);
275
+ Promise.resolve().then(operation).then(
276
+ (result) => finish(() => resolve(result)),
277
+ (error) => finish(() => reject(error))
278
+ );
279
+ });
280
+ }
281
+ async executeAll(targets, operation) {
282
+ this.metrics.operationCount++;
283
+ const originalIndexes = new Map(targets.map((target, index) => [target.shardId, index]));
284
+ const ordered = this.orderedTargets(targets);
285
+ const results = new Array(targets.length);
286
+ let nextIndex = 0;
287
+ const worker = async () => {
288
+ while (nextIndex < ordered.length) {
289
+ const target = ordered[nextIndex++];
290
+ const resultIndex = originalIndexes.get(target.shardId);
291
+ try {
292
+ const result = await this.withTimeout(
293
+ target.shardId,
294
+ () => operation(target.client, target.shardId)
295
+ );
296
+ results[resultIndex] = { shardId: target.shardId, result, error: void 0 };
297
+ } catch (error) {
298
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
299
+ this.recordFailure(target.shardId, normalizedError);
300
+ results[resultIndex] = {
301
+ shardId: target.shardId,
302
+ result: null,
303
+ error: normalizedError
304
+ };
305
+ }
306
+ }
307
+ };
308
+ const workerCount = Math.min(this.concurrency, ordered.length);
309
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
310
+ return results;
311
+ }
312
+ findFirst(targets, operation) {
313
+ this.metrics.operationCount++;
314
+ const ordered = this.orderedTargets(targets);
315
+ return new Promise((resolve) => {
316
+ if (ordered.length === 0) {
317
+ resolve({ result: null, shardId: null });
318
+ return;
319
+ }
320
+ let nextIndex = 0;
321
+ let active = 0;
322
+ let completed = 0;
323
+ let found = false;
324
+ const launch = () => {
325
+ while (!found && active < this.concurrency && nextIndex < ordered.length) {
326
+ const target = ordered[nextIndex++];
327
+ active++;
328
+ this.withTimeout(target.shardId, () => operation(target.client)).then((result) => {
329
+ if (!found && result !== null && result !== void 0) {
330
+ found = true;
331
+ resolve({ result, shardId: target.shardId });
332
+ }
333
+ }).catch((error) => {
334
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
335
+ this.recordFailure(target.shardId, normalizedError);
336
+ }).finally(() => {
337
+ active--;
338
+ completed++;
339
+ if (!found && completed === ordered.length) {
340
+ resolve({ result: null, shardId: null });
341
+ return;
342
+ }
343
+ launch();
344
+ });
345
+ }
346
+ };
347
+ launch();
348
+ });
349
+ }
350
+ };
351
+
149
352
  // src/core/manager.ts
353
+ var isObject = (value) => typeof value === "object" && value !== null;
354
+ var isConnectable = (client) => isObject(client) && typeof client.$connect === "function";
355
+ var isDisconnectable = (client) => isObject(client) && typeof client.$disconnect === "function";
356
+ var isQueryable = (client) => isObject(client) && typeof client.$queryRaw === "function";
357
+ var withTimeout = (operation, timeoutMs, timeoutMessage) => new Promise((resolve, reject) => {
358
+ let settled = false;
359
+ const timer = setTimeout(() => {
360
+ if (!settled) {
361
+ settled = true;
362
+ reject(new Error(timeoutMessage));
363
+ }
364
+ }, timeoutMs);
365
+ operation.then(
366
+ (result) => {
367
+ if (!settled) {
368
+ settled = true;
369
+ clearTimeout(timer);
370
+ resolve(result);
371
+ }
372
+ },
373
+ (error) => {
374
+ if (!settled) {
375
+ settled = true;
376
+ clearTimeout(timer);
377
+ reject(error);
378
+ }
379
+ }
380
+ );
381
+ });
150
382
  var ShardManager = class {
151
383
  constructor(config) {
152
384
  this.instances = /* @__PURE__ */ new Map();
385
+ this.unhealthyAccessWarnings = /* @__PURE__ */ new Set();
153
386
  this.healthCheckInterval = null;
387
+ this.healthCheckInFlight = false;
388
+ this.healthCheckPromise = null;
154
389
  this.config = config;
390
+ this.executor = new CrossShardExecutor();
155
391
  }
156
392
  async initialize() {
157
393
  this.config.logger.info(`Initializing ${this.config.shards.length} shard(s)...`);
158
- for (const shardConfig of this.config.shards) {
159
- this.initializeShard(shardConfig);
394
+ try {
395
+ for (const shardConfig of this.config.shards) {
396
+ this.initializeShard(shardConfig);
397
+ }
398
+ } catch (error) {
399
+ await this.disconnectInstances();
400
+ this.instances.clear();
401
+ throw error;
160
402
  }
403
+ void this.performHealthChecks(true).catch((error) => {
404
+ const message = error instanceof Error ? error.message : String(error);
405
+ this.config.logger.error(`Initial shard health verification failed: ${message}`);
406
+ });
161
407
  this.startHealthChecks();
162
408
  this.config.logger.info("All shards initialized successfully");
163
409
  }
@@ -179,7 +425,8 @@ var ShardManager = class {
179
425
  });
180
426
  this.config.logger.info(`Shard ${shardConfig.id} initialized`);
181
427
  } catch (error) {
182
- this.config.logger.error(`Failed to initialize shard ${shardConfig.id}: ${error}`);
428
+ const message = sanitizeDatabaseText(String(error), [shardConfig.url]);
429
+ this.config.logger.error(`Failed to initialize shard ${shardConfig.id}: ${message}`);
183
430
  throw new ConnectionError(`Failed to initialize shard ${shardConfig.id}`, shardConfig.id);
184
431
  }
185
432
  }
@@ -187,58 +434,108 @@ var ShardManager = class {
187
434
  if (this.healthCheckInterval) {
188
435
  clearInterval(this.healthCheckInterval);
189
436
  }
190
- this.healthCheckInterval = setInterval(async () => {
191
- await this.performHealthChecks();
437
+ this.healthCheckInterval = setInterval(() => {
438
+ if (!this.healthCheckInFlight) {
439
+ void this.performHealthChecks(false);
440
+ }
192
441
  }, this.config.healthCheckIntervalMs);
442
+ this.healthCheckInterval.unref?.();
193
443
  }
194
- async performHealthChecks() {
195
- const checks = Array.from(this.instances.values()).map(async (instance) => {
196
- const startTime = Date.now();
197
- try {
198
- const client = instance.client;
199
- if (typeof client.$queryRaw === "function") {
200
- await client.$queryRaw`SELECT 1`;
444
+ async verifyShard(instance, warmConnection) {
445
+ const startTime = Date.now();
446
+ const previousHealth = instance.health;
447
+ try {
448
+ const verification = async () => {
449
+ if (warmConnection && isConnectable(instance.client)) {
450
+ await instance.client.$connect();
201
451
  }
202
- const latencyMs = Date.now() - startTime;
203
- instance.health = {
204
- ...instance.health,
205
- isHealthy: true,
206
- latencyMs,
207
- lastChecked: /* @__PURE__ */ new Date(),
208
- consecutiveFailures: 0
209
- };
210
- } catch (error) {
211
- const consecutiveFailures = instance.health.consecutiveFailures + 1;
212
- const isHealthy = consecutiveFailures < this.config.circuitBreakerThreshold;
213
- instance.health = {
214
- ...instance.health,
215
- isHealthy,
216
- latencyMs: -1,
217
- lastChecked: /* @__PURE__ */ new Date(),
218
- errorCount: instance.health.errorCount + 1,
219
- consecutiveFailures
220
- };
221
- if (!isHealthy) {
222
- this.config.logger.error(
223
- `Shard ${instance.config.id} marked unhealthy after ${consecutiveFailures} consecutive failures`
224
- );
452
+ if (isQueryable(instance.client)) {
453
+ await instance.client.$queryRaw`SELECT 1`;
225
454
  }
455
+ };
456
+ await withTimeout(
457
+ verification(),
458
+ INTERNAL_DEFAULTS.HEALTH_CHECK_TIMEOUT_MS,
459
+ `Health check timed out for shard ${instance.config.id}`
460
+ );
461
+ instance.health = {
462
+ ...previousHealth,
463
+ isHealthy: true,
464
+ latencyMs: Date.now() - startTime,
465
+ lastChecked: /* @__PURE__ */ new Date(),
466
+ consecutiveFailures: 0
467
+ };
468
+ this.unhealthyAccessWarnings.delete(instance.config.id);
469
+ if (!previousHealth.isHealthy && previousHealth.consecutiveFailures > 0) {
470
+ this.config.logger.info(`Shard ${instance.config.id} recovered`);
226
471
  }
472
+ } catch (error) {
473
+ const consecutiveFailures = previousHealth.consecutiveFailures + 1;
474
+ const wasVerifiedHealthy = previousHealth.isHealthy;
475
+ const isHealthy = warmConnection ? false : wasVerifiedHealthy && consecutiveFailures < this.config.circuitBreakerThreshold;
476
+ instance.health = {
477
+ ...previousHealth,
478
+ isHealthy,
479
+ latencyMs: -1,
480
+ lastChecked: /* @__PURE__ */ new Date(),
481
+ errorCount: previousHealth.errorCount + 1,
482
+ consecutiveFailures
483
+ };
484
+ const message = sanitizeDatabaseText(
485
+ error instanceof Error ? error.message : String(error),
486
+ [instance.config.url]
487
+ );
488
+ const isFirstFailure = previousHealth.errorCount === 0;
489
+ const becameUnhealthy = previousHealth.isHealthy && !isHealthy;
490
+ if (isFirstFailure || becameUnhealthy) {
491
+ this.config.logger.error(
492
+ `Health check failed for ${instance.config.id}: ${message}${!isHealthy ? "; shard marked unhealthy" : ""}`
493
+ );
494
+ }
495
+ }
496
+ }
497
+ performHealthChecks(warmConnections) {
498
+ if (this.healthCheckInFlight) {
499
+ return this.healthCheckPromise ?? Promise.resolve();
500
+ }
501
+ this.healthCheckInFlight = true;
502
+ const operation = this.executor.executeAll(this.getExecutionTargets(), async (_client, shardId) => {
503
+ const instance = this.instances.get(shardId);
504
+ if (instance) {
505
+ await this.verifyShard(instance, warmConnections);
506
+ }
507
+ return null;
508
+ }).then(() => void 0).finally(() => {
509
+ this.healthCheckInFlight = false;
510
+ this.healthCheckPromise = null;
227
511
  });
228
- await Promise.allSettled(checks);
512
+ this.healthCheckPromise = operation;
513
+ return operation;
514
+ }
515
+ getExecutionTargets() {
516
+ return Array.from(this.instances.values()).map((instance) => ({
517
+ shardId: instance.config.id,
518
+ client: instance.client,
519
+ isHealthy: instance.health.isHealthy,
520
+ latencyMs: instance.health.latencyMs
521
+ }));
229
522
  }
230
523
  getClient(shardId) {
231
524
  const instance = this.instances.get(shardId);
232
525
  if (!instance) {
233
526
  throw new ConnectionError(`Shard ${shardId} not found`, shardId);
234
527
  }
235
- if (!instance.health.isHealthy) {
528
+ if (!instance.health.isHealthy && !this.unhealthyAccessWarnings.has(shardId)) {
529
+ this.unhealthyAccessWarnings.add(shardId);
236
530
  this.config.logger.warn(`Accessing unhealthy shard ${shardId}`);
237
531
  }
238
532
  return instance.client;
239
533
  }
240
534
  getClientByIndex(index) {
241
- const shardId = `shard_${index + 1}`;
535
+ const shardId = this.getShardIds()[index];
536
+ if (!shardId) {
537
+ throw new ConnectionError(`Shard index ${index} not found`, String(index));
538
+ }
242
539
  return this.getClient(shardId);
243
540
  }
244
541
  getAllClients() {
@@ -259,56 +556,38 @@ var ShardManager = class {
259
556
  getAllHealth() {
260
557
  return Array.from(this.instances.values()).map((instance) => instance.health);
261
558
  }
262
- async executeOnAll(operation) {
263
- const results = await Promise.allSettled(
264
- Array.from(this.instances.entries()).map(async ([shardId, instance]) => {
265
- try {
266
- const result = await operation(instance.client, shardId);
267
- return { shardId, result, error: void 0 };
268
- } catch (error) {
269
- return { shardId, result: null, error };
270
- }
271
- })
272
- );
273
- return results.map((result) => {
274
- if (result.status === "fulfilled") {
275
- return result.value;
276
- }
277
- return {
278
- shardId: "unknown",
279
- result: null,
280
- error: result.reason
281
- };
282
- });
559
+ executeOnAll(operation) {
560
+ return this.executor.executeAll(this.getExecutionTargets(), operation);
283
561
  }
284
- async findFirst(operation) {
285
- const results = await this.executeOnAll(operation);
286
- for (const res of results) {
287
- if (res.result !== null && res.result !== void 0) {
288
- return { result: res.result, shardId: res.shardId };
289
- }
290
- }
291
- return { result: null, shardId: null };
562
+ findFirst(operation) {
563
+ return this.executor.findFirst(this.getExecutionTargets(), operation);
292
564
  }
293
- async shutdown() {
294
- this.config.logger.info("Shutting down...");
295
- if (this.healthCheckInterval) {
296
- clearInterval(this.healthCheckInterval);
297
- this.healthCheckInterval = null;
298
- }
565
+ async disconnectInstances() {
299
566
  const disconnects = Array.from(this.instances.values()).map(async (instance) => {
300
567
  try {
301
- const client = instance.client;
302
- if (typeof client.$disconnect === "function") {
303
- await client.$disconnect();
568
+ if (isDisconnectable(instance.client)) {
569
+ await instance.client.$disconnect();
304
570
  }
305
571
  this.config.logger.info(`Shard ${instance.config.id} disconnected`);
306
572
  } catch (error) {
307
- this.config.logger.error(`Error disconnecting shard ${instance.config.id}: ${error}`);
573
+ const message = sanitizeDatabaseText(String(error), [instance.config.url]);
574
+ this.config.logger.error(`Error disconnecting shard ${instance.config.id}: ${message}`);
308
575
  }
309
576
  });
310
577
  await Promise.allSettled(disconnects);
578
+ }
579
+ async shutdown() {
580
+ this.config.logger.info("Shutting down...");
581
+ if (this.healthCheckInterval) {
582
+ clearInterval(this.healthCheckInterval);
583
+ this.healthCheckInterval = null;
584
+ }
585
+ if (this.healthCheckPromise) {
586
+ await this.healthCheckPromise;
587
+ }
588
+ await this.disconnectInstances();
311
589
  this.instances.clear();
590
+ this.unhealthyAccessWarnings.clear();
312
591
  this.config.logger.info("Shutdown complete");
313
592
  }
314
593
  };
@@ -327,17 +606,34 @@ var PrismaSharding = class {
327
606
  if (!config.shards || config.shards.length === 0) {
328
607
  throw new ConfigError(ERROR_MESSAGES.NO_SHARDS);
329
608
  }
330
- if (!config.createClient) {
609
+ if (typeof config.createClient !== "function") {
331
610
  throw new ConfigError(ERROR_MESSAGES.MISSING_CLIENT_FACTORY);
332
611
  }
612
+ const shardIds = /* @__PURE__ */ new Set();
333
613
  for (const shard of config.shards) {
614
+ if (!shard.id || shard.id.trim().length === 0) {
615
+ throw new ConfigError("Shard ID must not be empty");
616
+ }
617
+ if (shardIds.has(shard.id)) {
618
+ throw new ConfigError(`Duplicate shard ID: "${shard.id}"`);
619
+ }
620
+ shardIds.add(shard.id);
334
621
  if (!shard.url || !validateUrl(shard.url)) {
335
622
  throw new ConfigError(ERROR_MESSAGES.INVALID_SHARD_URL(shard.id));
336
623
  }
624
+ if (shard.weight !== void 0 && (!Number.isFinite(shard.weight) || shard.weight <= 0)) {
625
+ throw new ConfigError(`Weight for shard "${shard.id}" must be positive`);
626
+ }
337
627
  }
338
628
  if (config.strategy && config.strategy !== "modulo" && config.strategy !== "consistent-hash") {
339
629
  throw new ConfigError(ERROR_MESSAGES.INVALID_STRATEGY(config.strategy));
340
630
  }
631
+ if (config.healthCheckIntervalMs !== void 0 && (!Number.isFinite(config.healthCheckIntervalMs) || config.healthCheckIntervalMs <= 0)) {
632
+ throw new ConfigError("healthCheckIntervalMs must be positive");
633
+ }
634
+ if (config.circuitBreakerThreshold !== void 0 && (!Number.isFinite(config.circuitBreakerThreshold) || config.circuitBreakerThreshold <= 0)) {
635
+ throw new ConfigError("circuitBreakerThreshold must be positive");
636
+ }
341
637
  }
342
638
  async connect() {
343
639
  if (this.connected) {
@@ -351,10 +647,20 @@ var PrismaSharding = class {
351
647
  circuitBreakerThreshold: this.config.circuitBreakerThreshold ?? DEFAULTS.CIRCUIT_BREAKER_THRESHOLD,
352
648
  logger: this.logger
353
649
  });
354
- await this.manager.initialize();
650
+ try {
651
+ await this.manager.initialize();
652
+ } catch (error) {
653
+ this.manager = null;
654
+ this.router = null;
655
+ this.connected = false;
656
+ throw error;
657
+ }
355
658
  this.router = new ShardRouter({
356
659
  strategy: this.config.strategy ?? "modulo",
357
660
  shardIds: this.manager.getShardIds(),
661
+ shardWeights: new Map(
662
+ this.config.shards.map((shard) => [shard.id, shard.weight ?? 1])
663
+ ),
358
664
  logger: this.logger
359
665
  });
360
666
  this.connected = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prisma-sharding",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Lightweight database sharding library for Prisma with connection pooling and health monitoring",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -35,9 +35,9 @@
35
35
  }
36
36
  },
37
37
  "scripts": {
38
- "build": "tsup src/index.ts --format cjs,esm --dts --clean && tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts src/cli/update.ts --format cjs --outDir dist/cli --clean",
38
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean && tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts src/cli/update.ts src/cli/utils/command.ts src/cli/utils/postgres.ts --format cjs --outDir dist/cli --clean",
39
39
  "build:lib": "tsup src/index.ts --format cjs,esm --dts --clean",
40
- "build:cli": "tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts src/cli/update.ts --format cjs --outDir dist/cli",
40
+ "build:cli": "tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts src/cli/update.ts src/cli/utils/command.ts src/cli/utils/postgres.ts --format cjs --outDir dist/cli",
41
41
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
42
42
  "lint": "eslint src --ext .ts",
43
43
  "test": "yarn build && node --test test/*.test.js",