postgresdk 0.16.12 → 0.16.13

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 CHANGED
@@ -146,6 +146,7 @@ export default {
146
146
  schema: "public", // Database schema to introspect
147
147
  outDir: "./api", // Output directory (or { client: "./sdk", server: "./api" })
148
148
  softDeleteColumn: null, // Column name for soft deletes (e.g., "deleted_at")
149
+ numericMode: "auto", // "auto" | "number" | "string" - How to type numeric columns
149
150
  includeMethodsDepth: 2, // Max depth for nested includes
150
151
  dateType: "date", // "date" | "string" - How to handle timestamps
151
152
  serverFramework: "hono", // Currently only hono is supported
@@ -175,6 +176,14 @@ export default {
175
176
  };
176
177
  ```
177
178
 
179
+ #### Type Mapping (numericMode)
180
+
181
+ Controls how PostgreSQL numeric types map to TypeScript:
182
+
183
+ - **`"auto"` (default)**: `int2`/`int4`/floats → `number`, `int8`/`numeric` → `string`
184
+ - **`"number"`**: All numeric → `number` (⚠️ unsafe for bigint - JS can't handle values > 2^53)
185
+ - **`"string"`**: All numeric → `string` (safe but requires parsing)
186
+
178
187
  ### Database Drivers
179
188
 
180
189
  The generated code works with any PostgreSQL client that implements a simple `query` interface:
package/dist/cli.js CHANGED
@@ -1751,6 +1751,15 @@ function extractConfigFields(configContent) {
1751
1751
  isCommented: !!depthMatch[1]
1752
1752
  });
1753
1753
  }
1754
+ const numericModeMatch = configContent.match(/^\s*(\/\/)?\s*numericMode:\s*"(.+)"/m);
1755
+ if (numericModeMatch) {
1756
+ fields.push({
1757
+ key: "numericMode",
1758
+ value: numericModeMatch[2],
1759
+ description: "How to type numeric columns in TypeScript",
1760
+ isCommented: !!numericModeMatch[1]
1761
+ });
1762
+ }
1754
1763
  const frameworkMatch = configContent.match(/^\s*(\/\/)?\s*serverFramework:\s*"(.+)"/m);
1755
1764
  if (frameworkMatch) {
1756
1765
  fields.push({
@@ -1918,7 +1927,16 @@ export default {
1918
1927
  * @example "deleted_at"
1919
1928
  */
1920
1929
  ${getFieldLine("softDeleteColumn", existingFields, mergeStrategy, "null", userChoices)}
1921
-
1930
+
1931
+ /**
1932
+ * How to type numeric columns in TypeScript
1933
+ * - "auto": int2/int4/float → number, int8/numeric → string (recommended)
1934
+ * - "number": All numeric types become TypeScript number (unsafe for bigint)
1935
+ * - "string": All numeric types become TypeScript string (legacy)
1936
+ * @default "auto"
1937
+ */
1938
+ ${getFieldLine("numericMode", existingFields, mergeStrategy, '"auto"', userChoices)}
1939
+
1922
1940
  /**
1923
1941
  * Maximum depth for nested relationship includes to prevent infinite loops
1924
1942
  * @default 2
@@ -2347,6 +2365,20 @@ export default {
2347
2365
  */
2348
2366
  // softDeleteColumn: null,
2349
2367
 
2368
+ /**
2369
+ * How to type numeric columns in TypeScript
2370
+ * Options:
2371
+ * - "auto": int2/int4/float → number, int8/numeric → string (recommended, default)
2372
+ * - "number": All numeric types become TypeScript number (unsafe for bigint)
2373
+ * - "string": All numeric types become TypeScript string (legacy behavior)
2374
+ *
2375
+ * Auto mode is safest - keeps JavaScript-safe integers as numbers,
2376
+ * but preserves precision for bigint/numeric by using strings.
2377
+ *
2378
+ * Default: "auto"
2379
+ */
2380
+ // numericMode: "auto",
2381
+
2350
2382
  /**
2351
2383
  * Maximum depth for nested relationship includes to prevent infinite loops
2352
2384
  * Default: 2
@@ -2879,10 +2911,20 @@ function emitZod(table, opts, enums) {
2879
2911
  return `z.string()`;
2880
2912
  if (t === "bool" || t === "boolean")
2881
2913
  return `z.boolean()`;
2882
- if (t === "int2" || t === "int4" || t === "int8")
2883
- return opts.numericMode === "number" ? `z.number()` : `z.string()`;
2884
- if (t === "numeric" || t === "float4" || t === "float8")
2885
- return opts.numericMode === "number" ? `z.number()` : `z.string()`;
2914
+ if (t === "int2" || t === "int4" || t === "int8") {
2915
+ if (opts.numericMode === "number")
2916
+ return `z.number()`;
2917
+ if (opts.numericMode === "string")
2918
+ return `z.string()`;
2919
+ return t === "int2" || t === "int4" ? `z.number()` : `z.string()`;
2920
+ }
2921
+ if (t === "numeric" || t === "float4" || t === "float8") {
2922
+ if (opts.numericMode === "number")
2923
+ return `z.number()`;
2924
+ if (opts.numericMode === "string")
2925
+ return `z.string()`;
2926
+ return t === "float4" || t === "float8" ? `z.number()` : `z.string()`;
2927
+ }
2886
2928
  if (t === "jsonb" || t === "json")
2887
2929
  return `z.unknown()`;
2888
2930
  if (t === "date" || t.startsWith("timestamp"))
@@ -4346,7 +4388,11 @@ function tsTypeFor(pgType, opts, enums) {
4346
4388
  if (t === "bool" || t === "boolean")
4347
4389
  return "boolean";
4348
4390
  if (t === "int2" || t === "int4" || t === "int8" || t === "float4" || t === "float8" || t === "numeric") {
4349
- return opts.numericMode === "number" ? "number" : "string";
4391
+ if (opts.numericMode === "number")
4392
+ return "number";
4393
+ if (opts.numericMode === "string")
4394
+ return "string";
4395
+ return t === "int2" || t === "int4" || t === "float4" || t === "float8" ? "number" : "string";
4350
4396
  }
4351
4397
  if (t === "date" || t.startsWith("timestamp"))
4352
4398
  return "string";
@@ -6551,10 +6597,11 @@ async function generate(configPath) {
6551
6597
  console.log(`[Index] About to process ${Object.keys(model.tables || {}).length} tables for generation`);
6552
6598
  }
6553
6599
  for (const table of Object.values(model.tables)) {
6554
- const typesSrc = emitTypes(table, { numericMode: "string" }, model.enums);
6600
+ const numericMode = cfg.numericMode ?? "auto";
6601
+ const typesSrc = emitTypes(table, { numericMode }, model.enums);
6555
6602
  files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
6556
6603
  files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
6557
- const zodSrc = emitZod(table, { numericMode: "string" }, model.enums);
6604
+ const zodSrc = emitZod(table, { numericMode }, model.enums);
6558
6605
  files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
6559
6606
  files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
6560
6607
  const paramsZodSrc = emitParamsZod(table, graph);
@@ -1,4 +1,4 @@
1
1
  import type { Table } from "./introspect";
2
2
  export declare function emitTypes(table: Table, opts: {
3
- numericMode: "string" | "number";
3
+ numericMode: "string" | "number" | "auto";
4
4
  }, enums: Record<string, string[]>): string;
@@ -1,4 +1,4 @@
1
1
  import type { Table } from "./introspect";
2
2
  export declare function emitZod(table: Table, opts: {
3
- numericMode: "string" | "number";
3
+ numericMode: "string" | "number" | "auto";
4
4
  }, enums: Record<string, string[]>): string;
package/dist/index.js CHANGED
@@ -1978,10 +1978,20 @@ function emitZod(table, opts, enums) {
1978
1978
  return `z.string()`;
1979
1979
  if (t === "bool" || t === "boolean")
1980
1980
  return `z.boolean()`;
1981
- if (t === "int2" || t === "int4" || t === "int8")
1982
- return opts.numericMode === "number" ? `z.number()` : `z.string()`;
1983
- if (t === "numeric" || t === "float4" || t === "float8")
1984
- return opts.numericMode === "number" ? `z.number()` : `z.string()`;
1981
+ if (t === "int2" || t === "int4" || t === "int8") {
1982
+ if (opts.numericMode === "number")
1983
+ return `z.number()`;
1984
+ if (opts.numericMode === "string")
1985
+ return `z.string()`;
1986
+ return t === "int2" || t === "int4" ? `z.number()` : `z.string()`;
1987
+ }
1988
+ if (t === "numeric" || t === "float4" || t === "float8") {
1989
+ if (opts.numericMode === "number")
1990
+ return `z.number()`;
1991
+ if (opts.numericMode === "string")
1992
+ return `z.string()`;
1993
+ return t === "float4" || t === "float8" ? `z.number()` : `z.string()`;
1994
+ }
1985
1995
  if (t === "jsonb" || t === "json")
1986
1996
  return `z.unknown()`;
1987
1997
  if (t === "date" || t.startsWith("timestamp"))
@@ -3445,7 +3455,11 @@ function tsTypeFor(pgType, opts, enums) {
3445
3455
  if (t === "bool" || t === "boolean")
3446
3456
  return "boolean";
3447
3457
  if (t === "int2" || t === "int4" || t === "int8" || t === "float4" || t === "float8" || t === "numeric") {
3448
- return opts.numericMode === "number" ? "number" : "string";
3458
+ if (opts.numericMode === "number")
3459
+ return "number";
3460
+ if (opts.numericMode === "string")
3461
+ return "string";
3462
+ return t === "int2" || t === "int4" || t === "float4" || t === "float8" ? "number" : "string";
3449
3463
  }
3450
3464
  if (t === "date" || t.startsWith("timestamp"))
3451
3465
  return "string";
@@ -5650,10 +5664,11 @@ async function generate(configPath) {
5650
5664
  console.log(`[Index] About to process ${Object.keys(model.tables || {}).length} tables for generation`);
5651
5665
  }
5652
5666
  for (const table of Object.values(model.tables)) {
5653
- const typesSrc = emitTypes(table, { numericMode: "string" }, model.enums);
5667
+ const numericMode = cfg.numericMode ?? "auto";
5668
+ const typesSrc = emitTypes(table, { numericMode }, model.enums);
5654
5669
  files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
5655
5670
  files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
5656
- const zodSrc = emitZod(table, { numericMode: "string" }, model.enums);
5671
+ const zodSrc = emitZod(table, { numericMode }, model.enums);
5657
5672
  files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
5658
5673
  files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
5659
5674
  const paramsZodSrc = emitParamsZod(table, graph);
package/dist/types.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface Config {
24
24
  };
25
25
  softDeleteColumn?: string | null;
26
26
  dateType?: "date" | "string";
27
+ numericMode?: "string" | "number" | "auto";
27
28
  includeMethodsDepth?: number;
28
29
  skipJunctionTables?: boolean;
29
30
  serverFramework?: "hono" | "express" | "fastify";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.16.12",
3
+ "version": "0.16.13",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "scripts": {
24
24
  "build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=prompts --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
25
- "test": "bun test:init && bun test:gen && bun test test/test-where-clause.test.ts && bun test test/test-where-or-and.test.ts && bun test test/test-nested-include-options.test.ts && bun test test/test-include-methods-with-options.test.ts && bun test:gen-with-tests && bun test:pull && bun test:enums && bun test:typecheck && bun test:drizzle-e2e",
25
+ "test": "bun test:init && bun test:gen && bun test test/test-where-clause.test.ts && bun test test/test-where-or-and.test.ts && bun test test/test-nested-include-options.test.ts && bun test test/test-include-methods-with-options.test.ts && bun test:gen-with-tests && bun test:pull && bun test:enums && bun test:typecheck && bun test:drizzle-e2e && bun test test/test-numeric-mode-integration.test.ts",
26
26
  "test:init": "bun test/test-init.ts",
27
27
  "test:gen": "bun test/test-gen.ts",
28
28
  "test:gen-with-tests": "bun test/test-gen-with-tests.ts",