prisma-sharding 0.0.5 → 0.0.6

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/cli/test.js CHANGED
@@ -25,6 +25,31 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/cli/test.ts
27
27
  var import_config = require("dotenv/config");
28
+
29
+ // src/cli/utils/shards.ts
30
+ var NO_SHARDS_CONFIGURED_MESSAGE = "No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables.";
31
+ var getShardConfigResult = (env = process.env) => {
32
+ const shards = [];
33
+ const missingShardIds = [];
34
+ const shardCount = parseInt(env.SHARD_COUNT || "0", 10);
35
+ for (let i = 1; i <= shardCount; i++) {
36
+ const url = env[`SHARD_${i}_URL`];
37
+ if (url) {
38
+ shards.push({ id: `shard_${i}`, index: i - 1, url });
39
+ } else {
40
+ missingShardIds.push(`shard_${i}`);
41
+ }
42
+ }
43
+ if (shards.length === 0 && env.DATABASE_URL) {
44
+ shards.push({ id: "shard_1", index: 0, url: env.DATABASE_URL });
45
+ }
46
+ return { shards, missingShardIds };
47
+ };
48
+ var getShardConfigs = (env = process.env) => {
49
+ return getShardConfigResult(env).shards;
50
+ };
51
+
52
+ // src/cli/test.ts
28
53
  var results = [];
29
54
  var testUsers = [];
30
55
  var TEST_USER_COUNT = 24;
@@ -39,20 +64,6 @@ ${"=".repeat(60)}
39
64
  ${"=".repeat(60)}`),
40
65
  detail: (msg) => console.log(` ${msg}`)
41
66
  };
42
- var getShardConfigs = () => {
43
- const shards = [];
44
- const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
45
- for (let i = 1; i <= shardCount; i++) {
46
- const url = process.env[`SHARD_${i}_URL`];
47
- if (url) {
48
- shards.push({ id: `shard_${i}`, url });
49
- }
50
- }
51
- if (shards.length === 0 && process.env.DATABASE_URL) {
52
- shards.push({ id: "shard_1", url: process.env.DATABASE_URL });
53
- }
54
- return shards;
55
- };
56
67
  var runTest = async (name, testFn) => {
57
68
  const start = Date.now();
58
69
  try {
@@ -139,11 +150,7 @@ var executeSqlWithPsql = (url, sql) => {
139
150
  shell: true,
140
151
  stdio: ["pipe", "pipe", "pipe"]
141
152
  });
142
- let stdout = "";
143
153
  let stderr = "";
144
- psql.stdout?.on("data", (data) => {
145
- stdout += data.toString();
146
- });
147
154
  psql.stderr?.on("data", (data) => {
148
155
  stderr += data.toString();
149
156
  });
@@ -188,9 +195,8 @@ var runTests = async () => {
188
195
  console.log(` \u2022 Test Users: ${TEST_USER_COUNT}`);
189
196
  console.log(` \u2022 Test Table: ${testTableName}`);
190
197
  if (shards.length === 0) {
191
- console.error(
192
- "\n\u274C No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables."
193
- );
198
+ console.error(`
199
+ \u274C ${NO_SHARDS_CONFIGURED_MESSAGE}`);
194
200
  process.exit(1);
195
201
  }
196
202
  const pgAvailable = await getPgClient();
@@ -25,109 +25,225 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/cli/update.ts
27
27
  var import_config = require("dotenv/config");
28
+
29
+ // src/utils/env.ts
30
+ var parseBooleanEnv = (name, defaultValue, env = process.env) => {
31
+ const value = env[name]?.trim();
32
+ if (value === void 0 || value === "") {
33
+ return defaultValue;
34
+ }
35
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
36
+ };
37
+ var isVerboseEnv = (names, env = process.env) => names.some((name) => parseBooleanEnv(name, false, env));
38
+
39
+ // src/cli/utils/command.ts
28
40
  var import_child_process = require("child_process");
29
41
  var import_path = __toESM(require("path"));
30
- var getShardConfigs = () => {
42
+ var getNpxCommand = () => process.platform === "win32" ? "npx.cmd" : "npx";
43
+ var runCommand = (command, args, options = {}) => {
44
+ return new Promise((resolve) => {
45
+ let stdout = "";
46
+ let stderr = "";
47
+ let settled = false;
48
+ const child = (0, import_child_process.spawn)(command, args, {
49
+ env: options.env || process.env,
50
+ cwd: import_path.default.resolve(options.cwd || process.cwd()),
51
+ shell: false,
52
+ stdio: ["ignore", "pipe", "pipe"]
53
+ });
54
+ const settle = (result) => {
55
+ if (!settled) {
56
+ settled = true;
57
+ resolve(result);
58
+ }
59
+ };
60
+ child.stdout?.on("data", (data) => {
61
+ const output = data.toString();
62
+ stdout += output;
63
+ if (options.verbose) {
64
+ process.stdout.write(output);
65
+ }
66
+ });
67
+ child.stderr?.on("data", (data) => {
68
+ const output = data.toString();
69
+ stderr += output;
70
+ if (options.verbose) {
71
+ process.stderr.write(output);
72
+ }
73
+ });
74
+ child.once("error", (error) => {
75
+ settle({
76
+ success: false,
77
+ stdout,
78
+ stderr,
79
+ error: error.message
80
+ });
81
+ });
82
+ child.once("close", (exitCode) => {
83
+ settle({
84
+ success: exitCode === 0,
85
+ stdout,
86
+ stderr,
87
+ exitCode,
88
+ error: exitCode === 0 ? void 0 : stderr.trim() || stdout.trim() || `Command exited with code ${exitCode}`
89
+ });
90
+ });
91
+ });
92
+ };
93
+ var runPrismaCommand = (args, options = {}) => runCommand(getNpxCommand(), ["prisma", ...args], options);
94
+
95
+ // src/cli/utils/output.ts
96
+ var import_readline = __toESM(require("readline"));
97
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
98
+ var printCliHeader = (icon, title) => {
99
+ console.log(`${icon} ${title}
100
+ `);
101
+ };
102
+ var printCliRow = (icon, label, message) => {
103
+ console.log(`${icon} ${label} ${message}`);
104
+ };
105
+ var printVerboseHint = () => {
106
+ console.log("\nRun with SHARD_CLI_VERBOSE=true for details.");
107
+ };
108
+ var createCliLoader = (label, message, enabled = true) => {
109
+ const animated = enabled && Boolean(process.stdout.isTTY);
110
+ let frameIndex = 0;
111
+ let timer;
112
+ let finished = false;
113
+ const clear = () => {
114
+ if (animated) {
115
+ import_readline.default.clearLine(process.stdout, 0);
116
+ import_readline.default.cursorTo(process.stdout, 0);
117
+ }
118
+ };
119
+ const render = () => {
120
+ clear();
121
+ process.stdout.write(`${SPINNER_FRAMES[frameIndex]} ${label} ${message}`);
122
+ frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
123
+ };
124
+ if (animated) {
125
+ render();
126
+ timer = setInterval(render, 80);
127
+ }
128
+ const finish = (icon, finalMessage) => {
129
+ if (finished) {
130
+ return;
131
+ }
132
+ finished = true;
133
+ if (timer) {
134
+ clearInterval(timer);
135
+ }
136
+ clear();
137
+ printCliRow(icon, label, finalMessage);
138
+ };
139
+ return {
140
+ succeed: (finalMessage) => finish("\u2705", finalMessage),
141
+ fail: (finalMessage) => finish("\u274C", finalMessage)
142
+ };
143
+ };
144
+
145
+ // src/cli/utils/shards.ts
146
+ var NO_SHARDS_CONFIGURED_MESSAGE = "No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables.";
147
+ var getShardConfigResult = (env = process.env) => {
31
148
  const shards = [];
32
- const shardCount = parseInt(process.env.SHARD_COUNT || "0", 10);
149
+ const missingShardIds = [];
150
+ const shardCount = parseInt(env.SHARD_COUNT || "0", 10);
33
151
  for (let i = 1; i <= shardCount; i++) {
34
- const url = process.env[`SHARD_${i}_URL`];
152
+ const url = env[`SHARD_${i}_URL`];
35
153
  if (url) {
36
- shards.push({ id: `shard_${i}`, url });
154
+ shards.push({ id: `shard_${i}`, index: i - 1, url });
155
+ } else {
156
+ missingShardIds.push(`shard_${i}`);
37
157
  }
38
158
  }
39
- if (shards.length === 0 && process.env.DATABASE_URL) {
40
- shards.push({ id: "shard_1", url: process.env.DATABASE_URL });
159
+ if (shards.length === 0 && env.DATABASE_URL) {
160
+ shards.push({ id: "shard_1", index: 0, url: env.DATABASE_URL });
41
161
  }
42
- return shards;
162
+ return { shards, missingShardIds };
43
163
  };
44
- var runCommand = (command, args, env = process.env, cwd = process.cwd()) => {
45
- return new Promise((resolve) => {
46
- const proc = (0, import_child_process.spawn)(command, args, {
47
- env,
48
- cwd: import_path.default.resolve(cwd),
49
- shell: true
50
- });
51
- let output = "";
52
- let errorOutput = "";
53
- proc.stdout.on("data", (data) => {
54
- process.stdout.write(data);
55
- output += data.toString();
56
- });
57
- proc.stderr.on("data", (data) => {
58
- process.stderr.write(data);
59
- errorOutput += data.toString();
60
- });
61
- proc.on("close", (code) => {
62
- if (code === 0) {
63
- resolve({ success: true, output });
64
- } else {
65
- resolve({ success: false, output, error: errorOutput || `Exit code: ${code}` });
164
+ var getShardConfigs = (env = process.env) => {
165
+ return getShardConfigResult(env).shards;
166
+ };
167
+ var maskShardUrl = (url) => {
168
+ try {
169
+ const parsed = new URL(url);
170
+ if (parsed.password) {
171
+ parsed.password = "***";
172
+ }
173
+ return parsed.toString();
174
+ } catch {
175
+ return url.replace(/:[^:@]+@/, ":***@");
176
+ }
177
+ };
178
+
179
+ // src/cli/utils/prisma.ts
180
+ var syncShardSchemas = async (shards, extraArgs, verbose) => {
181
+ const results = [];
182
+ for (const shard of shards) {
183
+ const loader = createCliLoader(shard.id, "Syncing", !verbose);
184
+ if (verbose) {
185
+ console.log(`
186
+ Syncing ${shard.id}...`);
187
+ console.log(`Database: ${maskShardUrl(shard.url)}
188
+ `);
189
+ }
190
+ const result = await runPrismaCommand(
191
+ ["db", "push", "--accept-data-loss", ...extraArgs],
192
+ {
193
+ env: { ...process.env, DATABASE_URL: shard.url },
194
+ verbose
66
195
  }
67
- });
68
- proc.on("error", (err) => {
69
- resolve({ success: false, output, error: err.message });
70
- });
71
- });
196
+ );
197
+ if (verbose && result.error && !result.stderr.trim() && !result.stdout.trim()) {
198
+ console.error(result.error);
199
+ }
200
+ results.push({ shardId: shard.id, success: result.success });
201
+ if (result.success) {
202
+ loader.succeed("Synced");
203
+ } else {
204
+ loader.fail("Failed");
205
+ }
206
+ }
207
+ return results;
72
208
  };
209
+
210
+ // src/cli/update.ts
73
211
  var updateAll = async () => {
74
- console.log("\n" + "=".repeat(60));
75
- console.log("\u{1F504} PRISMA SHARDING UPDATE");
76
- console.log("=".repeat(60) + "\n");
77
- console.log("\u{1F6E0}\uFE0F Step 1: Generating Prisma Client Types...");
78
- const genResult = await runCommand("npx", ["prisma", "generate"]);
79
- if (!genResult.success) {
80
- console.error("\n\u274C Failed to generate client. Aborting migration.");
81
- process.exit(1);
82
- }
83
- console.log("\u2705 Client types generated successfully.\n");
212
+ const verbose = isVerboseEnv(["SHARD_UPDATE_VERBOSE", "SHARD_CLI_VERBOSE"]);
84
213
  const shards = getShardConfigs();
85
214
  const extraArgs = process.argv.slice(2);
86
- console.log("\u{1F4E6} Step 2: Migrating Shards...");
87
- console.log(`\u{1F4CA} Total shards to migrate: ${shards.length}`);
88
- if (extraArgs.length > 0) {
89
- console.log(`\u{1F527} Using flags: ${extraArgs.join(" ")}`);
90
- }
91
- console.log("");
215
+ printCliHeader("\u{1F504}", "Prisma Sharding Update");
92
216
  if (shards.length === 0) {
93
- console.error(
94
- "\u274C No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables."
95
- );
217
+ printCliRow("\u274C", "config", NO_SHARDS_CONFIGURED_MESSAGE);
96
218
  process.exit(1);
97
219
  }
98
- const results = [];
99
- for (const shard of shards) {
100
- console.log(`
101
- \u{1F449} Processing ${shard.id}...`);
102
- const env = { ...process.env, DATABASE_URL: shard.url };
103
- const args = ["prisma", "db", "push", "--accept-data-loss", ...extraArgs];
104
- const { success, output, error } = await runCommand("npx", args, env);
105
- if (!success) {
106
- console.error(` \u274C Failed`);
107
- results.push({ shardId: shard.id, success: false, output, error });
108
- } else {
109
- console.log(` \u2705 Success`);
110
- results.push({ shardId: shard.id, success: true, output });
220
+ if (verbose) {
221
+ console.log("Generating Prisma Client...\n");
222
+ }
223
+ const generateLoader = createCliLoader("client", "Generating", !verbose);
224
+ const generateResult = await runPrismaCommand(["generate"], { verbose });
225
+ if (verbose && generateResult.error && !generateResult.stderr.trim() && !generateResult.stdout.trim()) {
226
+ console.error(generateResult.error);
227
+ }
228
+ if (!generateResult.success) {
229
+ generateLoader.fail("Generation failed");
230
+ if (!verbose) {
231
+ printVerboseHint();
111
232
  }
233
+ process.exit(1);
112
234
  }
113
- console.log("\n" + "=".repeat(60));
114
- console.log("\u{1F4CB} Update Summary");
115
- console.log("=".repeat(60));
116
- const successful = results.filter((r) => r.success).length;
117
- const failed = results.filter((r) => !r.success).length;
118
- results.forEach((result) => {
119
- const status = result.success ? "\u2705" : "\u274C";
120
- console.log(` ${status} ${result.shardId}`);
121
- });
122
- console.log(`
123
- Total: ${results.length} | Success: ${successful} | Failed: ${failed}`);
235
+ generateLoader.succeed("Generated");
236
+ const results = await syncShardSchemas(shards, extraArgs, verbose);
237
+ const successful = results.filter((result) => result.success).length;
238
+ const failed = results.length - successful;
124
239
  if (failed > 0) {
125
- console.log("\n\u26A0\uFE0F Some migrations failed. Run with --force-reset if needed.");
240
+ if (!verbose) {
241
+ printVerboseHint();
242
+ }
126
243
  process.exit(1);
127
244
  }
128
- console.log("\n\u2728 All updates completed successfully!");
129
245
  };
130
246
  updateAll().catch((error) => {
131
- console.error("Update script failed:", error);
247
+ console.error(error instanceof Error ? error.message : String(error));
132
248
  process.exit(1);
133
249
  });
package/dist/index.d.mts CHANGED
@@ -68,6 +68,10 @@ declare class PrismaSharding<TClient> {
68
68
  getShardById(shardId: string): TClient;
69
69
  getShardWithInfo(key: string): ShardResult<TClient>;
70
70
  getRandomShard(): TClient;
71
+ /**
72
+ * Returns both the random shard client and its shardId.
73
+ * Used when the shardId must be stored on the user record for future routing.
74
+ */
71
75
  getRandomShardWithInfo(): ShardResult<TClient>;
72
76
  findFirst<T>(finder: (client: TClient) => Promise<T | null>): Promise<FindFirstResult<T, TClient>>;
73
77
  runOnAll<T>(operation: (client: TClient, shardId: string) => Promise<T>): Promise<T[]>;
package/dist/index.d.ts CHANGED
@@ -68,6 +68,10 @@ declare class PrismaSharding<TClient> {
68
68
  getShardById(shardId: string): TClient;
69
69
  getShardWithInfo(key: string): ShardResult<TClient>;
70
70
  getRandomShard(): TClient;
71
+ /**
72
+ * Returns both the random shard client and its shardId.
73
+ * Used when the shardId must be stored on the user record for future routing.
74
+ */
71
75
  getRandomShardWithInfo(): ShardResult<TClient>;
72
76
  findFirst<T>(finder: (client: TClient) => Promise<T | null>): Promise<FindFirstResult<T, TClient>>;
73
77
  runOnAll<T>(operation: (client: TClient, shardId: string) => Promise<T>): Promise<T[]>;
package/dist/index.js CHANGED
@@ -33,6 +33,15 @@ __export(index_exports, {
33
33
  });
34
34
  module.exports = __toCommonJS(index_exports);
35
35
 
36
+ // src/utils/env.ts
37
+ var parseBooleanEnv = (name, defaultValue, env = process.env) => {
38
+ const value = env[name]?.trim();
39
+ if (value === void 0 || value === "") {
40
+ return defaultValue;
41
+ }
42
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
43
+ };
44
+
36
45
  // src/utils/index.ts
37
46
  function hashString(str) {
38
47
  let hash = 0;
@@ -47,8 +56,13 @@ function validateUrl(url) {
47
56
  return url.startsWith("postgresql://") || url.startsWith("postgres://");
48
57
  }
49
58
  function createDefaultLogger() {
59
+ const verbose = parseBooleanEnv("PRISMA_SHARDING_VERBOSE", false);
50
60
  return {
51
- info: (msg) => console.log(`[PrismaSharding] ${msg}`),
61
+ info: (msg) => {
62
+ if (verbose) {
63
+ console.log(`[PrismaSharding] ${msg}`);
64
+ }
65
+ },
52
66
  warn: (msg) => console.warn(`[PrismaSharding] ${msg}`),
53
67
  error: (msg) => console.error(`[PrismaSharding] ${msg}`)
54
68
  };
@@ -416,6 +430,10 @@ var PrismaSharding = class {
416
430
  const shardId = this.router.getRandomShardId();
417
431
  return this.manager.getClient(shardId);
418
432
  }
433
+ /**
434
+ * Returns both the random shard client and its shardId.
435
+ * Used when the shardId must be stored on the user record for future routing.
436
+ */
419
437
  getRandomShardWithInfo() {
420
438
  this.ensureConnected();
421
439
  const shardId = this.router.getRandomShardId();
package/dist/index.mjs CHANGED
@@ -1,3 +1,12 @@
1
+ // src/utils/env.ts
2
+ var parseBooleanEnv = (name, defaultValue, env = process.env) => {
3
+ const value = env[name]?.trim();
4
+ if (value === void 0 || value === "") {
5
+ return defaultValue;
6
+ }
7
+ return ["1", "true", "yes", "on"].includes(value.toLowerCase());
8
+ };
9
+
1
10
  // src/utils/index.ts
2
11
  function hashString(str) {
3
12
  let hash = 0;
@@ -12,8 +21,13 @@ function validateUrl(url) {
12
21
  return url.startsWith("postgresql://") || url.startsWith("postgres://");
13
22
  }
14
23
  function createDefaultLogger() {
24
+ const verbose = parseBooleanEnv("PRISMA_SHARDING_VERBOSE", false);
15
25
  return {
16
- info: (msg) => console.log(`[PrismaSharding] ${msg}`),
26
+ info: (msg) => {
27
+ if (verbose) {
28
+ console.log(`[PrismaSharding] ${msg}`);
29
+ }
30
+ },
17
31
  warn: (msg) => console.warn(`[PrismaSharding] ${msg}`),
18
32
  error: (msg) => console.error(`[PrismaSharding] ${msg}`)
19
33
  };
@@ -381,6 +395,10 @@ var PrismaSharding = class {
381
395
  const shardId = this.router.getRandomShardId();
382
396
  return this.manager.getClient(shardId);
383
397
  }
398
+ /**
399
+ * Returns both the random shard client and its shardId.
400
+ * Used when the shardId must be stored on the user record for future routing.
401
+ */
384
402
  getRandomShardWithInfo() {
385
403
  this.ensureConnected();
386
404
  const shardId = this.router.getRandomShardId();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prisma-sharding",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
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",
@@ -40,6 +40,7 @@
40
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",
41
41
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
42
42
  "lint": "eslint src --ext .ts",
43
+ "test": "yarn build && node --test test/*.test.js",
43
44
  "typecheck": "tsc --noEmit",
44
45
  "prepublishOnly": "yarn build",
45
46
  "patch": "yarn version --patch && yarn publish",