postgresdk 0.18.9 → 0.18.11
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 +4 -1
- package/dist/cli.js +104 -26
- package/dist/emit-include-resolver.d.ts +9 -0
- package/dist/emit-sdk-bundle.d.ts +1 -1
- package/dist/index.js +94 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -567,7 +567,7 @@ const deleted = await sdk.users.delete(123);
|
|
|
567
567
|
|
|
568
568
|
#### Relationships & Eager Loading
|
|
569
569
|
|
|
570
|
-
Automatically handles relationships with the `include` parameter:
|
|
570
|
+
Automatically handles relationships with the `include` parameter. **Type inference works automatically** - no manual casts needed:
|
|
571
571
|
|
|
572
572
|
```typescript
|
|
573
573
|
// 1:N relationship - Get authors with their books
|
|
@@ -575,12 +575,14 @@ const authorsResult = await sdk.authors.list({
|
|
|
575
575
|
include: { books: true }
|
|
576
576
|
});
|
|
577
577
|
const authors = authorsResult.data;
|
|
578
|
+
// ✅ authors[0].books is automatically typed as SelectBooks[]
|
|
578
579
|
|
|
579
580
|
// M:N relationship - Get books with their tags
|
|
580
581
|
const booksResult = await sdk.books.list({
|
|
581
582
|
include: { tags: true }
|
|
582
583
|
});
|
|
583
584
|
const books = booksResult.data;
|
|
585
|
+
// ✅ books[0].tags is automatically typed as SelectTags[]
|
|
584
586
|
|
|
585
587
|
// Nested includes - Get authors with books and their tags
|
|
586
588
|
const nestedResult = await sdk.authors.list({
|
|
@@ -591,6 +593,7 @@ const nestedResult = await sdk.authors.list({
|
|
|
591
593
|
}
|
|
592
594
|
});
|
|
593
595
|
const authorsWithBooksAndTags = nestedResult.data;
|
|
596
|
+
// ✅ TypeScript knows: data[0].books[0].tags exists and is SelectTags[]
|
|
594
597
|
```
|
|
595
598
|
|
|
596
599
|
**Typed Include Methods:**
|
package/dist/cli.js
CHANGED
|
@@ -2119,7 +2119,7 @@ var exports_cli_init = {};
|
|
|
2119
2119
|
__export(exports_cli_init, {
|
|
2120
2120
|
initCommand: () => initCommand
|
|
2121
2121
|
});
|
|
2122
|
-
import { existsSync as existsSync3, writeFileSync, readFileSync, copyFileSync } from "fs";
|
|
2122
|
+
import { existsSync as existsSync3, writeFileSync, readFileSync as readFileSync2, copyFileSync } from "fs";
|
|
2123
2123
|
import { resolve } from "path";
|
|
2124
2124
|
import prompts from "prompts";
|
|
2125
2125
|
async function initCommand(args) {
|
|
@@ -2137,7 +2137,7 @@ async function initCommand(args) {
|
|
|
2137
2137
|
}
|
|
2138
2138
|
console.log(`⚠️ Found existing postgresdk.config.ts
|
|
2139
2139
|
`);
|
|
2140
|
-
const existingContent =
|
|
2140
|
+
const existingContent = readFileSync2(configPath, "utf-8");
|
|
2141
2141
|
const existingFields = extractConfigFields(existingContent);
|
|
2142
2142
|
console.log("\uD83D\uDCCB Existing configuration detected:");
|
|
2143
2143
|
existingFields.forEach((field) => {
|
|
@@ -2529,7 +2529,7 @@ __export(exports_cli_pull, {
|
|
|
2529
2529
|
pullCommand: () => pullCommand
|
|
2530
2530
|
});
|
|
2531
2531
|
import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
2532
|
-
import { join as join2, dirname as
|
|
2532
|
+
import { join as join2, dirname as dirname3, resolve as resolve2 } from "path";
|
|
2533
2533
|
import { existsSync as existsSync4 } from "fs";
|
|
2534
2534
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
2535
2535
|
async function pullCommand(args) {
|
|
@@ -2616,7 +2616,7 @@ Options:`);
|
|
|
2616
2616
|
const changedFiles = [];
|
|
2617
2617
|
for (const [path, content] of Object.entries(sdk.files)) {
|
|
2618
2618
|
const fullPath = join2(config.output, path);
|
|
2619
|
-
await mkdir2(
|
|
2619
|
+
await mkdir2(dirname3(fullPath), { recursive: true });
|
|
2620
2620
|
let shouldWrite = true;
|
|
2621
2621
|
if (existsSync4(fullPath)) {
|
|
2622
2622
|
const existing = await readFile2(fullPath, "utf-8");
|
|
@@ -2662,9 +2662,9 @@ var init_cli_pull = () => {};
|
|
|
2662
2662
|
|
|
2663
2663
|
// src/index.ts
|
|
2664
2664
|
var import_config = __toESM(require_config(), 1);
|
|
2665
|
-
import { join, relative } from "node:path";
|
|
2666
|
-
import { pathToFileURL } from "node:url";
|
|
2667
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
2665
|
+
import { join, relative, dirname as dirname2 } from "node:path";
|
|
2666
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
2667
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
2668
2668
|
|
|
2669
2669
|
// src/introspect.ts
|
|
2670
2670
|
import { Client } from "pg";
|
|
@@ -2899,6 +2899,74 @@ function toPascal(s) {
|
|
|
2899
2899
|
return s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
|
|
2900
2900
|
}
|
|
2901
2901
|
|
|
2902
|
+
// src/emit-include-resolver.ts
|
|
2903
|
+
init_utils();
|
|
2904
|
+
function emitIncludeResolver(graph, useJsExtensions) {
|
|
2905
|
+
const ext = useJsExtensions ? ".js" : "";
|
|
2906
|
+
let out = `/**
|
|
2907
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2908
|
+
*
|
|
2909
|
+
* Type helpers for automatic include inference.
|
|
2910
|
+
* These types transform IncludeSpec into the actual return type shape.
|
|
2911
|
+
*/
|
|
2912
|
+
`;
|
|
2913
|
+
const tables = Object.keys(graph);
|
|
2914
|
+
for (const table of tables) {
|
|
2915
|
+
out += `import type { Select${pascal(table)} } from "./types/${table}${ext}";
|
|
2916
|
+
`;
|
|
2917
|
+
}
|
|
2918
|
+
for (const table of tables) {
|
|
2919
|
+
out += `import type { ${pascal(table)}IncludeSpec } from "./include-spec${ext}";
|
|
2920
|
+
`;
|
|
2921
|
+
}
|
|
2922
|
+
out += `
|
|
2923
|
+
`;
|
|
2924
|
+
for (const table of tables) {
|
|
2925
|
+
const Type = pascal(table);
|
|
2926
|
+
const edges = graph[table] || {};
|
|
2927
|
+
const edgeEntries = Object.entries(edges);
|
|
2928
|
+
if (edgeEntries.length === 0) {
|
|
2929
|
+
out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> = Select${Type};
|
|
2930
|
+
|
|
2931
|
+
`;
|
|
2932
|
+
continue;
|
|
2933
|
+
}
|
|
2934
|
+
out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> =
|
|
2935
|
+
Select${Type} & {
|
|
2936
|
+
[K in keyof TInclude as TInclude[K] extends false | undefined ? never : K]:`;
|
|
2937
|
+
for (let i = 0;i < edgeEntries.length; i++) {
|
|
2938
|
+
const entry = edgeEntries[i];
|
|
2939
|
+
if (!entry)
|
|
2940
|
+
continue;
|
|
2941
|
+
const [relKey, edge] = entry;
|
|
2942
|
+
const targetType = pascal(edge.target);
|
|
2943
|
+
const isLast = i === edgeEntries.length - 1;
|
|
2944
|
+
out += `
|
|
2945
|
+
K extends '${relKey}' ? `;
|
|
2946
|
+
if (edge.kind === "many") {
|
|
2947
|
+
out += `(
|
|
2948
|
+
TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
|
|
2949
|
+
? Array<${targetType}WithIncludes<U>>
|
|
2950
|
+
: Select${targetType}[]
|
|
2951
|
+
)`;
|
|
2952
|
+
} else {
|
|
2953
|
+
out += `(
|
|
2954
|
+
TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
|
|
2955
|
+
? ${targetType}WithIncludes<U>
|
|
2956
|
+
: Select${targetType}
|
|
2957
|
+
)`;
|
|
2958
|
+
}
|
|
2959
|
+
out += ` :${isLast ? `
|
|
2960
|
+
never` : ""}`;
|
|
2961
|
+
}
|
|
2962
|
+
out += `
|
|
2963
|
+
};
|
|
2964
|
+
|
|
2965
|
+
`;
|
|
2966
|
+
}
|
|
2967
|
+
return out;
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2902
2970
|
// src/emit-include-builder.ts
|
|
2903
2971
|
function emitIncludeBuilder(graph, maxDepth) {
|
|
2904
2972
|
return `/**
|
|
@@ -3462,6 +3530,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
3462
3530
|
const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
|
|
3463
3531
|
const includeSpecTypes = [table.name, ...Array.from(importedTypes).filter((t) => t !== table.name)];
|
|
3464
3532
|
const includeSpecImport = `import type { ${includeSpecTypes.map((t) => `${pascal(t)}IncludeSpec`).join(", ")} } from "./include-spec${ext}";`;
|
|
3533
|
+
const includeResolverImport = `import type { ${Type}WithIncludes } from "./include-resolver${ext}";`;
|
|
3465
3534
|
const otherTableImports = [];
|
|
3466
3535
|
for (const target of Array.from(importedTypes)) {
|
|
3467
3536
|
if (target !== table.name) {
|
|
@@ -3655,6 +3724,7 @@ import type { Where } from "./where-types${ext}";
|
|
|
3655
3724
|
import type { PaginatedResponse } from "./types/shared${ext}";
|
|
3656
3725
|
${typeImports}
|
|
3657
3726
|
${includeSpecImport}
|
|
3727
|
+
${includeResolverImport}
|
|
3658
3728
|
${otherTableImports.join(`
|
|
3659
3729
|
`)}
|
|
3660
3730
|
|
|
@@ -3845,14 +3915,17 @@ ${hasJsonbColumns ? ` /**
|
|
|
3845
3915
|
* @param params.order - Sort direction(s): "asc" or "desc"
|
|
3846
3916
|
* @param params.limit - Maximum number of records to return (default: 50, max: 1000)
|
|
3847
3917
|
* @param params.offset - Number of records to skip for pagination
|
|
3848
|
-
* @param params.include - Related records to include (
|
|
3849
|
-
* @returns Paginated results with all fields
|
|
3918
|
+
* @param params.include - Related records to include (return type automatically infers included relations)
|
|
3919
|
+
* @returns Paginated results with all fields (and included relations if specified)
|
|
3850
3920
|
* @example
|
|
3851
3921
|
* // With JSONB type override:
|
|
3852
3922
|
* const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
|
|
3923
|
+
* // With automatic include inference:
|
|
3924
|
+
* const users = await client.list({ include: { posts: true } });
|
|
3925
|
+
* // users[0].posts is automatically typed
|
|
3853
3926
|
*/
|
|
3854
|
-
async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
|
|
3855
|
-
include?:
|
|
3927
|
+
async list<TJsonb extends Partial<Select${Type}> = {}, TInclude extends ${Type}IncludeSpec = {}>(params?: {
|
|
3928
|
+
include?: TInclude;
|
|
3856
3929
|
limit?: number;
|
|
3857
3930
|
offset?: number;
|
|
3858
3931
|
where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
|
|
@@ -3864,7 +3937,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
3864
3937
|
};` : ""}
|
|
3865
3938
|
orderBy?: string | string[];
|
|
3866
3939
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3867
|
-
}): Promise<PaginatedResponse
|
|
3940
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3868
3941
|
async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
|
|
3869
3942
|
include?: ${Type}IncludeSpec;
|
|
3870
3943
|
select?: string[];
|
|
@@ -3930,11 +4003,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
3930
4003
|
* @param params.order - Sort direction(s): "asc" or "desc"
|
|
3931
4004
|
* @param params.limit - Maximum number of records to return (default: 50, max: 1000)
|
|
3932
4005
|
* @param params.offset - Number of records to skip for pagination
|
|
3933
|
-
* @param params.include - Related records to include (
|
|
3934
|
-
* @returns Paginated results with all fields
|
|
4006
|
+
* @param params.include - Related records to include (return type automatically infers included relations)
|
|
4007
|
+
* @returns Paginated results with all fields (and included relations if specified)
|
|
3935
4008
|
*/
|
|
3936
|
-
async list(params?: {
|
|
3937
|
-
include?:
|
|
4009
|
+
async list<TInclude extends ${Type}IncludeSpec = {}>(params?: {
|
|
4010
|
+
include?: TInclude;
|
|
3938
4011
|
limit?: number;
|
|
3939
4012
|
offset?: number;
|
|
3940
4013
|
where?: Where<Select${Type}>;${hasVectorColumns ? `
|
|
@@ -3946,7 +4019,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
3946
4019
|
};` : ""}
|
|
3947
4020
|
orderBy?: string | string[];
|
|
3948
4021
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3949
|
-
}): Promise<PaginatedResponse
|
|
4022
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
3950
4023
|
async list(params?: {
|
|
3951
4024
|
include?: ${Type}IncludeSpec;
|
|
3952
4025
|
select?: string[];
|
|
@@ -5575,7 +5648,7 @@ export * from "./include-spec${ext}";
|
|
|
5575
5648
|
}
|
|
5576
5649
|
|
|
5577
5650
|
// src/emit-sdk-bundle.ts
|
|
5578
|
-
function emitSdkBundle(clientFiles, clientDir) {
|
|
5651
|
+
function emitSdkBundle(clientFiles, clientDir, version) {
|
|
5579
5652
|
const files = {};
|
|
5580
5653
|
for (const file of clientFiles) {
|
|
5581
5654
|
const normalizedClientDir = clientDir.replace(/\\/g, "/");
|
|
@@ -5585,7 +5658,6 @@ function emitSdkBundle(clientFiles, clientDir) {
|
|
|
5585
5658
|
files[relativePath] = file.content;
|
|
5586
5659
|
}
|
|
5587
5660
|
}
|
|
5588
|
-
const version = `1.0.0`;
|
|
5589
5661
|
return `/**
|
|
5590
5662
|
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
5591
5663
|
*
|
|
@@ -7059,6 +7131,9 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
|
|
|
7059
7131
|
// src/index.ts
|
|
7060
7132
|
init_emit_sdk_contract();
|
|
7061
7133
|
init_utils();
|
|
7134
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
7135
|
+
var __dirname2 = dirname2(__filename2);
|
|
7136
|
+
var { version: CLI_VERSION } = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
|
|
7062
7137
|
async function generate(configPath) {
|
|
7063
7138
|
if (!existsSync2(configPath)) {
|
|
7064
7139
|
throw new Error(`Config file not found: ${configPath}
|
|
@@ -7071,6 +7146,7 @@ async function generate(configPath) {
|
|
|
7071
7146
|
const rawCfg = module.default || module;
|
|
7072
7147
|
const normalizedAuth = normalizeAuthConfig(rawCfg.auth);
|
|
7073
7148
|
const cfg = { ...rawCfg, auth: normalizedAuth };
|
|
7149
|
+
console.log(`\uD83D\uDCE6 postgresdk v${CLI_VERSION}`);
|
|
7074
7150
|
console.log("\uD83D\uDD0D Introspecting database...");
|
|
7075
7151
|
const model = await introspect(cfg.connectionString, cfg.schema || "public");
|
|
7076
7152
|
console.log("\uD83D\uDD17 Building relationship graph...");
|
|
@@ -7119,6 +7195,8 @@ async function generate(configPath) {
|
|
|
7119
7195
|
const includeSpec = emitIncludeSpec(graph);
|
|
7120
7196
|
files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
|
|
7121
7197
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
|
7198
|
+
const includeResolver = emitIncludeResolver(graph, cfg.useJsExtensions);
|
|
7199
|
+
files.push({ path: join(clientDir, "include-resolver.ts"), content: includeResolver });
|
|
7122
7200
|
files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
|
|
7123
7201
|
files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
|
|
7124
7202
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
@@ -7210,7 +7288,7 @@ async function generate(configPath) {
|
|
|
7210
7288
|
});
|
|
7211
7289
|
files.push({
|
|
7212
7290
|
path: join(serverDir, "sdk-bundle.ts"),
|
|
7213
|
-
content: emitSdkBundle(clientFiles, clientDir)
|
|
7291
|
+
content: emitSdkBundle(clientFiles, clientDir, CLI_VERSION)
|
|
7214
7292
|
});
|
|
7215
7293
|
if (generateTests) {
|
|
7216
7294
|
console.log("\uD83E\uDDEA Generating tests...");
|
|
@@ -7286,12 +7364,12 @@ async function generate(configPath) {
|
|
|
7286
7364
|
// src/cli.ts
|
|
7287
7365
|
var import_config2 = __toESM(require_config(), 1);
|
|
7288
7366
|
import { resolve as resolve3 } from "node:path";
|
|
7289
|
-
import { readFileSync as
|
|
7290
|
-
import { fileURLToPath } from "node:url";
|
|
7291
|
-
import { dirname as
|
|
7292
|
-
var
|
|
7293
|
-
var
|
|
7294
|
-
var packageJson = JSON.parse(
|
|
7367
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7368
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7369
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
7370
|
+
var __filename3 = fileURLToPath2(import.meta.url);
|
|
7371
|
+
var __dirname3 = dirname4(__filename3);
|
|
7372
|
+
var packageJson = JSON.parse(readFileSync3(join3(__dirname3, "../package.json"), "utf-8"));
|
|
7295
7373
|
var VERSION = packageJson.version;
|
|
7296
7374
|
var args = process.argv.slice(2);
|
|
7297
7375
|
var command = args[0];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Graph } from "./rel-classify";
|
|
2
|
+
/**
|
|
3
|
+
* Generate TypeScript types that resolve IncludeSpec to actual return types
|
|
4
|
+
*
|
|
5
|
+
* This allows automatic type inference:
|
|
6
|
+
* const result = await sdk.captures.list({ include: { website: true } });
|
|
7
|
+
* // result.data[0].website is typed as SelectWebsites
|
|
8
|
+
*/
|
|
9
|
+
export declare function emitIncludeResolver(graph: Graph, useJsExtensions?: boolean): string;
|
package/dist/index.js
CHANGED
|
@@ -1701,9 +1701,9 @@ var init_emit_sdk_contract = __esm(() => {
|
|
|
1701
1701
|
|
|
1702
1702
|
// src/index.ts
|
|
1703
1703
|
var import_config = __toESM(require_config(), 1);
|
|
1704
|
-
import { join, relative } from "node:path";
|
|
1705
|
-
import { pathToFileURL } from "node:url";
|
|
1706
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
1704
|
+
import { join, relative, dirname as dirname2 } from "node:path";
|
|
1705
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
1706
|
+
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
1707
1707
|
|
|
1708
1708
|
// src/introspect.ts
|
|
1709
1709
|
import { Client } from "pg";
|
|
@@ -1938,6 +1938,74 @@ function toPascal(s) {
|
|
|
1938
1938
|
return s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
|
|
1939
1939
|
}
|
|
1940
1940
|
|
|
1941
|
+
// src/emit-include-resolver.ts
|
|
1942
|
+
init_utils();
|
|
1943
|
+
function emitIncludeResolver(graph, useJsExtensions) {
|
|
1944
|
+
const ext = useJsExtensions ? ".js" : "";
|
|
1945
|
+
let out = `/**
|
|
1946
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
1947
|
+
*
|
|
1948
|
+
* Type helpers for automatic include inference.
|
|
1949
|
+
* These types transform IncludeSpec into the actual return type shape.
|
|
1950
|
+
*/
|
|
1951
|
+
`;
|
|
1952
|
+
const tables = Object.keys(graph);
|
|
1953
|
+
for (const table of tables) {
|
|
1954
|
+
out += `import type { Select${pascal(table)} } from "./types/${table}${ext}";
|
|
1955
|
+
`;
|
|
1956
|
+
}
|
|
1957
|
+
for (const table of tables) {
|
|
1958
|
+
out += `import type { ${pascal(table)}IncludeSpec } from "./include-spec${ext}";
|
|
1959
|
+
`;
|
|
1960
|
+
}
|
|
1961
|
+
out += `
|
|
1962
|
+
`;
|
|
1963
|
+
for (const table of tables) {
|
|
1964
|
+
const Type = pascal(table);
|
|
1965
|
+
const edges = graph[table] || {};
|
|
1966
|
+
const edgeEntries = Object.entries(edges);
|
|
1967
|
+
if (edgeEntries.length === 0) {
|
|
1968
|
+
out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> = Select${Type};
|
|
1969
|
+
|
|
1970
|
+
`;
|
|
1971
|
+
continue;
|
|
1972
|
+
}
|
|
1973
|
+
out += `export type ${Type}WithIncludes<TInclude extends ${Type}IncludeSpec> =
|
|
1974
|
+
Select${Type} & {
|
|
1975
|
+
[K in keyof TInclude as TInclude[K] extends false | undefined ? never : K]:`;
|
|
1976
|
+
for (let i = 0;i < edgeEntries.length; i++) {
|
|
1977
|
+
const entry = edgeEntries[i];
|
|
1978
|
+
if (!entry)
|
|
1979
|
+
continue;
|
|
1980
|
+
const [relKey, edge] = entry;
|
|
1981
|
+
const targetType = pascal(edge.target);
|
|
1982
|
+
const isLast = i === edgeEntries.length - 1;
|
|
1983
|
+
out += `
|
|
1984
|
+
K extends '${relKey}' ? `;
|
|
1985
|
+
if (edge.kind === "many") {
|
|
1986
|
+
out += `(
|
|
1987
|
+
TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
|
|
1988
|
+
? Array<${targetType}WithIncludes<U>>
|
|
1989
|
+
: Select${targetType}[]
|
|
1990
|
+
)`;
|
|
1991
|
+
} else {
|
|
1992
|
+
out += `(
|
|
1993
|
+
TInclude[K] extends { include: infer U extends ${targetType}IncludeSpec }
|
|
1994
|
+
? ${targetType}WithIncludes<U>
|
|
1995
|
+
: Select${targetType}
|
|
1996
|
+
)`;
|
|
1997
|
+
}
|
|
1998
|
+
out += ` :${isLast ? `
|
|
1999
|
+
never` : ""}`;
|
|
2000
|
+
}
|
|
2001
|
+
out += `
|
|
2002
|
+
};
|
|
2003
|
+
|
|
2004
|
+
`;
|
|
2005
|
+
}
|
|
2006
|
+
return out;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1941
2009
|
// src/emit-include-builder.ts
|
|
1942
2010
|
function emitIncludeBuilder(graph, maxDepth) {
|
|
1943
2011
|
return `/**
|
|
@@ -2501,6 +2569,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
2501
2569
|
const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
|
|
2502
2570
|
const includeSpecTypes = [table.name, ...Array.from(importedTypes).filter((t) => t !== table.name)];
|
|
2503
2571
|
const includeSpecImport = `import type { ${includeSpecTypes.map((t) => `${pascal(t)}IncludeSpec`).join(", ")} } from "./include-spec${ext}";`;
|
|
2572
|
+
const includeResolverImport = `import type { ${Type}WithIncludes } from "./include-resolver${ext}";`;
|
|
2504
2573
|
const otherTableImports = [];
|
|
2505
2574
|
for (const target of Array.from(importedTypes)) {
|
|
2506
2575
|
if (target !== table.name) {
|
|
@@ -2694,6 +2763,7 @@ import type { Where } from "./where-types${ext}";
|
|
|
2694
2763
|
import type { PaginatedResponse } from "./types/shared${ext}";
|
|
2695
2764
|
${typeImports}
|
|
2696
2765
|
${includeSpecImport}
|
|
2766
|
+
${includeResolverImport}
|
|
2697
2767
|
${otherTableImports.join(`
|
|
2698
2768
|
`)}
|
|
2699
2769
|
|
|
@@ -2884,14 +2954,17 @@ ${hasJsonbColumns ? ` /**
|
|
|
2884
2954
|
* @param params.order - Sort direction(s): "asc" or "desc"
|
|
2885
2955
|
* @param params.limit - Maximum number of records to return (default: 50, max: 1000)
|
|
2886
2956
|
* @param params.offset - Number of records to skip for pagination
|
|
2887
|
-
* @param params.include - Related records to include (
|
|
2888
|
-
* @returns Paginated results with all fields
|
|
2957
|
+
* @param params.include - Related records to include (return type automatically infers included relations)
|
|
2958
|
+
* @returns Paginated results with all fields (and included relations if specified)
|
|
2889
2959
|
* @example
|
|
2890
2960
|
* // With JSONB type override:
|
|
2891
2961
|
* const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
|
|
2962
|
+
* // With automatic include inference:
|
|
2963
|
+
* const users = await client.list({ include: { posts: true } });
|
|
2964
|
+
* // users[0].posts is automatically typed
|
|
2892
2965
|
*/
|
|
2893
|
-
async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
|
|
2894
|
-
include?:
|
|
2966
|
+
async list<TJsonb extends Partial<Select${Type}> = {}, TInclude extends ${Type}IncludeSpec = {}>(params?: {
|
|
2967
|
+
include?: TInclude;
|
|
2895
2968
|
limit?: number;
|
|
2896
2969
|
offset?: number;
|
|
2897
2970
|
where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
|
|
@@ -2903,7 +2976,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
2903
2976
|
};` : ""}
|
|
2904
2977
|
orderBy?: string | string[];
|
|
2905
2978
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
2906
|
-
}): Promise<PaginatedResponse
|
|
2979
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
2907
2980
|
async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
|
|
2908
2981
|
include?: ${Type}IncludeSpec;
|
|
2909
2982
|
select?: string[];
|
|
@@ -2969,11 +3042,11 @@ ${hasJsonbColumns ? ` /**
|
|
|
2969
3042
|
* @param params.order - Sort direction(s): "asc" or "desc"
|
|
2970
3043
|
* @param params.limit - Maximum number of records to return (default: 50, max: 1000)
|
|
2971
3044
|
* @param params.offset - Number of records to skip for pagination
|
|
2972
|
-
* @param params.include - Related records to include (
|
|
2973
|
-
* @returns Paginated results with all fields
|
|
3045
|
+
* @param params.include - Related records to include (return type automatically infers included relations)
|
|
3046
|
+
* @returns Paginated results with all fields (and included relations if specified)
|
|
2974
3047
|
*/
|
|
2975
|
-
async list(params?: {
|
|
2976
|
-
include?:
|
|
3048
|
+
async list<TInclude extends ${Type}IncludeSpec = {}>(params?: {
|
|
3049
|
+
include?: TInclude;
|
|
2977
3050
|
limit?: number;
|
|
2978
3051
|
offset?: number;
|
|
2979
3052
|
where?: Where<Select${Type}>;${hasVectorColumns ? `
|
|
@@ -2985,7 +3058,7 @@ ${hasJsonbColumns ? ` /**
|
|
|
2985
3058
|
};` : ""}
|
|
2986
3059
|
orderBy?: string | string[];
|
|
2987
3060
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
2988
|
-
}): Promise<PaginatedResponse
|
|
3061
|
+
}): Promise<PaginatedResponse<${Type}WithIncludes<TInclude>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
|
|
2989
3062
|
async list(params?: {
|
|
2990
3063
|
include?: ${Type}IncludeSpec;
|
|
2991
3064
|
select?: string[];
|
|
@@ -4614,7 +4687,7 @@ export * from "./include-spec${ext}";
|
|
|
4614
4687
|
}
|
|
4615
4688
|
|
|
4616
4689
|
// src/emit-sdk-bundle.ts
|
|
4617
|
-
function emitSdkBundle(clientFiles, clientDir) {
|
|
4690
|
+
function emitSdkBundle(clientFiles, clientDir, version) {
|
|
4618
4691
|
const files = {};
|
|
4619
4692
|
for (const file of clientFiles) {
|
|
4620
4693
|
const normalizedClientDir = clientDir.replace(/\\/g, "/");
|
|
@@ -4624,7 +4697,6 @@ function emitSdkBundle(clientFiles, clientDir) {
|
|
|
4624
4697
|
files[relativePath] = file.content;
|
|
4625
4698
|
}
|
|
4626
4699
|
}
|
|
4627
|
-
const version = `1.0.0`;
|
|
4628
4700
|
return `/**
|
|
4629
4701
|
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
4630
4702
|
*
|
|
@@ -6098,6 +6170,9 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
|
|
|
6098
6170
|
// src/index.ts
|
|
6099
6171
|
init_emit_sdk_contract();
|
|
6100
6172
|
init_utils();
|
|
6173
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
6174
|
+
var __dirname2 = dirname2(__filename2);
|
|
6175
|
+
var { version: CLI_VERSION } = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
|
|
6101
6176
|
async function generate(configPath) {
|
|
6102
6177
|
if (!existsSync2(configPath)) {
|
|
6103
6178
|
throw new Error(`Config file not found: ${configPath}
|
|
@@ -6110,6 +6185,7 @@ async function generate(configPath) {
|
|
|
6110
6185
|
const rawCfg = module.default || module;
|
|
6111
6186
|
const normalizedAuth = normalizeAuthConfig(rawCfg.auth);
|
|
6112
6187
|
const cfg = { ...rawCfg, auth: normalizedAuth };
|
|
6188
|
+
console.log(`\uD83D\uDCE6 postgresdk v${CLI_VERSION}`);
|
|
6113
6189
|
console.log("\uD83D\uDD0D Introspecting database...");
|
|
6114
6190
|
const model = await introspect(cfg.connectionString, cfg.schema || "public");
|
|
6115
6191
|
console.log("\uD83D\uDD17 Building relationship graph...");
|
|
@@ -6158,6 +6234,8 @@ async function generate(configPath) {
|
|
|
6158
6234
|
const includeSpec = emitIncludeSpec(graph);
|
|
6159
6235
|
files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
|
|
6160
6236
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
|
6237
|
+
const includeResolver = emitIncludeResolver(graph, cfg.useJsExtensions);
|
|
6238
|
+
files.push({ path: join(clientDir, "include-resolver.ts"), content: includeResolver });
|
|
6161
6239
|
files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
|
|
6162
6240
|
files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
|
|
6163
6241
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
@@ -6249,7 +6327,7 @@ async function generate(configPath) {
|
|
|
6249
6327
|
});
|
|
6250
6328
|
files.push({
|
|
6251
6329
|
path: join(serverDir, "sdk-bundle.ts"),
|
|
6252
|
-
content: emitSdkBundle(clientFiles, clientDir)
|
|
6330
|
+
content: emitSdkBundle(clientFiles, clientDir, CLI_VERSION)
|
|
6253
6331
|
});
|
|
6254
6332
|
if (generateTests) {
|
|
6255
6333
|
console.log("\uD83E\uDDEA Generating tests...");
|