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 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
@@ -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 };
@@ -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 };