prisma-sharding 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # Prisma Sharding
2
+
3
+ Lightweight database sharding library for Prisma with connection pooling, health monitoring, and CLI tools.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add prisma-sharding
9
+ # or
10
+ npm install prisma-sharding
11
+ ```
12
+
13
+ > Don't forget to follow me on [GitHub](https://github.com/safdar-azeem)!
14
+
15
+ ## Step 1: Create Sharding Connection
16
+
17
+ ```typescript
18
+ // src/config/prisma.ts
19
+
20
+ import { PrismaSharding } from 'prisma-sharding';
21
+ import { PrismaClient } from '@/generated/prisma';
22
+ import { PrismaPg } from '@prisma/adapter-pg';
23
+
24
+ const sharding = new PrismaSharding<PrismaClient>({
25
+ shards: [
26
+ { id: 'shard_1', url: process.env.SHARD_1_URL! },
27
+ { id: 'shard_2', url: process.env.SHARD_2_URL! },
28
+ ],
29
+ strategy: 'modulo', // 'modulo' | 'consistent-hash'
30
+ createClient: (url) => {
31
+ const adapter = new PrismaPg({ connectionString: url, max: 10 });
32
+ return new PrismaClient({ adapter });
33
+ },
34
+ });
35
+
36
+ await sharding.connect();
37
+ ```
38
+
39
+ ## API
40
+
41
+ | Method | Description |
42
+ | ---------------------------- | --------------------------------------------- |
43
+ | `getShard(key)` | Get Prisma client for a given key |
44
+ | `getShardById(shardId)` | Get Prisma client by shard ID |
45
+ | `getRandomShard()` | Get random shard (for new records) |
46
+ | `findFirst(fn)` | Search across all shards, return first result |
47
+ | `runOnAll(fn)` | Execute on all shards |
48
+ | `getHealth()` | Get health status of all shards |
49
+ | `connect()` / `disconnect()` | Lifecycle methods |
50
+
51
+ ### Step 2: Create a User (Assign to a Shard)
52
+
53
+ New records should be created on a random shard for even distribution.
54
+
55
+ ```ts
56
+ import { sharding } from '@/config/prisma';
57
+
58
+ const client = sharding.getRandomShard();
59
+
60
+ const user = await client.user.create({
61
+ data: {
62
+ email: 'user@example.com',
63
+ username: 'new_user',
64
+ },
65
+ });
66
+ ```
67
+
68
+ ## Step 3: Access User by ID (Shard Routing)
69
+
70
+ When you have a user ID, Prisma Sharding routes you to the correct shard automatically.
71
+
72
+ ```ts
73
+ const userId = 'abc123';
74
+
75
+ const client = sharding.getShard(userId);
76
+
77
+ const user = await client.user.findUnique({
78
+ where: { id: userId },
79
+ });
80
+ ```
81
+
82
+ `Important rule:`
83
+
84
+ Once you get the shard client using a user ID, **all future operations for that user must use this same client**.
85
+
86
+ That includes:
87
+
88
+ - Reading user data
89
+ - Updating user data
90
+ - Creating related records (profiles, posts, settings, etc)
91
+
92
+ Every user belongs to exactly one shard. Their entire data lives on that shard only.
93
+
94
+ Do **not** switch shards or use a random shard for user related actions.
95
+
96
+ Always do this:
97
+
98
+ ```ts
99
+ const client = sharding.getShard(userId);
100
+ ```
101
+
102
+ This guarantees all user data stays on the correct shard and avoids cross shard bugs.
103
+
104
+ ### Step 4: Find User Without ID (Cross Shard Search)
105
+
106
+ If you do not have the user ID, search all shards in parallel.
107
+ Use this only when necessary.
108
+
109
+ ```typescript
110
+ // Find user by email across ALL shards (parallel execution)
111
+ const { result: user, client } = await sharding.findFirst(async (c) =>
112
+ c.user.findFirst({ where: { email } })
113
+ );
114
+
115
+ if (user && client) {
116
+ // Continue operations on the found shard
117
+ await client.user.update({
118
+ where: { id: user.id },
119
+ data: { lastLogin: new Date() },
120
+ });
121
+ }
122
+ ```
123
+
124
+ ## Step 5: Run on All Shards (Admin or Analytics)
125
+
126
+ ```typescript
127
+ // Get counts from all shards
128
+ const counts = await sharding.runOnAll(async (client) => client.user.count());
129
+ const totalUsers = counts.reduce((sum, count) => sum + count, 0);
130
+
131
+ // With detailed results (includes errors)
132
+ const results = await sharding.runOnAllWithDetails(async (client, shardId) => {
133
+ return { shardId, count: await client.user.count() };
134
+ });
135
+ ```
136
+
137
+ ### Health Monitoring
138
+
139
+ ```typescript
140
+ // Get health of all shards
141
+ const health = sharding.getHealth();
142
+ // Returns: [{ shardId, isHealthy, latencyMs, lastChecked, ... }]
143
+
144
+ // Get specific shard health
145
+ const shard1Health = sharding.getHealthByShard('shard_1');
146
+ ```
147
+
148
+ ### Lifecycle
149
+
150
+ ```typescript
151
+ // Graceful shutdown
152
+ await sharding.disconnect();
153
+
154
+ // Check connection status
155
+ if (sharding.isConnected()) {
156
+ // ...
157
+ }
158
+ ```
159
+
160
+ ## CLI Tools
161
+
162
+ The package includes CLI tools for common sharding operations. No need to write custom scripts!
163
+
164
+ ### Setup
165
+
166
+ Add to your `package.json`:
167
+
168
+ ```json
169
+ {
170
+ "scripts": {
171
+ "db:studio:all": "prisma-sharding-studio",
172
+ "migrate:shards": "prisma-sharding-migrate",
173
+ "test:shards": "prisma-sharding-test"
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### Environment Variables
179
+
180
+ ```bash
181
+ SHARD_COUNT=3
182
+ SHARD_1_URL=postgresql://user:pass@host:5432/db1
183
+ SHARD_2_URL=postgresql://user:pass@host:5432/db2
184
+ SHARD_3_URL=postgresql://user:pass@host:5432/db3
185
+ SHARD_ROUTING_STRATEGY=modulo # or consistent-hash
186
+ SHARD_STUDIO_BASE_PORT=51212 # optional, for studio
187
+ ```
188
+
189
+ ### Commands
190
+
191
+ #### `prisma-sharding-migrate`
192
+
193
+ Push schema to all shards using `prisma db push`.
194
+
195
+ ```bash
196
+ yarn migrate:shards
197
+ ```
198
+
199
+ #### `prisma-sharding-studio`
200
+
201
+ Start Prisma Studio for all shards on sequential ports.
202
+
203
+ ```bash
204
+ yarn db:studio:all
205
+ # Opens shard_1 on :51212, shard_2 on :51213, etc.
206
+ ```
207
+
208
+ #### `prisma-sharding-test`
209
+
210
+ Test connections to all shards.
211
+
212
+ ```bash
213
+ yarn test:shards
214
+ ```
215
+
216
+ ## Configuration
217
+
218
+ | Option | Type | Default | Description |
219
+ | ------------------------- | ------------------------------- | ---------- | --------------------------------- |
220
+ | `shards` | `ShardConfig[]` | Required | Array of shard configurations |
221
+ | `strategy` | `'modulo' \| 'consistent-hash'` | `'modulo'` | Routing algorithm |
222
+ | `createClient` | `(url, shardId) => TClient` | Required | Factory to create Prisma clients |
223
+ | `healthCheckIntervalMs` | `number` | `30000` | Health check frequency |
224
+ | `circuitBreakerThreshold` | `number` | `3` | Failures before marking unhealthy |
225
+
226
+ ### Shard Config
227
+
228
+ ```typescript
229
+ interface ShardConfig {
230
+ id: string; // Unique identifier (e.g., 'shard_1')
231
+ url: string; // PostgreSQL connection string
232
+ weight?: number; // Optional weight for distribution
233
+ isReadReplica?: boolean;
234
+ }
235
+ ```
236
+
237
+ ## Routing Strategies
238
+
239
+ ### Modulo (Default)
240
+
241
+ Simple and fast. Uses `hash(key) % shardCount` for routing.
242
+
243
+ ```typescript
244
+ strategy: 'modulo';
245
+ ```
246
+
247
+ ### Consistent Hash
248
+
249
+ Minimizes data movement when adding/removing shards.
250
+
251
+ ```typescript
252
+ strategy: 'consistent-hash';
253
+ ```
254
+
255
+ ## Error Handling
256
+
257
+ ```typescript
258
+ import { ShardingError, ConfigError, ConnectionError } from 'prisma-sharding';
259
+
260
+ try {
261
+ const client = sharding.getShard(userId);
262
+ } catch (error) {
263
+ if (error instanceof ConnectionError) {
264
+ console.error(`Shard ${error.shardId} unavailable`);
265
+ }
266
+ }
267
+ ```
268
+
269
+ ## Custom Logger
270
+
271
+ ```typescript
272
+ const sharding = new PrismaSharding({
273
+ // ...config,
274
+ logger: {
275
+ info: (msg) => myLogger.info(msg),
276
+ warn: (msg) => myLogger.warn(msg),
277
+ error: (msg) => myLogger.error(msg),
278
+ },
279
+ });
280
+ ```
281
+
282
+ ---
283
+
284
+ ### `getAllClients()`
285
+
286
+ Get all Prisma client instances.
287
+
288
+ ```typescript
289
+ const clients = sharding.getAllClients();
290
+
291
+ console.log(`Managing ${clients.length} shard clients`);
292
+ ```
293
+
294
+ **Returns:** `PrismaClient[]`
295
+
296
+ ---
297
+
298
+ ### `getShardCount()`
299
+
300
+ Get total number of configured shards.
301
+
302
+ ```typescript
303
+ const count = sharding.getShardCount();
304
+ console.log(`Running on ${count} shards`);
305
+ // Output: Running on 3 shards
306
+ ```
307
+
308
+ ---
309
+
310
+ ### `getShardIds()`
311
+
312
+ Get array of all shard IDs.
313
+
314
+ ```typescript
315
+ const shardIds = sharding.getShardIds();
316
+ console.log(shardIds);
317
+ // Output: ['shard_1', 'shard_2', 'shard_3']
318
+ ```
319
+
320
+ **Returns:** `string[]`
321
+
322
+ ---
323
+
324
+ ## Author
325
+
326
+ [safdar-azeem](https://github.com/safdar-azeem)
327
+
328
+ ## License
329
+
330
+ MIT
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/migrate.ts
27
+ var import_config = require("dotenv/config");
28
+ var import_child_process = require("child_process");
29
+ var import_path = __toESM(require("path"));
30
+ var getShardConfigs = () => {
31
+ const shards = [];
32
+ const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
33
+ for (let i = 1; i <= shardCount; i++) {
34
+ const url = process.env[`SHARD_${i}_URL`];
35
+ if (url) {
36
+ shards.push({ id: `shard_${i}`, url });
37
+ }
38
+ }
39
+ if (shards.length === 0 && process.env.DATABASE_URL) {
40
+ shards.push({ id: "shard_1", url: process.env.DATABASE_URL });
41
+ }
42
+ return shards;
43
+ };
44
+ var runPrismaCommand = (shardUrl, command) => {
45
+ return new Promise((resolve) => {
46
+ const prisma = (0, import_child_process.spawn)("npx", ["prisma", ...command, "--url", shardUrl], {
47
+ env: process.env,
48
+ cwd: import_path.default.resolve(process.cwd()),
49
+ shell: true
50
+ });
51
+ let output = "";
52
+ let errorOutput = "";
53
+ prisma.stdout.on("data", (data) => {
54
+ output += data.toString();
55
+ });
56
+ prisma.stderr.on("data", (data) => {
57
+ errorOutput += data.toString();
58
+ });
59
+ prisma.on("close", (code) => {
60
+ if (code === 0) {
61
+ resolve({ output });
62
+ } else {
63
+ resolve({ output, error: errorOutput || `Exit code: ${code}` });
64
+ }
65
+ });
66
+ prisma.on("error", (err) => {
67
+ resolve({ output, error: err.message });
68
+ });
69
+ });
70
+ };
71
+ var migrateAllShards = async () => {
72
+ const shards = getShardConfigs();
73
+ console.log("\u{1F504} prisma-sharding: Starting migrations...\n");
74
+ console.log(`\u{1F4CA} Total shards to migrate: ${shards.length}
75
+ `);
76
+ if (shards.length === 0) {
77
+ console.error(
78
+ "\u274C No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables."
79
+ );
80
+ process.exit(1);
81
+ }
82
+ const results = [];
83
+ for (const shard of shards) {
84
+ console.log(`
85
+ \u{1F4E6} Migrating ${shard.id}...`);
86
+ console.log(` URL: ${shard.url.replace(/:[^:@]+@/, ":***@")}`);
87
+ const { output, error } = await runPrismaCommand(shard.url, ["db", "push"]);
88
+ if (error && !output.includes("Your database is now in sync")) {
89
+ console.error(` \u274C Failed: ${error.split("\n")[0]}`);
90
+ results.push({ shardId: shard.id, success: false, output, error });
91
+ } else {
92
+ console.log(` \u2705 Success`);
93
+ results.push({ shardId: shard.id, success: true, output });
94
+ }
95
+ }
96
+ console.log("\n" + "=".repeat(50));
97
+ console.log("\u{1F4CB} Migration Summary\n");
98
+ const successful = results.filter((r) => r.success).length;
99
+ const failed = results.filter((r) => !r.success).length;
100
+ results.forEach((result) => {
101
+ const status = result.success ? "\u2705" : "\u274C";
102
+ console.log(
103
+ ` ${status} ${result.shardId}${result.error ? ` - ${result.error.split("\n")[0]}` : ""}`
104
+ );
105
+ });
106
+ console.log(`
107
+ Total: ${results.length} | Success: ${successful} | Failed: ${failed}`);
108
+ if (failed > 0) {
109
+ console.log("\n\u26A0\uFE0F Some migrations failed. Please review the errors above.");
110
+ process.exit(1);
111
+ }
112
+ console.log("\n\u2705 All shard migrations completed successfully!");
113
+ };
114
+ migrateAllShards().catch((error) => {
115
+ console.error("Migration script failed:", error);
116
+ process.exit(1);
117
+ });
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli/studio.ts
5
+ var import_config = require("dotenv/config");
6
+ var import_child_process = require("child_process");
7
+ var instances = [];
8
+ var BASE_PORT = parseInt(process.env.SHARD_STUDIO_BASE_PORT || "51212", 10);
9
+ var getShardConfigs = () => {
10
+ const shards = [];
11
+ const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
12
+ for (let i = 1; i <= shardCount; i++) {
13
+ const url = process.env[`SHARD_${i}_URL`];
14
+ if (url) {
15
+ shards.push({ id: `shard_${i}`, url });
16
+ }
17
+ }
18
+ if (shards.length === 0 && process.env.DATABASE_URL) {
19
+ shards.push({ id: "shard_1", url: process.env.DATABASE_URL });
20
+ }
21
+ return shards;
22
+ };
23
+ var startStudio = (shard, index) => {
24
+ return new Promise((resolve, reject) => {
25
+ const port = BASE_PORT + index;
26
+ const shardId = shard.id;
27
+ console.log(`
28
+ \u{1F680} Starting Prisma Studio for ${shardId} on port ${port}...`);
29
+ console.log(` URL: ${shard.url.replace(/:[^:@]+@/, ":***@")}`);
30
+ const studioProcess = (0, import_child_process.spawn)(
31
+ "npx",
32
+ ["prisma", "studio", "--port", port.toString(), "--browser", "none"],
33
+ {
34
+ env: {
35
+ ...process.env,
36
+ DATABASE_URL: shard.url
37
+ },
38
+ shell: true,
39
+ stdio: "pipe"
40
+ }
41
+ );
42
+ studioProcess.stdout?.on("data", (data) => {
43
+ const output = data.toString();
44
+ if (output.includes("Prisma Studio is running")) {
45
+ console.log(` \u2705 ${shardId} ready at http://localhost:${port}`);
46
+ }
47
+ });
48
+ studioProcess.stderr?.on("data", (data) => {
49
+ const output = data.toString();
50
+ if (!output.includes("warn") && !output.includes("Loaded")) {
51
+ console.error(` [${shardId}] ${output}`);
52
+ }
53
+ });
54
+ studioProcess.on("error", (err) => {
55
+ console.error(` \u274C Failed to start ${shardId}:`, err.message);
56
+ reject(err);
57
+ });
58
+ const instance = {
59
+ shardId,
60
+ port,
61
+ process: studioProcess
62
+ };
63
+ instances.push(instance);
64
+ setTimeout(() => resolve(instance), 2e3);
65
+ });
66
+ };
67
+ var startAllStudios = async () => {
68
+ const shards = getShardConfigs();
69
+ console.log("=".repeat(60));
70
+ console.log("\u{1F5C4}\uFE0F prisma-sharding: Multi-Shard Studio Viewer");
71
+ console.log("=".repeat(60));
72
+ console.log(`
73
+ \u{1F4CA} Starting ${shards.length} Prisma Studio instance(s)...
74
+ `);
75
+ if (shards.length === 0) {
76
+ console.error(
77
+ "\u274C No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables."
78
+ );
79
+ process.exit(1);
80
+ }
81
+ for (let i = 0; i < shards.length; i++) {
82
+ try {
83
+ await startStudio(shards[i], i);
84
+ } catch (error) {
85
+ console.error(`Failed to start studio for ${shards[i].id}:`, error);
86
+ }
87
+ }
88
+ console.log("\n" + "=".repeat(60));
89
+ console.log("\u{1F4CB} All Studios Running:");
90
+ console.log("=".repeat(60));
91
+ instances.forEach((instance) => {
92
+ console.log(` \u2022 ${instance.shardId}: http://localhost:${instance.port}`);
93
+ });
94
+ console.log("\n Press Ctrl+C to stop all instances\n");
95
+ };
96
+ var gracefulShutdown = () => {
97
+ console.log("\n\n\u{1F6D1} Shutting down all Prisma Studio instances...\n");
98
+ instances.forEach((instance) => {
99
+ console.log(` Stopping ${instance.shardId}...`);
100
+ instance.process.kill("SIGTERM");
101
+ });
102
+ setTimeout(() => {
103
+ console.log("\n\u2705 All instances stopped\n");
104
+ process.exit(0);
105
+ }, 1e3);
106
+ };
107
+ process.on("SIGINT", gracefulShutdown);
108
+ process.on("SIGTERM", gracefulShutdown);
109
+ startAllStudios().catch((error) => {
110
+ console.error("Failed to start studios:", error);
111
+ process.exit(1);
112
+ });
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // src/cli/test.ts
5
+ var import_config = require("dotenv/config");
6
+ var import_child_process = require("child_process");
7
+ var results = [];
8
+ var log = {
9
+ info: (msg) => console.log(`\u2139\uFE0F ${msg}`),
10
+ success: (msg) => console.log(`\u2705 ${msg}`),
11
+ error: (msg) => console.log(`\u274C ${msg}`),
12
+ warn: (msg) => console.log(`\u26A0\uFE0F ${msg}`),
13
+ section: (msg) => console.log(`
14
+ ${"=".repeat(50)}
15
+ \u{1F4CB} ${msg}
16
+ ${"=".repeat(50)}`)
17
+ };
18
+ var getShardConfigs = () => {
19
+ const shards = [];
20
+ const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
21
+ for (let i = 1; i <= shardCount; i++) {
22
+ const url = process.env[`SHARD_${i}_URL`];
23
+ if (url) {
24
+ shards.push({ id: `shard_${i}`, url });
25
+ }
26
+ }
27
+ if (shards.length === 0 && process.env.DATABASE_URL) {
28
+ shards.push({ id: "shard_1", url: process.env.DATABASE_URL });
29
+ }
30
+ return shards;
31
+ };
32
+ var runTest = async (name, testFn) => {
33
+ const start = Date.now();
34
+ try {
35
+ await testFn();
36
+ const duration = Date.now() - start;
37
+ results.push({ name, passed: true, message: "Passed", duration });
38
+ log.success(`${name} (${duration}ms)`);
39
+ } catch (error) {
40
+ const duration = Date.now() - start;
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ results.push({ name, passed: false, message, duration });
43
+ log.error(`${name}: ${message}`);
44
+ }
45
+ };
46
+ var testConnection = (url) => {
47
+ return new Promise((resolve) => {
48
+ const prisma = (0, import_child_process.spawn)("npx", ["prisma", "db", "execute", "--url", url, "--stdin"], {
49
+ shell: true,
50
+ stdio: ["pipe", "pipe", "pipe"]
51
+ });
52
+ prisma.stdin.write("SELECT 1");
53
+ prisma.stdin.end();
54
+ prisma.on("close", (code) => {
55
+ resolve(code === 0);
56
+ });
57
+ prisma.on("error", () => {
58
+ resolve(false);
59
+ });
60
+ });
61
+ };
62
+ var runTests = async () => {
63
+ const shards = getShardConfigs();
64
+ console.log("\n\u{1F9EA} prisma-sharding: Shard Connection Test Suite\n");
65
+ console.log(`\u{1F4CA} Configuration:`);
66
+ console.log(` - Shard Count: ${shards.length}`);
67
+ console.log(` - Routing Strategy: ${process.env.SHARD_ROUTING_STRATEGY || "modulo"}`);
68
+ if (shards.length === 0) {
69
+ console.error(
70
+ "\n\u274C No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables."
71
+ );
72
+ process.exit(1);
73
+ }
74
+ log.section("Test 1: Shard Connection Tests");
75
+ for (const shard of shards) {
76
+ await runTest(`Connect to ${shard.id}`, async () => {
77
+ const success = await testConnection(shard.url);
78
+ if (!success) {
79
+ throw new Error(`Failed to connect to ${shard.id}`);
80
+ }
81
+ });
82
+ }
83
+ log.section("Test 2: Configuration Verification");
84
+ await runTest("Verify environment variables", async () => {
85
+ const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
86
+ if (shardCount === 0 && !process.env.DATABASE_URL) {
87
+ throw new Error("SHARD_COUNT or DATABASE_URL must be set");
88
+ }
89
+ log.info(`SHARD_COUNT: ${shardCount}`);
90
+ log.info(`Routing Strategy: ${process.env.SHARD_ROUTING_STRATEGY || "modulo"}`);
91
+ });
92
+ await runTest("Verify all shard URLs are present", async () => {
93
+ const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
94
+ for (let i = 1; i <= shardCount; i++) {
95
+ const url = process.env[`SHARD_${i}_URL`];
96
+ if (!url) {
97
+ throw new Error(`SHARD_${i}_URL is missing`);
98
+ }
99
+ }
100
+ });
101
+ log.section("Test Results Summary");
102
+ const passed = results.filter((r) => r.passed).length;
103
+ const failed = results.filter((r) => !r.passed).length;
104
+ const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
105
+ console.log(`
106
+ Total Tests: ${results.length}`);
107
+ console.log(` Passed: ${passed}`);
108
+ console.log(` Failed: ${failed}`);
109
+ console.log(` Duration: ${totalDuration}ms`);
110
+ if (failed > 0) {
111
+ console.log("\n Failed Tests:");
112
+ results.filter((r) => !r.passed).forEach((r) => {
113
+ console.log(` - ${r.name}: ${r.message}`);
114
+ });
115
+ process.exit(1);
116
+ }
117
+ console.log("\n\u2705 All shard tests passed!");
118
+ };
119
+ runTests().catch((error) => {
120
+ console.error("\n\u{1F4A5} Test suite crashed:", error);
121
+ process.exit(1);
122
+ });