prisma-sharding 0.0.1 → 0.0.2
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 +180 -0
- package/dist/index.d.mts +126 -0
- package/dist/index.d.ts +126 -0
- package/dist/index.js +488 -0
- package/dist/index.mjs +452 -0
- package/package.json +52 -4
- package/index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Prisma Sharding
|
|
2
|
+
|
|
3
|
+
Lightweight database sharding library for Prisma with connection pooling, health monitoring, and circuit breaker support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add prisma-sharding
|
|
9
|
+
# or
|
|
10
|
+
npm install prisma-sharding
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { PrismaSharding } from 'prisma-sharding';
|
|
17
|
+
import { PrismaClient } from '@/generated/prisma';
|
|
18
|
+
import { PrismaPg } from '@prisma/adapter-pg';
|
|
19
|
+
|
|
20
|
+
const sharding = new PrismaSharding<PrismaClient>({
|
|
21
|
+
shards: [
|
|
22
|
+
{ id: 'shard_1', url: process.env.SHARD_1_URL! },
|
|
23
|
+
{ id: 'shard_2', url: process.env.SHARD_2_URL! },
|
|
24
|
+
{ id: 'shard_3', url: process.env.SHARD_3_URL! },
|
|
25
|
+
],
|
|
26
|
+
strategy: 'modulo', // 'modulo' | 'consistent-hash'
|
|
27
|
+
createClient: (url) => {
|
|
28
|
+
const adapter = new PrismaPg({ connectionString: url, max: 10 });
|
|
29
|
+
return new PrismaClient({ adapter });
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Initialize connections
|
|
34
|
+
await sharding.connect();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Get Shard by Key
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Get client for existing user (routed by user ID)
|
|
43
|
+
const client = sharding.getShard(userId);
|
|
44
|
+
const user = await client.user.findUnique({ where: { id: userId } });
|
|
45
|
+
|
|
46
|
+
// Get shard with metadata
|
|
47
|
+
const { client, shardId } = sharding.getShardWithInfo(userId);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Random Shard (New Records)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Get random shard for creating new user (ensures even distribution)
|
|
54
|
+
const client = sharding.getRandomShard();
|
|
55
|
+
const newUser = await client.user.create({ data: { email, username } });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Cross-Shard Search
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Find user by email across ALL shards (parallel execution)
|
|
62
|
+
const { result: user, client } = await sharding.findFirst(async (c) =>
|
|
63
|
+
c.user.findFirst({ where: { email } })
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (user && client) {
|
|
67
|
+
// Continue operations on the found shard
|
|
68
|
+
await client.user.update({
|
|
69
|
+
where: { id: user.id },
|
|
70
|
+
data: { lastLogin: new Date() },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Execute on All Shards
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Get counts from all shards
|
|
79
|
+
const counts = await sharding.runOnAll(async (client) => client.user.count());
|
|
80
|
+
const totalUsers = counts.reduce((sum, count) => sum + count, 0);
|
|
81
|
+
|
|
82
|
+
// With detailed results (includes errors)
|
|
83
|
+
const results = await sharding.runOnAllWithDetails(async (client, shardId) => {
|
|
84
|
+
return { shardId, count: await client.user.count() };
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Health Monitoring
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Get health of all shards
|
|
92
|
+
const health = sharding.getHealth();
|
|
93
|
+
// Returns: [{ shardId, isHealthy, latencyMs, lastChecked, ... }]
|
|
94
|
+
|
|
95
|
+
// Get specific shard health
|
|
96
|
+
const shard1Health = sharding.getHealthByShard('shard_1');
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Lifecycle
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Graceful shutdown
|
|
103
|
+
await sharding.disconnect();
|
|
104
|
+
|
|
105
|
+
// Check connection status
|
|
106
|
+
if (sharding.isConnected()) {
|
|
107
|
+
// ...
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
|
|
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 function to create Prisma clients |
|
|
118
|
+
| `healthCheckIntervalMs` | `number` | `30000` | Health check frequency |
|
|
119
|
+
| `circuitBreakerThreshold` | `number` | `3` | Failures before marking unhealthy |
|
|
120
|
+
| `logger` | `ShardingLogger` | Console | Custom logger |
|
|
121
|
+
|
|
122
|
+
### Shard Config
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
interface ShardConfig {
|
|
126
|
+
id: string; // Unique identifier (e.g., 'shard_1')
|
|
127
|
+
url: string; // PostgreSQL connection string
|
|
128
|
+
weight?: number; // Optional weight for distribution
|
|
129
|
+
isReadReplica?: boolean;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Routing Strategies
|
|
134
|
+
|
|
135
|
+
### Modulo (Default)
|
|
136
|
+
|
|
137
|
+
Simple and fast. Uses `hash(key) % shardCount` for routing.
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
strategy: 'modulo';
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Consistent Hash
|
|
144
|
+
|
|
145
|
+
Minimizes data movement when adding/removing shards.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
strategy: 'consistent-hash';
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Error Handling
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { ShardingError, ConfigError, ConnectionError, RoutingError } from 'prisma-sharding';
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const client = sharding.getShard(userId);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof ConnectionError) {
|
|
160
|
+
console.error(`Shard ${error.shardId} unavailable`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Custom Logger
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const sharding = new PrismaSharding({
|
|
169
|
+
// ...config,
|
|
170
|
+
logger: {
|
|
171
|
+
info: (msg) => myLogger.info(msg),
|
|
172
|
+
warn: (msg) => myLogger.warn(msg),
|
|
173
|
+
error: (msg) => myLogger.error(msg),
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
interface ShardConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
url: string;
|
|
4
|
+
weight?: number;
|
|
5
|
+
isReadReplica?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface PoolConfig {
|
|
8
|
+
maxConnections?: number;
|
|
9
|
+
idleTimeoutMs?: number;
|
|
10
|
+
connectionTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
interface ShardHealth {
|
|
13
|
+
shardId: string;
|
|
14
|
+
isHealthy: boolean;
|
|
15
|
+
latencyMs: number;
|
|
16
|
+
lastChecked: Date;
|
|
17
|
+
errorCount: number;
|
|
18
|
+
consecutiveFailures: number;
|
|
19
|
+
}
|
|
20
|
+
interface ShardInstance<TClient> {
|
|
21
|
+
config: ShardConfig;
|
|
22
|
+
client: TClient;
|
|
23
|
+
health: ShardHealth;
|
|
24
|
+
}
|
|
25
|
+
interface ShardResult<TClient> {
|
|
26
|
+
shardId: string;
|
|
27
|
+
client: TClient;
|
|
28
|
+
}
|
|
29
|
+
interface CrossShardResult<T> {
|
|
30
|
+
shardId: string;
|
|
31
|
+
result: T | null;
|
|
32
|
+
error?: Error;
|
|
33
|
+
}
|
|
34
|
+
interface FindFirstResult<T, TClient> {
|
|
35
|
+
result: T | null;
|
|
36
|
+
shardId: string | null;
|
|
37
|
+
client: TClient | null;
|
|
38
|
+
}
|
|
39
|
+
type RoutingStrategy = 'modulo' | 'consistent-hash';
|
|
40
|
+
interface ShardingConfig<TClient> {
|
|
41
|
+
shards: ShardConfig[];
|
|
42
|
+
strategy?: RoutingStrategy;
|
|
43
|
+
createClient: (url: string, shardId: string) => TClient;
|
|
44
|
+
pool?: PoolConfig;
|
|
45
|
+
healthCheckIntervalMs?: number;
|
|
46
|
+
circuitBreakerThreshold?: number;
|
|
47
|
+
logger?: ShardingLogger;
|
|
48
|
+
}
|
|
49
|
+
interface ShardingLogger {
|
|
50
|
+
info: (message: string) => void;
|
|
51
|
+
warn: (message: string) => void;
|
|
52
|
+
error: (message: string) => void;
|
|
53
|
+
}
|
|
54
|
+
type ClientFactory<TClient> = (url: string, shardId: string) => TClient;
|
|
55
|
+
|
|
56
|
+
declare class PrismaSharding<TClient> {
|
|
57
|
+
private manager;
|
|
58
|
+
private router;
|
|
59
|
+
private config;
|
|
60
|
+
private logger;
|
|
61
|
+
private connected;
|
|
62
|
+
constructor(config: ShardingConfig<TClient>);
|
|
63
|
+
private validateConfig;
|
|
64
|
+
connect(): Promise<void>;
|
|
65
|
+
disconnect(): Promise<void>;
|
|
66
|
+
private ensureConnected;
|
|
67
|
+
getShard(key: string): TClient;
|
|
68
|
+
getShardById(shardId: string): TClient;
|
|
69
|
+
getShardWithInfo(key: string): ShardResult<TClient>;
|
|
70
|
+
getRandomShard(): TClient;
|
|
71
|
+
getRandomShardWithInfo(): ShardResult<TClient>;
|
|
72
|
+
findFirst<T>(finder: (client: TClient) => Promise<T | null>): Promise<FindFirstResult<T, TClient>>;
|
|
73
|
+
runOnAll<T>(operation: (client: TClient, shardId: string) => Promise<T>): Promise<T[]>;
|
|
74
|
+
runOnAllWithDetails<T>(operation: (client: TClient, shardId: string) => Promise<T>): Promise<CrossShardResult<T>[]>;
|
|
75
|
+
getHealth(): ShardHealth[];
|
|
76
|
+
getHealthByShard(shardId: string): ShardHealth | undefined;
|
|
77
|
+
getAllClients(): TClient[];
|
|
78
|
+
getHealthyClients(): TClient[];
|
|
79
|
+
getShardCount(): number;
|
|
80
|
+
getShardIds(): string[];
|
|
81
|
+
isConnected(): boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
declare class ShardingError extends Error {
|
|
85
|
+
readonly code: string;
|
|
86
|
+
constructor(message: string, code?: string);
|
|
87
|
+
}
|
|
88
|
+
declare class ConfigError extends ShardingError {
|
|
89
|
+
constructor(message: string);
|
|
90
|
+
}
|
|
91
|
+
declare class ConnectionError extends ShardingError {
|
|
92
|
+
readonly shardId: string;
|
|
93
|
+
constructor(message: string, shardId: string);
|
|
94
|
+
}
|
|
95
|
+
declare class RoutingError extends ShardingError {
|
|
96
|
+
constructor(message: string);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const DEFAULTS: {
|
|
100
|
+
readonly POOL_MAX_CONNECTIONS: 10;
|
|
101
|
+
readonly POOL_IDLE_TIMEOUT_MS: 10000;
|
|
102
|
+
readonly POOL_CONNECTION_TIMEOUT_MS: 5000;
|
|
103
|
+
readonly HEALTH_CHECK_INTERVAL_MS: 30000;
|
|
104
|
+
readonly CIRCUIT_BREAKER_THRESHOLD: 3;
|
|
105
|
+
readonly CONSISTENT_HASH_VIRTUAL_NODES: 150;
|
|
106
|
+
};
|
|
107
|
+
declare const ERROR_MESSAGES: {
|
|
108
|
+
readonly NO_SHARDS: "At least one shard must be configured";
|
|
109
|
+
readonly SHARD_NOT_FOUND: (id: string) => string;
|
|
110
|
+
readonly NO_HEALTHY_SHARDS: "No healthy shards available";
|
|
111
|
+
readonly INVALID_STRATEGY: (s: string) => string;
|
|
112
|
+
readonly NOT_CONNECTED: "Sharding not connected. Call connect() first";
|
|
113
|
+
readonly ALREADY_CONNECTED: "Sharding already connected";
|
|
114
|
+
readonly MISSING_CLIENT_FACTORY: "createClient function is required";
|
|
115
|
+
readonly INVALID_SHARD_URL: (id: string) => string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
declare function hashString(str: string): number;
|
|
119
|
+
declare function validateUrl(url: string): boolean;
|
|
120
|
+
declare function createDefaultLogger(): {
|
|
121
|
+
info: (msg: string) => void;
|
|
122
|
+
warn: (msg: string) => void;
|
|
123
|
+
error: (msg: string) => void;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export { type ClientFactory, ConfigError, ConnectionError, type CrossShardResult, DEFAULTS, ERROR_MESSAGES, type FindFirstResult, type PoolConfig, PrismaSharding, RoutingError, type RoutingStrategy, type ShardConfig, type ShardHealth, type ShardInstance, type ShardResult, type ShardingConfig, ShardingError, type ShardingLogger, createDefaultLogger, hashString, validateUrl };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
interface ShardConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
url: string;
|
|
4
|
+
weight?: number;
|
|
5
|
+
isReadReplica?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface PoolConfig {
|
|
8
|
+
maxConnections?: number;
|
|
9
|
+
idleTimeoutMs?: number;
|
|
10
|
+
connectionTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
interface ShardHealth {
|
|
13
|
+
shardId: string;
|
|
14
|
+
isHealthy: boolean;
|
|
15
|
+
latencyMs: number;
|
|
16
|
+
lastChecked: Date;
|
|
17
|
+
errorCount: number;
|
|
18
|
+
consecutiveFailures: number;
|
|
19
|
+
}
|
|
20
|
+
interface ShardInstance<TClient> {
|
|
21
|
+
config: ShardConfig;
|
|
22
|
+
client: TClient;
|
|
23
|
+
health: ShardHealth;
|
|
24
|
+
}
|
|
25
|
+
interface ShardResult<TClient> {
|
|
26
|
+
shardId: string;
|
|
27
|
+
client: TClient;
|
|
28
|
+
}
|
|
29
|
+
interface CrossShardResult<T> {
|
|
30
|
+
shardId: string;
|
|
31
|
+
result: T | null;
|
|
32
|
+
error?: Error;
|
|
33
|
+
}
|
|
34
|
+
interface FindFirstResult<T, TClient> {
|
|
35
|
+
result: T | null;
|
|
36
|
+
shardId: string | null;
|
|
37
|
+
client: TClient | null;
|
|
38
|
+
}
|
|
39
|
+
type RoutingStrategy = 'modulo' | 'consistent-hash';
|
|
40
|
+
interface ShardingConfig<TClient> {
|
|
41
|
+
shards: ShardConfig[];
|
|
42
|
+
strategy?: RoutingStrategy;
|
|
43
|
+
createClient: (url: string, shardId: string) => TClient;
|
|
44
|
+
pool?: PoolConfig;
|
|
45
|
+
healthCheckIntervalMs?: number;
|
|
46
|
+
circuitBreakerThreshold?: number;
|
|
47
|
+
logger?: ShardingLogger;
|
|
48
|
+
}
|
|
49
|
+
interface ShardingLogger {
|
|
50
|
+
info: (message: string) => void;
|
|
51
|
+
warn: (message: string) => void;
|
|
52
|
+
error: (message: string) => void;
|
|
53
|
+
}
|
|
54
|
+
type ClientFactory<TClient> = (url: string, shardId: string) => TClient;
|
|
55
|
+
|
|
56
|
+
declare class PrismaSharding<TClient> {
|
|
57
|
+
private manager;
|
|
58
|
+
private router;
|
|
59
|
+
private config;
|
|
60
|
+
private logger;
|
|
61
|
+
private connected;
|
|
62
|
+
constructor(config: ShardingConfig<TClient>);
|
|
63
|
+
private validateConfig;
|
|
64
|
+
connect(): Promise<void>;
|
|
65
|
+
disconnect(): Promise<void>;
|
|
66
|
+
private ensureConnected;
|
|
67
|
+
getShard(key: string): TClient;
|
|
68
|
+
getShardById(shardId: string): TClient;
|
|
69
|
+
getShardWithInfo(key: string): ShardResult<TClient>;
|
|
70
|
+
getRandomShard(): TClient;
|
|
71
|
+
getRandomShardWithInfo(): ShardResult<TClient>;
|
|
72
|
+
findFirst<T>(finder: (client: TClient) => Promise<T | null>): Promise<FindFirstResult<T, TClient>>;
|
|
73
|
+
runOnAll<T>(operation: (client: TClient, shardId: string) => Promise<T>): Promise<T[]>;
|
|
74
|
+
runOnAllWithDetails<T>(operation: (client: TClient, shardId: string) => Promise<T>): Promise<CrossShardResult<T>[]>;
|
|
75
|
+
getHealth(): ShardHealth[];
|
|
76
|
+
getHealthByShard(shardId: string): ShardHealth | undefined;
|
|
77
|
+
getAllClients(): TClient[];
|
|
78
|
+
getHealthyClients(): TClient[];
|
|
79
|
+
getShardCount(): number;
|
|
80
|
+
getShardIds(): string[];
|
|
81
|
+
isConnected(): boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
declare class ShardingError extends Error {
|
|
85
|
+
readonly code: string;
|
|
86
|
+
constructor(message: string, code?: string);
|
|
87
|
+
}
|
|
88
|
+
declare class ConfigError extends ShardingError {
|
|
89
|
+
constructor(message: string);
|
|
90
|
+
}
|
|
91
|
+
declare class ConnectionError extends ShardingError {
|
|
92
|
+
readonly shardId: string;
|
|
93
|
+
constructor(message: string, shardId: string);
|
|
94
|
+
}
|
|
95
|
+
declare class RoutingError extends ShardingError {
|
|
96
|
+
constructor(message: string);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const DEFAULTS: {
|
|
100
|
+
readonly POOL_MAX_CONNECTIONS: 10;
|
|
101
|
+
readonly POOL_IDLE_TIMEOUT_MS: 10000;
|
|
102
|
+
readonly POOL_CONNECTION_TIMEOUT_MS: 5000;
|
|
103
|
+
readonly HEALTH_CHECK_INTERVAL_MS: 30000;
|
|
104
|
+
readonly CIRCUIT_BREAKER_THRESHOLD: 3;
|
|
105
|
+
readonly CONSISTENT_HASH_VIRTUAL_NODES: 150;
|
|
106
|
+
};
|
|
107
|
+
declare const ERROR_MESSAGES: {
|
|
108
|
+
readonly NO_SHARDS: "At least one shard must be configured";
|
|
109
|
+
readonly SHARD_NOT_FOUND: (id: string) => string;
|
|
110
|
+
readonly NO_HEALTHY_SHARDS: "No healthy shards available";
|
|
111
|
+
readonly INVALID_STRATEGY: (s: string) => string;
|
|
112
|
+
readonly NOT_CONNECTED: "Sharding not connected. Call connect() first";
|
|
113
|
+
readonly ALREADY_CONNECTED: "Sharding already connected";
|
|
114
|
+
readonly MISSING_CLIENT_FACTORY: "createClient function is required";
|
|
115
|
+
readonly INVALID_SHARD_URL: (id: string) => string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
declare function hashString(str: string): number;
|
|
119
|
+
declare function validateUrl(url: string): boolean;
|
|
120
|
+
declare function createDefaultLogger(): {
|
|
121
|
+
info: (msg: string) => void;
|
|
122
|
+
warn: (msg: string) => void;
|
|
123
|
+
error: (msg: string) => void;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export { type ClientFactory, ConfigError, ConnectionError, type CrossShardResult, DEFAULTS, ERROR_MESSAGES, type FindFirstResult, type PoolConfig, PrismaSharding, RoutingError, type RoutingStrategy, type ShardConfig, type ShardHealth, type ShardInstance, type ShardResult, type ShardingConfig, ShardingError, type ShardingLogger, createDefaultLogger, hashString, validateUrl };
|