prisma-sharding 0.0.4 → 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();
@@ -0,0 +1,249 @@
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/update.ts
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
40
+ var import_child_process = require("child_process");
41
+ var import_path = __toESM(require("path"));
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) => {
148
+ const shards = [];
149
+ const missingShardIds = [];
150
+ const shardCount = parseInt(env.SHARD_COUNT || "0", 10);
151
+ for (let i = 1; i <= shardCount; i++) {
152
+ const url = env[`SHARD_${i}_URL`];
153
+ if (url) {
154
+ shards.push({ id: `shard_${i}`, index: i - 1, url });
155
+ } else {
156
+ missingShardIds.push(`shard_${i}`);
157
+ }
158
+ }
159
+ if (shards.length === 0 && env.DATABASE_URL) {
160
+ shards.push({ id: "shard_1", index: 0, url: env.DATABASE_URL });
161
+ }
162
+ return { shards, missingShardIds };
163
+ };
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
195
+ }
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;
208
+ };
209
+
210
+ // src/cli/update.ts
211
+ var updateAll = async () => {
212
+ const verbose = isVerboseEnv(["SHARD_UPDATE_VERBOSE", "SHARD_CLI_VERBOSE"]);
213
+ const shards = getShardConfigs();
214
+ const extraArgs = process.argv.slice(2);
215
+ printCliHeader("\u{1F504}", "Prisma Sharding Update");
216
+ if (shards.length === 0) {
217
+ printCliRow("\u274C", "config", NO_SHARDS_CONFIGURED_MESSAGE);
218
+ process.exit(1);
219
+ }
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();
232
+ }
233
+ process.exit(1);
234
+ }
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;
239
+ if (failed > 0) {
240
+ if (!verbose) {
241
+ printVerboseHint();
242
+ }
243
+ process.exit(1);
244
+ }
245
+ };
246
+ updateAll().catch((error) => {
247
+ console.error(error instanceof Error ? error.message : String(error));
248
+ process.exit(1);
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.4",
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",
@@ -20,6 +20,7 @@
20
20
  ],
21
21
  "bin": {
22
22
  "prisma-sharding-migrate": "./dist/cli/migrate.js",
23
+ "prisma-sharding-update": "./dist/cli/update.js",
23
24
  "prisma-sharding-studio": "./dist/cli/studio.js",
24
25
  "prisma-sharding-test": "./dist/cli/test.js"
25
26
  },
@@ -34,11 +35,12 @@
34
35
  }
35
36
  },
36
37
  "scripts": {
37
- "build": "tsup src/index.ts --format cjs,esm --dts --clean && tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts --format cjs --outDir dist/cli --clean",
38
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean && tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts src/cli/update.ts --format cjs --outDir dist/cli --clean",
38
39
  "build:lib": "tsup src/index.ts --format cjs,esm --dts --clean",
39
- "build:cli": "tsup src/cli/migrate.ts src/cli/studio.ts src/cli/test.ts --format cjs --outDir dist/cli",
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",
40
41
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
41
42
  "lint": "eslint src --ext .ts",
43
+ "test": "yarn build && node --test test/*.test.js",
42
44
  "typecheck": "tsc --noEmit",
43
45
  "prepublishOnly": "yarn build",
44
46
  "patch": "yarn version --patch && yarn publish",