postgresdk 0.16.12 → 0.16.14
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 +9 -0
- package/dist/cli.js +63 -16
- package/dist/emit-types.d.ts +1 -1
- package/dist/emit-zod.d.ts +1 -1
- package/dist/index.js +30 -15
- package/dist/types.d.ts +1 -0
- package/package.json +2 -2
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
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
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"))
|
|
@@ -3497,7 +3539,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
3497
3539
|
* const user = await client.create<{ metadata: Metadata }>({ name: 'Alice', metadata: { tags: [], prefs: { theme: 'light' } } });
|
|
3498
3540
|
*/
|
|
3499
3541
|
async create<TJsonb extends Partial<Select${Type}> = {}>(
|
|
3500
|
-
data: Insert${Type}<TJsonb
|
|
3542
|
+
data: NoInfer<Insert${Type}<TJsonb>>
|
|
3501
3543
|
): Promise<Select${Type}<TJsonb>> {
|
|
3502
3544
|
return this.post<Select${Type}<TJsonb>>(this.resource, data);
|
|
3503
3545
|
}` : ` /**
|
|
@@ -3600,7 +3642,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
3600
3642
|
*/
|
|
3601
3643
|
async update<TJsonb extends Partial<Select${Type}> = {}>(
|
|
3602
3644
|
pk: ${pkType},
|
|
3603
|
-
patch: Update${Type}<TJsonb
|
|
3645
|
+
patch: NoInfer<Update${Type}<TJsonb>>
|
|
3604
3646
|
): Promise<Select${Type}<TJsonb> | null> {
|
|
3605
3647
|
const path = ${pkPathExpr};
|
|
3606
3648
|
return this.patch<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`, patch);
|
|
@@ -3812,18 +3854,18 @@ export abstract class BaseClient {
|
|
|
3812
3854
|
/**
|
|
3813
3855
|
* Make a POST request
|
|
3814
3856
|
*/
|
|
3815
|
-
protected async post<T>(path: string, body?:
|
|
3857
|
+
protected async post<T>(path: string, body?: unknown): Promise<T> {
|
|
3816
3858
|
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
|
3817
3859
|
method: "POST",
|
|
3818
3860
|
headers: await this.headers(true),
|
|
3819
3861
|
body: JSON.stringify(body),
|
|
3820
3862
|
});
|
|
3821
|
-
|
|
3863
|
+
|
|
3822
3864
|
// Handle 404 specially for operations that might return null
|
|
3823
3865
|
if (res.status === 404) {
|
|
3824
3866
|
return null as T;
|
|
3825
3867
|
}
|
|
3826
|
-
|
|
3868
|
+
|
|
3827
3869
|
await this.okOrThrow(res, "POST", path);
|
|
3828
3870
|
return (await res.json()) as T;
|
|
3829
3871
|
}
|
|
@@ -3847,17 +3889,17 @@ export abstract class BaseClient {
|
|
|
3847
3889
|
/**
|
|
3848
3890
|
* Make a PATCH request
|
|
3849
3891
|
*/
|
|
3850
|
-
protected async patch<T>(path: string, body?:
|
|
3892
|
+
protected async patch<T>(path: string, body?: unknown): Promise<T> {
|
|
3851
3893
|
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
|
3852
3894
|
method: "PATCH",
|
|
3853
3895
|
headers: await this.headers(true),
|
|
3854
3896
|
body: JSON.stringify(body),
|
|
3855
3897
|
});
|
|
3856
|
-
|
|
3898
|
+
|
|
3857
3899
|
if (res.status === 404) {
|
|
3858
3900
|
return null as T;
|
|
3859
3901
|
}
|
|
3860
|
-
|
|
3902
|
+
|
|
3861
3903
|
await this.okOrThrow(res, "PATCH", path);
|
|
3862
3904
|
return (await res.json()) as T;
|
|
3863
3905
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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);
|
package/dist/emit-types.d.ts
CHANGED
package/dist/emit-zod.d.ts
CHANGED
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
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
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"))
|
|
@@ -2596,7 +2606,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
2596
2606
|
* const user = await client.create<{ metadata: Metadata }>({ name: 'Alice', metadata: { tags: [], prefs: { theme: 'light' } } });
|
|
2597
2607
|
*/
|
|
2598
2608
|
async create<TJsonb extends Partial<Select${Type}> = {}>(
|
|
2599
|
-
data: Insert${Type}<TJsonb
|
|
2609
|
+
data: NoInfer<Insert${Type}<TJsonb>>
|
|
2600
2610
|
): Promise<Select${Type}<TJsonb>> {
|
|
2601
2611
|
return this.post<Select${Type}<TJsonb>>(this.resource, data);
|
|
2602
2612
|
}` : ` /**
|
|
@@ -2699,7 +2709,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
2699
2709
|
*/
|
|
2700
2710
|
async update<TJsonb extends Partial<Select${Type}> = {}>(
|
|
2701
2711
|
pk: ${pkType},
|
|
2702
|
-
patch: Update${Type}<TJsonb
|
|
2712
|
+
patch: NoInfer<Update${Type}<TJsonb>>
|
|
2703
2713
|
): Promise<Select${Type}<TJsonb> | null> {
|
|
2704
2714
|
const path = ${pkPathExpr};
|
|
2705
2715
|
return this.patch<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`, patch);
|
|
@@ -2911,18 +2921,18 @@ export abstract class BaseClient {
|
|
|
2911
2921
|
/**
|
|
2912
2922
|
* Make a POST request
|
|
2913
2923
|
*/
|
|
2914
|
-
protected async post<T>(path: string, body?:
|
|
2924
|
+
protected async post<T>(path: string, body?: unknown): Promise<T> {
|
|
2915
2925
|
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
|
2916
2926
|
method: "POST",
|
|
2917
2927
|
headers: await this.headers(true),
|
|
2918
2928
|
body: JSON.stringify(body),
|
|
2919
2929
|
});
|
|
2920
|
-
|
|
2930
|
+
|
|
2921
2931
|
// Handle 404 specially for operations that might return null
|
|
2922
2932
|
if (res.status === 404) {
|
|
2923
2933
|
return null as T;
|
|
2924
2934
|
}
|
|
2925
|
-
|
|
2935
|
+
|
|
2926
2936
|
await this.okOrThrow(res, "POST", path);
|
|
2927
2937
|
return (await res.json()) as T;
|
|
2928
2938
|
}
|
|
@@ -2946,17 +2956,17 @@ export abstract class BaseClient {
|
|
|
2946
2956
|
/**
|
|
2947
2957
|
* Make a PATCH request
|
|
2948
2958
|
*/
|
|
2949
|
-
protected async patch<T>(path: string, body?:
|
|
2959
|
+
protected async patch<T>(path: string, body?: unknown): Promise<T> {
|
|
2950
2960
|
const res = await this.fetchFn(\`\${this.baseUrl}\${path}\`, {
|
|
2951
2961
|
method: "PATCH",
|
|
2952
2962
|
headers: await this.headers(true),
|
|
2953
2963
|
body: JSON.stringify(body),
|
|
2954
2964
|
});
|
|
2955
|
-
|
|
2965
|
+
|
|
2956
2966
|
if (res.status === 404) {
|
|
2957
2967
|
return null as T;
|
|
2958
2968
|
}
|
|
2959
|
-
|
|
2969
|
+
|
|
2960
2970
|
await this.okOrThrow(res, "PATCH", path);
|
|
2961
2971
|
return (await res.json()) as T;
|
|
2962
2972
|
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.16.14",
|
|
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",
|