prisma-sharding 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -27
- package/dist/cli/migrate.js +117 -0
- package/dist/cli/studio.js +112 -0
- package/dist/cli/test.js +401 -0
- package/dist/index.js +101 -101
- package/dist/index.mjs +101 -101
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prisma Sharding
|
|
2
2
|
|
|
3
|
-
Lightweight database sharding library for Prisma with connection pooling, health monitoring, and
|
|
3
|
+
Lightweight database sharding library for Prisma with connection pooling, health monitoring, and CLI tools.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,9 +10,13 @@ yarn add prisma-sharding
|
|
|
10
10
|
npm install prisma-sharding
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
> Don't forget to follow me on [GitHub](https://github.com/safdar-azeem)!
|
|
14
|
+
|
|
15
|
+
## Step 1: Create Sharding Connection
|
|
14
16
|
|
|
15
17
|
```typescript
|
|
18
|
+
// src/config/prisma.ts
|
|
19
|
+
|
|
16
20
|
import { PrismaSharding } from 'prisma-sharding';
|
|
17
21
|
import { PrismaClient } from '@/generated/prisma';
|
|
18
22
|
import { PrismaPg } from '@prisma/adapter-pg';
|
|
@@ -21,7 +25,6 @@ const sharding = new PrismaSharding<PrismaClient>({
|
|
|
21
25
|
shards: [
|
|
22
26
|
{ id: 'shard_1', url: process.env.SHARD_1_URL! },
|
|
23
27
|
{ id: 'shard_2', url: process.env.SHARD_2_URL! },
|
|
24
|
-
{ id: 'shard_3', url: process.env.SHARD_3_URL! },
|
|
25
28
|
],
|
|
26
29
|
strategy: 'modulo', // 'modulo' | 'consistent-hash'
|
|
27
30
|
createClient: (url) => {
|
|
@@ -30,32 +33,78 @@ const sharding = new PrismaSharding<PrismaClient>({
|
|
|
30
33
|
},
|
|
31
34
|
});
|
|
32
35
|
|
|
33
|
-
// Initialize connections
|
|
34
36
|
await sharding.connect();
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
##
|
|
39
|
+
## API
|
|
38
40
|
|
|
39
|
-
|
|
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';
|
|
40
74
|
|
|
41
|
-
```typescript
|
|
42
|
-
// Get client for existing user (routed by user ID)
|
|
43
75
|
const client = sharding.getShard(userId);
|
|
44
|
-
const user = await client.user.findUnique({ where: { id: userId } });
|
|
45
76
|
|
|
46
|
-
|
|
47
|
-
|
|
77
|
+
const user = await client.user.findUnique({
|
|
78
|
+
where: { id: userId },
|
|
79
|
+
});
|
|
48
80
|
```
|
|
49
81
|
|
|
50
|
-
|
|
82
|
+
`Important rule:`
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
56
100
|
```
|
|
57
101
|
|
|
58
|
-
|
|
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.
|
|
59
108
|
|
|
60
109
|
```typescript
|
|
61
110
|
// Find user by email across ALL shards (parallel execution)
|
|
@@ -72,7 +121,7 @@ if (user && client) {
|
|
|
72
121
|
}
|
|
73
122
|
```
|
|
74
123
|
|
|
75
|
-
|
|
124
|
+
## Step 5: Run on All Shards (Admin or Analytics)
|
|
76
125
|
|
|
77
126
|
```typescript
|
|
78
127
|
// Get counts from all shards
|
|
@@ -108,16 +157,117 @@ if (sharding.isConnected()) {
|
|
|
108
157
|
}
|
|
109
158
|
```
|
|
110
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
|
+
```
|
|
217
|
+
================================
|
|
218
|
+
📋 User Distribution Test
|
|
219
|
+
================================
|
|
220
|
+
Creating 24 test users across 3 shards...
|
|
221
|
+
|
|
222
|
+
User 1/24: "testuser_0" → shard_3
|
|
223
|
+
User 2/24: "testuser_1" → shard_1
|
|
224
|
+
User 3/24: "testuser_2" → shard_2
|
|
225
|
+
User 4/24: "testuser_3" → shard_3
|
|
226
|
+
User 5/24: "testuser_4" → shard_1
|
|
227
|
+
User 6/24: "testuser_5" → shard_2
|
|
228
|
+
User 7/24: "testuser_6" → shard_3
|
|
229
|
+
User 8/24: "testuser_7" → shard_1
|
|
230
|
+
User 9/24: "testuser_8" → shard_2
|
|
231
|
+
User 10/24: "testuser_9" → shard_3
|
|
232
|
+
User 11/24: "testuser_10" → shard_2
|
|
233
|
+
User 12/24: "testuser_11" → shard_1
|
|
234
|
+
User 13/24: "testuser_12" → shard_3
|
|
235
|
+
User 14/24: "testuser_13" → shard_2
|
|
236
|
+
User 15/24: "testuser_14" → shard_1
|
|
237
|
+
User 16/24: "testuser_15" → shard_3
|
|
238
|
+
User 17/24: "testuser_16" → shard_2
|
|
239
|
+
User 18/24: "testuser_17" → shard_1
|
|
240
|
+
User 19/24: "testuser_18" → shard_3
|
|
241
|
+
User 20/24: "testuser_19" → shard_2
|
|
242
|
+
User 21/24: "testuser_20" → shard_1
|
|
243
|
+
User 22/24: "testuser_21" → shard_3
|
|
244
|
+
User 23/24: "testuser_22" → shard_2
|
|
245
|
+
User 24/24: "testuser_23" → shard_1
|
|
246
|
+
✅ Created 24/24 test users
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
================================
|
|
251
|
+
📋 Read Verification
|
|
252
|
+
================================
|
|
253
|
+
✓ User "test_user_1770289330292_0" found on shard_3
|
|
254
|
+
✓ User "test_user_1770289330292_1" found on shard_1
|
|
255
|
+
✓ User "test_user_1770289330292_2" found on shard_2
|
|
256
|
+
✓ User "test_user_1770289330292_3" found on shard_3
|
|
257
|
+
✓ User "test_user_1770289330292_4" found on shard_1
|
|
258
|
+
Verified 5/5 users on correct shards
|
|
259
|
+
✅ Verify users exist on correct shards (136ms)
|
|
260
|
+
```
|
|
261
|
+
|
|
111
262
|
## Configuration
|
|
112
263
|
|
|
113
|
-
| Option | Type | Default | Description
|
|
114
|
-
| ------------------------- | ------------------------------- | ---------- |
|
|
115
|
-
| `shards` | `ShardConfig[]` | Required | Array of shard configurations
|
|
116
|
-
| `strategy` | `'modulo' \| 'consistent-hash'` | `'modulo'` | Routing algorithm
|
|
117
|
-
| `createClient` | `(url, shardId) => TClient` | Required | Factory
|
|
118
|
-
| `healthCheckIntervalMs` | `number` | `30000` | Health check frequency
|
|
119
|
-
| `circuitBreakerThreshold` | `number` | `3` | Failures before marking unhealthy
|
|
120
|
-
| `logger` | `ShardingLogger` | Console | Custom logger |
|
|
264
|
+
| Option | Type | Default | Description |
|
|
265
|
+
| ------------------------- | ------------------------------- | ---------- | --------------------------------- |
|
|
266
|
+
| `shards` | `ShardConfig[]` | Required | Array of shard configurations |
|
|
267
|
+
| `strategy` | `'modulo' \| 'consistent-hash'` | `'modulo'` | Routing algorithm |
|
|
268
|
+
| `createClient` | `(url, shardId) => TClient` | Required | Factory to create Prisma clients |
|
|
269
|
+
| `healthCheckIntervalMs` | `number` | `30000` | Health check frequency |
|
|
270
|
+
| `circuitBreakerThreshold` | `number` | `3` | Failures before marking unhealthy |
|
|
121
271
|
|
|
122
272
|
### Shard Config
|
|
123
273
|
|
|
@@ -151,7 +301,7 @@ strategy: 'consistent-hash';
|
|
|
151
301
|
## Error Handling
|
|
152
302
|
|
|
153
303
|
```typescript
|
|
154
|
-
import { ShardingError, ConfigError, ConnectionError
|
|
304
|
+
import { ShardingError, ConfigError, ConnectionError } from 'prisma-sharding';
|
|
155
305
|
|
|
156
306
|
try {
|
|
157
307
|
const client = sharding.getShard(userId);
|
|
@@ -175,6 +325,52 @@ const sharding = new PrismaSharding({
|
|
|
175
325
|
});
|
|
176
326
|
```
|
|
177
327
|
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### `getAllClients()`
|
|
331
|
+
|
|
332
|
+
Get all Prisma client instances.
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const clients = sharding.getAllClients();
|
|
336
|
+
|
|
337
|
+
console.log(`Managing ${clients.length} shard clients`);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Returns:** `PrismaClient[]`
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### `getShardCount()`
|
|
345
|
+
|
|
346
|
+
Get total number of configured shards.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
const count = sharding.getShardCount();
|
|
350
|
+
console.log(`Running on ${count} shards`);
|
|
351
|
+
// Output: Running on 3 shards
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### `getShardIds()`
|
|
357
|
+
|
|
358
|
+
Get array of all shard IDs.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const shardIds = sharding.getShardIds();
|
|
362
|
+
console.log(shardIds);
|
|
363
|
+
// Output: ['shard_1', 'shard_2', 'shard_3']
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Returns:** `string[]`
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Author
|
|
371
|
+
|
|
372
|
+
[safdar-azeem](https://github.com/safdar-azeem)
|
|
373
|
+
|
|
178
374
|
## License
|
|
179
375
|
|
|
180
376
|
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
|
+
});
|