postgresdk 0.18.5 → 0.18.7

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.js CHANGED
@@ -473,11 +473,25 @@ var require_config = __commonJS(() => {
473
473
  // src/utils.ts
474
474
  import { mkdir, writeFile, readFile } from "fs/promises";
475
475
  import { dirname } from "path";
476
- async function writeFiles(files) {
476
+ import { existsSync } from "fs";
477
+ async function writeFilesIfChanged(files) {
478
+ let written = 0;
479
+ let unchanged = 0;
480
+ const filesWritten = [];
477
481
  for (const f of files) {
478
482
  await mkdir(dirname(f.path), { recursive: true });
483
+ if (existsSync(f.path)) {
484
+ const existing = await readFile(f.path, "utf-8");
485
+ if (existing === f.content) {
486
+ unchanged++;
487
+ continue;
488
+ }
489
+ }
479
490
  await writeFile(f.path, f.content, "utf-8");
491
+ written++;
492
+ filesWritten.push(f.path);
480
493
  }
494
+ return { written, unchanged, filesWritten };
481
495
  }
482
496
  async function ensureDirs(dirs) {
483
497
  for (const d of dirs)
@@ -1686,107 +1700,6 @@ var init_emit_sdk_contract = __esm(() => {
1686
1700
  init_emit_include_methods();
1687
1701
  });
1688
1702
 
1689
- // src/cache.ts
1690
- import { createHash } from "crypto";
1691
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, appendFile } from "fs/promises";
1692
- import { existsSync, readFileSync } from "fs";
1693
- import { join } from "path";
1694
- function computeSchemaHash(model, config) {
1695
- const payload = {
1696
- version: POSTGRESDK_VERSION,
1697
- schema: model.schema,
1698
- tables: model.tables,
1699
- enums: model.enums,
1700
- config: {
1701
- outDir: config.outDir,
1702
- schema: config.schema,
1703
- softDeleteColumn: config.softDeleteColumn,
1704
- includeMethodsDepth: config.includeMethodsDepth,
1705
- serverFramework: config.serverFramework,
1706
- useJsExtensions: config.useJsExtensions,
1707
- useJsExtensionsClient: config.useJsExtensionsClient,
1708
- numericMode: config.numericMode,
1709
- skipJunctionTables: config.skipJunctionTables,
1710
- apiPathPrefix: config.apiPathPrefix,
1711
- auth: config.auth,
1712
- tests: config.tests
1713
- }
1714
- };
1715
- const json = JSON.stringify(payload, Object.keys(payload).sort());
1716
- return createHash("sha256").update(json).digest("hex");
1717
- }
1718
- function getCacheDir(baseDir = process.cwd()) {
1719
- return join(baseDir, ".postgresdk");
1720
- }
1721
- async function ensureGitignore(baseDir = process.cwd()) {
1722
- const gitignorePath = join(baseDir, ".gitignore");
1723
- if (!existsSync(gitignorePath)) {
1724
- return;
1725
- }
1726
- try {
1727
- const content = await readFile2(gitignorePath, "utf-8");
1728
- if (content.includes(".postgresdk")) {
1729
- return;
1730
- }
1731
- const entry = `
1732
- # PostgreSDK cache and history
1733
- .postgresdk/
1734
- `;
1735
- await appendFile(gitignorePath, entry);
1736
- console.log("✓ Added .postgresdk/ to .gitignore");
1737
- } catch {}
1738
- }
1739
- async function readCache(baseDir) {
1740
- const cachePath = join(getCacheDir(baseDir), "cache.json");
1741
- if (!existsSync(cachePath)) {
1742
- return null;
1743
- }
1744
- try {
1745
- const content = await readFile2(cachePath, "utf-8");
1746
- return JSON.parse(content);
1747
- } catch {
1748
- return null;
1749
- }
1750
- }
1751
- async function writeCache(data, baseDir) {
1752
- const cacheDir = getCacheDir(baseDir);
1753
- const isNewCache = !existsSync(cacheDir);
1754
- await mkdir2(cacheDir, { recursive: true });
1755
- if (isNewCache) {
1756
- await ensureGitignore(baseDir);
1757
- }
1758
- const cachePath = join(cacheDir, "cache.json");
1759
- await writeFile2(cachePath, JSON.stringify(data, null, 2), "utf-8");
1760
- }
1761
- async function appendToHistory(entry, baseDir) {
1762
- const cacheDir = getCacheDir(baseDir);
1763
- const isNewCache = !existsSync(cacheDir);
1764
- await mkdir2(cacheDir, { recursive: true });
1765
- if (isNewCache) {
1766
- await ensureGitignore(baseDir);
1767
- }
1768
- const historyPath = join(cacheDir, "history.md");
1769
- const timestamp = new Date().toISOString().replace("T", " ").substring(0, 19);
1770
- const formattedEntry = `## ${timestamp} - ${entry}
1771
-
1772
- `;
1773
- try {
1774
- const existing = existsSync(historyPath) ? await readFile2(historyPath, "utf-8") : `# PostgreSDK Generation History
1775
-
1776
- `;
1777
- await writeFile2(historyPath, existing + formattedEntry, "utf-8");
1778
- } catch {
1779
- await writeFile2(historyPath, `# PostgreSDK Generation History
1780
-
1781
- ${formattedEntry}`, "utf-8");
1782
- }
1783
- }
1784
- var __dirname = "/workspace/src", packageJson, POSTGRESDK_VERSION;
1785
- var init_cache = __esm(() => {
1786
- packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
1787
- POSTGRESDK_VERSION = packageJson.version;
1788
- });
1789
-
1790
1703
  // src/cli-config-utils.ts
1791
1704
  function extractConfigFields(configContent) {
1792
1705
  const fields = [];
@@ -2206,7 +2119,7 @@ var exports_cli_init = {};
2206
2119
  __export(exports_cli_init, {
2207
2120
  initCommand: () => initCommand
2208
2121
  });
2209
- import { existsSync as existsSync3, writeFileSync, readFileSync as readFileSync2, copyFileSync } from "fs";
2122
+ import { existsSync as existsSync3, writeFileSync, readFileSync, copyFileSync } from "fs";
2210
2123
  import { resolve } from "path";
2211
2124
  import prompts from "prompts";
2212
2125
  async function initCommand(args) {
@@ -2224,7 +2137,7 @@ async function initCommand(args) {
2224
2137
  }
2225
2138
  console.log(`⚠️ Found existing postgresdk.config.ts
2226
2139
  `);
2227
- const existingContent = readFileSync2(configPath, "utf-8");
2140
+ const existingContent = readFileSync(configPath, "utf-8");
2228
2141
  const existingFields = extractConfigFields(existingContent);
2229
2142
  console.log("\uD83D\uDCCB Existing configuration detected:");
2230
2143
  existingFields.forEach((field) => {
@@ -2615,8 +2528,8 @@ var exports_cli_pull = {};
2615
2528
  __export(exports_cli_pull, {
2616
2529
  pullCommand: () => pullCommand
2617
2530
  });
2618
- import { writeFile as writeFile3, mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
2619
- import { join as join3, dirname as dirname3, resolve as resolve2 } from "path";
2531
+ import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
2532
+ import { join as join2, dirname as dirname2, resolve as resolve2 } from "path";
2620
2533
  import { existsSync as existsSync4 } from "fs";
2621
2534
  import { pathToFileURL as pathToFileURL2 } from "url";
2622
2535
  async function pullCommand(args) {
@@ -2702,72 +2615,54 @@ Options:`);
2702
2615
  let filesUnchanged = 0;
2703
2616
  const changedFiles = [];
2704
2617
  for (const [path, content] of Object.entries(sdk.files)) {
2705
- const fullPath = join3(config.output, path);
2706
- await mkdir3(dirname3(fullPath), { recursive: true });
2618
+ const fullPath = join2(config.output, path);
2619
+ await mkdir2(dirname2(fullPath), { recursive: true });
2707
2620
  let shouldWrite = true;
2708
2621
  if (existsSync4(fullPath)) {
2709
- const existing = await readFile3(fullPath, "utf-8");
2622
+ const existing = await readFile2(fullPath, "utf-8");
2710
2623
  if (existing === content) {
2711
2624
  shouldWrite = false;
2712
2625
  filesUnchanged++;
2713
2626
  }
2714
2627
  }
2715
2628
  if (shouldWrite) {
2716
- await writeFile3(fullPath, content, "utf-8");
2629
+ await writeFile2(fullPath, content, "utf-8");
2717
2630
  filesWritten++;
2718
2631
  changedFiles.push(path);
2719
2632
  console.log(` ✓ ${path}`);
2720
2633
  }
2721
2634
  }
2722
- const metadataPath = join3(config.output, ".postgresdk.json");
2635
+ const metadataPath = join2(config.output, ".postgresdk.json");
2723
2636
  const metadata = {
2724
2637
  version: sdk.version,
2725
2638
  pulledFrom: config.from
2726
2639
  };
2727
2640
  let metadataChanged = true;
2728
2641
  if (existsSync4(metadataPath)) {
2729
- const existing = await readFile3(metadataPath, "utf-8");
2642
+ const existing = await readFile2(metadataPath, "utf-8");
2730
2643
  if (existing === JSON.stringify(metadata, null, 2)) {
2731
2644
  metadataChanged = false;
2732
2645
  }
2733
2646
  }
2734
2647
  if (metadataChanged) {
2735
- await writeFile3(metadataPath, JSON.stringify(metadata, null, 2));
2648
+ await writeFile2(metadataPath, JSON.stringify(metadata, null, 2));
2736
2649
  }
2737
2650
  if (filesWritten === 0 && !metadataChanged) {
2738
2651
  console.log(`✅ SDK up-to-date (${filesUnchanged} files unchanged)`);
2739
- await appendToHistory(`Pull
2740
- ✅ SDK up-to-date
2741
- - Pulled from: ${config.from}
2742
- - Files checked: ${filesUnchanged}`);
2743
2652
  } else {
2744
2653
  console.log(`✅ SDK pulled successfully to ${config.output}`);
2745
2654
  console.log(` Updated: ${filesWritten} files, Unchanged: ${filesUnchanged} files`);
2746
- let logEntry = `Pull
2747
- ✅ Updated ${filesWritten} files from ${config.from}
2748
- - SDK version: ${sdk.version}
2749
- - Files unchanged: ${filesUnchanged}`;
2750
- if (changedFiles.length > 0 && changedFiles.length <= 10) {
2751
- logEntry += `
2752
- - Modified: ${changedFiles.join(", ")}`;
2753
- } else if (changedFiles.length > 10) {
2754
- logEntry += `
2755
- - Modified: ${changedFiles.slice(0, 10).join(", ")} and ${changedFiles.length - 10} more...`;
2756
- }
2757
- await appendToHistory(logEntry);
2758
2655
  }
2759
2656
  } catch (err) {
2760
2657
  console.error(`❌ Pull failed:`, err);
2761
2658
  process.exit(1);
2762
2659
  }
2763
2660
  }
2764
- var init_cli_pull = __esm(() => {
2765
- init_cache();
2766
- });
2661
+ var init_cli_pull = () => {};
2767
2662
 
2768
2663
  // src/index.ts
2769
2664
  var import_config = __toESM(require_config(), 1);
2770
- import { join as join2, relative } from "node:path";
2665
+ import { join, relative } from "node:path";
2771
2666
  import { pathToFileURL } from "node:url";
2772
2667
  import { existsSync as existsSync2 } from "node:fs";
2773
2668
 
@@ -3565,6 +3460,7 @@ function emitClient(table, graph, opts, model) {
3565
3460
  }
3566
3461
  }
3567
3462
  const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
3463
+ const includeSpecImport = `import type { ${Type}IncludeSpec } from "./include-spec${ext}";`;
3568
3464
  const otherTableImports = [];
3569
3465
  for (const target of Array.from(importedTypes)) {
3570
3466
  if (target !== table.name) {
@@ -3647,7 +3543,7 @@ function emitClient(table, graph, opts, model) {
3647
3543
  order?: "asc" | "desc";
3648
3544
  limit?: number;
3649
3545
  offset?: number;
3650
- include?: any;
3546
+ include?: ${Type}IncludeSpec;
3651
3547
  };
3652
3548
  }`;
3653
3549
  }
@@ -3755,6 +3651,7 @@ import { BaseClient } from "./base-client${ext}";
3755
3651
  import type { Where } from "./where-types${ext}";
3756
3652
  import type { PaginatedResponse } from "./types/shared${ext}";
3757
3653
  ${typeImports}
3654
+ ${includeSpecImport}
3758
3655
  ${otherTableImports.join(`
3759
3656
  `)}
3760
3657
 
@@ -3904,7 +3801,7 @@ ${hasJsonbColumns ? ` /**
3904
3801
  */
3905
3802
  async list<TJsonb extends Partial<Select${Type}> = {}>(params: {
3906
3803
  select: string[];
3907
- include?: any;
3804
+ include?: ${Type}IncludeSpec;
3908
3805
  limit?: number;
3909
3806
  offset?: number;
3910
3807
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -3924,7 +3821,7 @@ ${hasJsonbColumns ? ` /**
3924
3821
  */
3925
3822
  async list<TJsonb extends Partial<Select${Type}> = {}>(params: {
3926
3823
  exclude: string[];
3927
- include?: any;
3824
+ include?: ${Type}IncludeSpec;
3928
3825
  limit?: number;
3929
3826
  offset?: number;
3930
3827
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -3952,7 +3849,7 @@ ${hasJsonbColumns ? ` /**
3952
3849
  * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
3953
3850
  */
3954
3851
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3955
- include?: any;
3852
+ include?: ${Type}IncludeSpec;
3956
3853
  limit?: number;
3957
3854
  offset?: number;
3958
3855
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -3966,7 +3863,7 @@ ${hasJsonbColumns ? ` /**
3966
3863
  order?: "asc" | "desc" | ("asc" | "desc")[];
3967
3864
  }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3968
3865
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3969
- include?: any;
3866
+ include?: ${Type}IncludeSpec;
3970
3867
  select?: string[];
3971
3868
  exclude?: string[];
3972
3869
  limit?: number;
@@ -3989,7 +3886,7 @@ ${hasJsonbColumns ? ` /**
3989
3886
  */
3990
3887
  async list(params: {
3991
3888
  select: string[];
3992
- include?: any;
3889
+ include?: ${Type}IncludeSpec;
3993
3890
  limit?: number;
3994
3891
  offset?: number;
3995
3892
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -4009,7 +3906,7 @@ ${hasJsonbColumns ? ` /**
4009
3906
  */
4010
3907
  async list(params: {
4011
3908
  exclude: string[];
4012
- include?: any;
3909
+ include?: ${Type}IncludeSpec;
4013
3910
  limit?: number;
4014
3911
  offset?: number;
4015
3912
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -4034,7 +3931,7 @@ ${hasJsonbColumns ? ` /**
4034
3931
  * @returns Paginated results with all fields
4035
3932
  */
4036
3933
  async list(params?: {
4037
- include?: any;
3934
+ include?: ${Type}IncludeSpec;
4038
3935
  limit?: number;
4039
3936
  offset?: number;
4040
3937
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -4048,7 +3945,7 @@ ${hasJsonbColumns ? ` /**
4048
3945
  order?: "asc" | "desc" | ("asc" | "desc")[];
4049
3946
  }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4050
3947
  async list(params?: {
4051
- include?: any;
3948
+ include?: ${Type}IncludeSpec;
4052
3949
  select?: string[];
4053
3950
  exclude?: string[];
4054
3951
  limit?: number;
@@ -7159,7 +7056,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
7159
7056
  // src/index.ts
7160
7057
  init_emit_sdk_contract();
7161
7058
  init_utils();
7162
- init_cache();
7163
7059
  async function generate(configPath) {
7164
7060
  if (!existsSync2(configPath)) {
7165
7061
  throw new Error(`Config file not found: ${configPath}
@@ -7174,58 +7070,43 @@ async function generate(configPath) {
7174
7070
  const cfg = { ...rawCfg, auth: normalizedAuth };
7175
7071
  console.log("\uD83D\uDD0D Introspecting database...");
7176
7072
  const model = await introspect(cfg.connectionString, cfg.schema || "public");
7177
- const schemaHash = computeSchemaHash(model, cfg);
7178
- const cache = await readCache();
7179
- let serverDir;
7180
- if (typeof cfg.outDir === "string") {
7181
- serverDir = cfg.outDir;
7182
- } else if (cfg.outDir && typeof cfg.outDir === "object") {
7183
- serverDir = cfg.outDir.server;
7184
- } else {
7185
- serverDir = "./api/server";
7186
- }
7187
- const currentOutDir = typeof cfg.outDir === "string" ? cfg.outDir : `${serverDir}`;
7188
- const cachedOutDir = typeof cache?.config.outDir === "string" ? cache.config.outDir : cache?.config.outDir?.server;
7189
- if (cache && cache.schemaHash === schemaHash && existsSync2(serverDir) && currentOutDir === cachedOutDir) {
7190
- console.log("✅ Schema unchanged, skipping generation");
7191
- await appendToHistory(`Generate
7192
- ✅ Schema unchanged, skipped generation
7193
- - Schema hash: ${schemaHash.substring(0, 12)}...`);
7194
- return;
7195
- }
7196
7073
  console.log("\uD83D\uDD17 Building relationship graph...");
7197
7074
  const graph = buildGraph(model);
7075
+ let serverDir;
7198
7076
  let originalClientDir;
7199
7077
  if (typeof cfg.outDir === "string") {
7078
+ serverDir = cfg.outDir;
7200
7079
  originalClientDir = cfg.outDir;
7201
7080
  } else if (cfg.outDir && typeof cfg.outDir === "object") {
7081
+ serverDir = cfg.outDir.server;
7202
7082
  originalClientDir = cfg.outDir.client;
7203
7083
  } else {
7084
+ serverDir = "./api/server";
7204
7085
  originalClientDir = "./api/client";
7205
7086
  }
7206
7087
  const sameDirectory = serverDir === originalClientDir;
7207
7088
  let clientDir = originalClientDir;
7208
7089
  if (sameDirectory) {
7209
- clientDir = join2(originalClientDir, "sdk");
7090
+ clientDir = join(originalClientDir, "sdk");
7210
7091
  }
7211
7092
  const serverFramework = cfg.serverFramework || "hono";
7212
7093
  const generateTests = cfg.tests?.generate ?? false;
7213
7094
  const originalTestDir = cfg.tests?.output || "./api/tests";
7214
7095
  let testDir = originalTestDir;
7215
7096
  if (generateTests && (originalTestDir === serverDir || originalTestDir === originalClientDir)) {
7216
- testDir = join2(originalTestDir, "tests");
7097
+ testDir = join(originalTestDir, "tests");
7217
7098
  }
7218
7099
  const testFramework = cfg.tests?.framework || "vitest";
7219
7100
  console.log("\uD83D\uDCC1 Creating directories...");
7220
7101
  const dirs = [
7221
7102
  serverDir,
7222
- join2(serverDir, "types"),
7223
- join2(serverDir, "zod"),
7224
- join2(serverDir, "routes"),
7103
+ join(serverDir, "types"),
7104
+ join(serverDir, "zod"),
7105
+ join(serverDir, "routes"),
7225
7106
  clientDir,
7226
- join2(clientDir, "types"),
7227
- join2(clientDir, "zod"),
7228
- join2(clientDir, "params")
7107
+ join(clientDir, "types"),
7108
+ join(clientDir, "zod"),
7109
+ join(clientDir, "params")
7229
7110
  ];
7230
7111
  if (generateTests) {
7231
7112
  dirs.push(testDir);
@@ -7233,26 +7114,26 @@ async function generate(configPath) {
7233
7114
  await ensureDirs(dirs);
7234
7115
  const files = [];
7235
7116
  const includeSpec = emitIncludeSpec(graph);
7236
- files.push({ path: join2(serverDir, "include-spec.ts"), content: includeSpec });
7237
- files.push({ path: join2(clientDir, "include-spec.ts"), content: includeSpec });
7238
- files.push({ path: join2(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
7239
- files.push({ path: join2(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
7240
- files.push({ path: join2(clientDir, "base-client.ts"), content: emitBaseClient() });
7241
- files.push({ path: join2(clientDir, "where-types.ts"), content: emitWhereTypes() });
7117
+ files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
7118
+ files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
7119
+ files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
7120
+ files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
7121
+ files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
7122
+ files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
7242
7123
  files.push({
7243
- path: join2(serverDir, "include-builder.ts"),
7124
+ path: join(serverDir, "include-builder.ts"),
7244
7125
  content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
7245
7126
  });
7246
7127
  files.push({
7247
- path: join2(serverDir, "include-loader.ts"),
7128
+ path: join(serverDir, "include-loader.ts"),
7248
7129
  content: emitIncludeLoader(graph, model, cfg.includeMethodsDepth || 2, cfg.useJsExtensions)
7249
7130
  });
7250
- files.push({ path: join2(serverDir, "logger.ts"), content: emitLogger() });
7131
+ files.push({ path: join(serverDir, "logger.ts"), content: emitLogger() });
7251
7132
  if (getAuthStrategy(normalizedAuth) !== "none") {
7252
- files.push({ path: join2(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
7133
+ files.push({ path: join(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
7253
7134
  }
7254
7135
  files.push({
7255
- path: join2(serverDir, "core", "operations.ts"),
7136
+ path: join(serverDir, "core", "operations.ts"),
7256
7137
  content: emitCoreOperations()
7257
7138
  });
7258
7139
  if (process.env.SDK_DEBUG) {
@@ -7261,13 +7142,13 @@ async function generate(configPath) {
7261
7142
  for (const table of Object.values(model.tables)) {
7262
7143
  const numericMode = cfg.numericMode ?? "auto";
7263
7144
  const typesSrc = emitTypes(table, { numericMode }, model.enums);
7264
- files.push({ path: join2(serverDir, "types", `${table.name}.ts`), content: typesSrc });
7265
- files.push({ path: join2(clientDir, "types", `${table.name}.ts`), content: typesSrc });
7145
+ files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
7146
+ files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
7266
7147
  const zodSrc = emitZod(table, { numericMode }, model.enums);
7267
- files.push({ path: join2(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
7268
- files.push({ path: join2(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
7148
+ files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
7149
+ files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
7269
7150
  const paramsZodSrc = emitParamsZod(table, graph);
7270
- files.push({ path: join2(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
7151
+ files.push({ path: join(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
7271
7152
  let routeContent;
7272
7153
  if (serverFramework === "hono") {
7273
7154
  routeContent = emitHonoRoutes(table, graph, {
@@ -7281,11 +7162,11 @@ async function generate(configPath) {
7281
7162
  throw new Error(`Framework "${serverFramework}" is not yet supported. Currently only "hono" is available.`);
7282
7163
  }
7283
7164
  files.push({
7284
- path: join2(serverDir, "routes", `${table.name}.ts`),
7165
+ path: join(serverDir, "routes", `${table.name}.ts`),
7285
7166
  content: routeContent
7286
7167
  });
7287
7168
  files.push({
7288
- path: join2(clientDir, `${table.name}.ts`),
7169
+ path: join(clientDir, `${table.name}.ts`),
7289
7170
  content: emitClient(table, graph, {
7290
7171
  useJsExtensions: cfg.useJsExtensionsClient,
7291
7172
  includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
@@ -7294,12 +7175,12 @@ async function generate(configPath) {
7294
7175
  });
7295
7176
  }
7296
7177
  files.push({
7297
- path: join2(clientDir, "index.ts"),
7178
+ path: join(clientDir, "index.ts"),
7298
7179
  content: emitClientIndex(Object.values(model.tables), cfg.useJsExtensionsClient)
7299
7180
  });
7300
7181
  if (serverFramework === "hono") {
7301
7182
  files.push({
7302
- path: join2(serverDir, "router.ts"),
7183
+ path: join(serverDir, "router.ts"),
7303
7184
  content: emitHonoRouter(Object.values(model.tables), getAuthStrategy(normalizedAuth) !== "none", cfg.useJsExtensions, cfg.pullToken)
7304
7185
  });
7305
7186
  }
@@ -7309,73 +7190,64 @@ async function generate(configPath) {
7309
7190
  }
7310
7191
  const contract = generateUnifiedContract2(model, cfg, graph);
7311
7192
  files.push({
7312
- path: join2(serverDir, "CONTRACT.md"),
7193
+ path: join(serverDir, "CONTRACT.md"),
7313
7194
  content: generateUnifiedContractMarkdown2(contract)
7314
7195
  });
7315
7196
  files.push({
7316
- path: join2(clientDir, "CONTRACT.md"),
7197
+ path: join(clientDir, "CONTRACT.md"),
7317
7198
  content: generateUnifiedContractMarkdown2(contract)
7318
7199
  });
7319
7200
  const contractCode = emitUnifiedContract(model, cfg, graph);
7320
7201
  files.push({
7321
- path: join2(serverDir, "contract.ts"),
7202
+ path: join(serverDir, "contract.ts"),
7322
7203
  content: contractCode
7323
7204
  });
7324
7205
  const clientFiles = files.filter((f) => {
7325
7206
  return f.path.includes(clientDir);
7326
7207
  });
7327
7208
  files.push({
7328
- path: join2(serverDir, "sdk-bundle.ts"),
7209
+ path: join(serverDir, "sdk-bundle.ts"),
7329
7210
  content: emitSdkBundle(clientFiles, clientDir)
7330
7211
  });
7331
7212
  if (generateTests) {
7332
7213
  console.log("\uD83E\uDDEA Generating tests...");
7333
7214
  const relativeClientPath = relative(testDir, clientDir);
7334
7215
  files.push({
7335
- path: join2(testDir, "setup.ts"),
7216
+ path: join(testDir, "setup.ts"),
7336
7217
  content: emitTestSetup(relativeClientPath, testFramework)
7337
7218
  });
7338
7219
  files.push({
7339
- path: join2(testDir, "docker-compose.yml"),
7220
+ path: join(testDir, "docker-compose.yml"),
7340
7221
  content: emitDockerCompose()
7341
7222
  });
7342
7223
  files.push({
7343
- path: join2(testDir, "run-tests.sh"),
7224
+ path: join(testDir, "run-tests.sh"),
7344
7225
  content: emitTestScript(testFramework, testDir)
7345
7226
  });
7346
7227
  files.push({
7347
- path: join2(testDir, ".gitignore"),
7228
+ path: join(testDir, ".gitignore"),
7348
7229
  content: emitTestGitignore()
7349
7230
  });
7350
7231
  if (testFramework === "vitest") {
7351
7232
  files.push({
7352
- path: join2(testDir, "vitest.config.ts"),
7233
+ path: join(testDir, "vitest.config.ts"),
7353
7234
  content: emitVitestConfig()
7354
7235
  });
7355
7236
  }
7356
7237
  for (const table of Object.values(model.tables)) {
7357
7238
  files.push({
7358
- path: join2(testDir, `${table.name}.test.ts`),
7239
+ path: join(testDir, `${table.name}.test.ts`),
7359
7240
  content: emitTableTest(table, model, relativeClientPath, testFramework)
7360
7241
  });
7361
7242
  }
7362
7243
  }
7363
7244
  console.log("✍️ Writing files...");
7364
- await writeFiles(files);
7365
- const outDirStr = typeof cfg.outDir === "string" ? cfg.outDir : `${cfg.outDir?.server || "./api/server"}`;
7366
- await writeCache({
7367
- schemaHash,
7368
- lastRun: new Date().toISOString(),
7369
- filesGenerated: files.length,
7370
- config: {
7371
- outDir: cfg.outDir || "./api",
7372
- schema: cfg.schema || "public"
7373
- }
7374
- });
7375
- await appendToHistory(`Generate
7376
- ✅ Generated ${files.length} files
7377
- - Schema hash: ${schemaHash.substring(0, 12)}...`);
7378
- console.log(`✅ Generated ${files.length} files`);
7245
+ const writeResult = await writeFilesIfChanged(files);
7246
+ if (writeResult.written === 0) {
7247
+ console.log(`✅ All ${writeResult.unchanged} files up-to-date (no changes)`);
7248
+ } else {
7249
+ console.log(`✅ Updated ${writeResult.written} files, ${writeResult.unchanged} unchanged`);
7250
+ }
7379
7251
  console.log(` Server: ${serverDir}`);
7380
7252
  console.log(` Client: ${sameDirectory ? clientDir + " (in sdk subdir due to same output dir)" : clientDir}`);
7381
7253
  if (generateTests) {
@@ -7411,13 +7283,13 @@ async function generate(configPath) {
7411
7283
  // src/cli.ts
7412
7284
  var import_config2 = __toESM(require_config(), 1);
7413
7285
  import { resolve as resolve3 } from "node:path";
7414
- import { readFileSync as readFileSync3 } from "node:fs";
7286
+ import { readFileSync as readFileSync2 } from "node:fs";
7415
7287
  import { fileURLToPath } from "node:url";
7416
- import { dirname as dirname4, join as join4 } from "node:path";
7288
+ import { dirname as dirname3, join as join3 } from "node:path";
7417
7289
  var __filename2 = fileURLToPath(import.meta.url);
7418
- var __dirname2 = dirname4(__filename2);
7419
- var packageJson2 = JSON.parse(readFileSync3(join4(__dirname2, "../package.json"), "utf-8"));
7420
- var VERSION = packageJson2.version;
7290
+ var __dirname2 = dirname3(__filename2);
7291
+ var packageJson = JSON.parse(readFileSync2(join3(__dirname2, "../package.json"), "utf-8"));
7292
+ var VERSION = packageJson.version;
7421
7293
  var args = process.argv.slice(2);
7422
7294
  var command = args[0];
7423
7295
  if (args.includes("--version") || args.includes("-v") || command === "version") {
package/dist/index.js CHANGED
@@ -472,11 +472,25 @@ var require_config = __commonJS(() => {
472
472
  // src/utils.ts
473
473
  import { mkdir, writeFile, readFile } from "fs/promises";
474
474
  import { dirname } from "path";
475
- async function writeFiles(files) {
475
+ import { existsSync } from "fs";
476
+ async function writeFilesIfChanged(files) {
477
+ let written = 0;
478
+ let unchanged = 0;
479
+ const filesWritten = [];
476
480
  for (const f of files) {
477
481
  await mkdir(dirname(f.path), { recursive: true });
482
+ if (existsSync(f.path)) {
483
+ const existing = await readFile(f.path, "utf-8");
484
+ if (existing === f.content) {
485
+ unchanged++;
486
+ continue;
487
+ }
488
+ }
478
489
  await writeFile(f.path, f.content, "utf-8");
490
+ written++;
491
+ filesWritten.push(f.path);
479
492
  }
493
+ return { written, unchanged, filesWritten };
480
494
  }
481
495
  async function ensureDirs(dirs) {
482
496
  for (const d of dirs)
@@ -1685,110 +1699,9 @@ var init_emit_sdk_contract = __esm(() => {
1685
1699
  init_emit_include_methods();
1686
1700
  });
1687
1701
 
1688
- // src/cache.ts
1689
- import { createHash } from "crypto";
1690
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, appendFile } from "fs/promises";
1691
- import { existsSync, readFileSync } from "fs";
1692
- import { join } from "path";
1693
- function computeSchemaHash(model, config) {
1694
- const payload = {
1695
- version: POSTGRESDK_VERSION,
1696
- schema: model.schema,
1697
- tables: model.tables,
1698
- enums: model.enums,
1699
- config: {
1700
- outDir: config.outDir,
1701
- schema: config.schema,
1702
- softDeleteColumn: config.softDeleteColumn,
1703
- includeMethodsDepth: config.includeMethodsDepth,
1704
- serverFramework: config.serverFramework,
1705
- useJsExtensions: config.useJsExtensions,
1706
- useJsExtensionsClient: config.useJsExtensionsClient,
1707
- numericMode: config.numericMode,
1708
- skipJunctionTables: config.skipJunctionTables,
1709
- apiPathPrefix: config.apiPathPrefix,
1710
- auth: config.auth,
1711
- tests: config.tests
1712
- }
1713
- };
1714
- const json = JSON.stringify(payload, Object.keys(payload).sort());
1715
- return createHash("sha256").update(json).digest("hex");
1716
- }
1717
- function getCacheDir(baseDir = process.cwd()) {
1718
- return join(baseDir, ".postgresdk");
1719
- }
1720
- async function ensureGitignore(baseDir = process.cwd()) {
1721
- const gitignorePath = join(baseDir, ".gitignore");
1722
- if (!existsSync(gitignorePath)) {
1723
- return;
1724
- }
1725
- try {
1726
- const content = await readFile2(gitignorePath, "utf-8");
1727
- if (content.includes(".postgresdk")) {
1728
- return;
1729
- }
1730
- const entry = `
1731
- # PostgreSDK cache and history
1732
- .postgresdk/
1733
- `;
1734
- await appendFile(gitignorePath, entry);
1735
- console.log("✓ Added .postgresdk/ to .gitignore");
1736
- } catch {}
1737
- }
1738
- async function readCache(baseDir) {
1739
- const cachePath = join(getCacheDir(baseDir), "cache.json");
1740
- if (!existsSync(cachePath)) {
1741
- return null;
1742
- }
1743
- try {
1744
- const content = await readFile2(cachePath, "utf-8");
1745
- return JSON.parse(content);
1746
- } catch {
1747
- return null;
1748
- }
1749
- }
1750
- async function writeCache(data, baseDir) {
1751
- const cacheDir = getCacheDir(baseDir);
1752
- const isNewCache = !existsSync(cacheDir);
1753
- await mkdir2(cacheDir, { recursive: true });
1754
- if (isNewCache) {
1755
- await ensureGitignore(baseDir);
1756
- }
1757
- const cachePath = join(cacheDir, "cache.json");
1758
- await writeFile2(cachePath, JSON.stringify(data, null, 2), "utf-8");
1759
- }
1760
- async function appendToHistory(entry, baseDir) {
1761
- const cacheDir = getCacheDir(baseDir);
1762
- const isNewCache = !existsSync(cacheDir);
1763
- await mkdir2(cacheDir, { recursive: true });
1764
- if (isNewCache) {
1765
- await ensureGitignore(baseDir);
1766
- }
1767
- const historyPath = join(cacheDir, "history.md");
1768
- const timestamp = new Date().toISOString().replace("T", " ").substring(0, 19);
1769
- const formattedEntry = `## ${timestamp} - ${entry}
1770
-
1771
- `;
1772
- try {
1773
- const existing = existsSync(historyPath) ? await readFile2(historyPath, "utf-8") : `# PostgreSDK Generation History
1774
-
1775
- `;
1776
- await writeFile2(historyPath, existing + formattedEntry, "utf-8");
1777
- } catch {
1778
- await writeFile2(historyPath, `# PostgreSDK Generation History
1779
-
1780
- ${formattedEntry}`, "utf-8");
1781
- }
1782
- }
1783
- var __dirname = "/workspace/src", packageJson, POSTGRESDK_VERSION;
1784
- var init_cache = __esm(() => {
1785
- packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
1786
- POSTGRESDK_VERSION = packageJson.version;
1787
- });
1788
-
1789
1702
  // src/index.ts
1790
1703
  var import_config = __toESM(require_config(), 1);
1791
- import { join as join2, relative } from "node:path";
1704
+ import { join, relative } from "node:path";
1792
1705
  import { pathToFileURL } from "node:url";
1793
1706
  import { existsSync as existsSync2 } from "node:fs";
1794
1707
 
@@ -2586,6 +2499,7 @@ function emitClient(table, graph, opts, model) {
2586
2499
  }
2587
2500
  }
2588
2501
  const typeImports = `import type { Insert${Type}, Update${Type}, Select${Type} } from "./types/${table.name}${ext}";`;
2502
+ const includeSpecImport = `import type { ${Type}IncludeSpec } from "./include-spec${ext}";`;
2589
2503
  const otherTableImports = [];
2590
2504
  for (const target of Array.from(importedTypes)) {
2591
2505
  if (target !== table.name) {
@@ -2668,7 +2582,7 @@ function emitClient(table, graph, opts, model) {
2668
2582
  order?: "asc" | "desc";
2669
2583
  limit?: number;
2670
2584
  offset?: number;
2671
- include?: any;
2585
+ include?: ${Type}IncludeSpec;
2672
2586
  };
2673
2587
  }`;
2674
2588
  }
@@ -2776,6 +2690,7 @@ import { BaseClient } from "./base-client${ext}";
2776
2690
  import type { Where } from "./where-types${ext}";
2777
2691
  import type { PaginatedResponse } from "./types/shared${ext}";
2778
2692
  ${typeImports}
2693
+ ${includeSpecImport}
2779
2694
  ${otherTableImports.join(`
2780
2695
  `)}
2781
2696
 
@@ -2925,7 +2840,7 @@ ${hasJsonbColumns ? ` /**
2925
2840
  */
2926
2841
  async list<TJsonb extends Partial<Select${Type}> = {}>(params: {
2927
2842
  select: string[];
2928
- include?: any;
2843
+ include?: ${Type}IncludeSpec;
2929
2844
  limit?: number;
2930
2845
  offset?: number;
2931
2846
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -2945,7 +2860,7 @@ ${hasJsonbColumns ? ` /**
2945
2860
  */
2946
2861
  async list<TJsonb extends Partial<Select${Type}> = {}>(params: {
2947
2862
  exclude: string[];
2948
- include?: any;
2863
+ include?: ${Type}IncludeSpec;
2949
2864
  limit?: number;
2950
2865
  offset?: number;
2951
2866
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -2973,7 +2888,7 @@ ${hasJsonbColumns ? ` /**
2973
2888
  * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
2974
2889
  */
2975
2890
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2976
- include?: any;
2891
+ include?: ${Type}IncludeSpec;
2977
2892
  limit?: number;
2978
2893
  offset?: number;
2979
2894
  where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
@@ -2987,7 +2902,7 @@ ${hasJsonbColumns ? ` /**
2987
2902
  order?: "asc" | "desc" | ("asc" | "desc")[];
2988
2903
  }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
2989
2904
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2990
- include?: any;
2905
+ include?: ${Type}IncludeSpec;
2991
2906
  select?: string[];
2992
2907
  exclude?: string[];
2993
2908
  limit?: number;
@@ -3010,7 +2925,7 @@ ${hasJsonbColumns ? ` /**
3010
2925
  */
3011
2926
  async list(params: {
3012
2927
  select: string[];
3013
- include?: any;
2928
+ include?: ${Type}IncludeSpec;
3014
2929
  limit?: number;
3015
2930
  offset?: number;
3016
2931
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -3030,7 +2945,7 @@ ${hasJsonbColumns ? ` /**
3030
2945
  */
3031
2946
  async list(params: {
3032
2947
  exclude: string[];
3033
- include?: any;
2948
+ include?: ${Type}IncludeSpec;
3034
2949
  limit?: number;
3035
2950
  offset?: number;
3036
2951
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -3055,7 +2970,7 @@ ${hasJsonbColumns ? ` /**
3055
2970
  * @returns Paginated results with all fields
3056
2971
  */
3057
2972
  async list(params?: {
3058
- include?: any;
2973
+ include?: ${Type}IncludeSpec;
3059
2974
  limit?: number;
3060
2975
  offset?: number;
3061
2976
  where?: Where<Select${Type}>;${hasVectorColumns ? `
@@ -3069,7 +2984,7 @@ ${hasJsonbColumns ? ` /**
3069
2984
  order?: "asc" | "desc" | ("asc" | "desc")[];
3070
2985
  }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3071
2986
  async list(params?: {
3072
- include?: any;
2987
+ include?: ${Type}IncludeSpec;
3073
2988
  select?: string[];
3074
2989
  exclude?: string[];
3075
2990
  limit?: number;
@@ -6180,7 +6095,6 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
6180
6095
  // src/index.ts
6181
6096
  init_emit_sdk_contract();
6182
6097
  init_utils();
6183
- init_cache();
6184
6098
  async function generate(configPath) {
6185
6099
  if (!existsSync2(configPath)) {
6186
6100
  throw new Error(`Config file not found: ${configPath}
@@ -6195,58 +6109,43 @@ async function generate(configPath) {
6195
6109
  const cfg = { ...rawCfg, auth: normalizedAuth };
6196
6110
  console.log("\uD83D\uDD0D Introspecting database...");
6197
6111
  const model = await introspect(cfg.connectionString, cfg.schema || "public");
6198
- const schemaHash = computeSchemaHash(model, cfg);
6199
- const cache = await readCache();
6200
- let serverDir;
6201
- if (typeof cfg.outDir === "string") {
6202
- serverDir = cfg.outDir;
6203
- } else if (cfg.outDir && typeof cfg.outDir === "object") {
6204
- serverDir = cfg.outDir.server;
6205
- } else {
6206
- serverDir = "./api/server";
6207
- }
6208
- const currentOutDir = typeof cfg.outDir === "string" ? cfg.outDir : `${serverDir}`;
6209
- const cachedOutDir = typeof cache?.config.outDir === "string" ? cache.config.outDir : cache?.config.outDir?.server;
6210
- if (cache && cache.schemaHash === schemaHash && existsSync2(serverDir) && currentOutDir === cachedOutDir) {
6211
- console.log("✅ Schema unchanged, skipping generation");
6212
- await appendToHistory(`Generate
6213
- ✅ Schema unchanged, skipped generation
6214
- - Schema hash: ${schemaHash.substring(0, 12)}...`);
6215
- return;
6216
- }
6217
6112
  console.log("\uD83D\uDD17 Building relationship graph...");
6218
6113
  const graph = buildGraph(model);
6114
+ let serverDir;
6219
6115
  let originalClientDir;
6220
6116
  if (typeof cfg.outDir === "string") {
6117
+ serverDir = cfg.outDir;
6221
6118
  originalClientDir = cfg.outDir;
6222
6119
  } else if (cfg.outDir && typeof cfg.outDir === "object") {
6120
+ serverDir = cfg.outDir.server;
6223
6121
  originalClientDir = cfg.outDir.client;
6224
6122
  } else {
6123
+ serverDir = "./api/server";
6225
6124
  originalClientDir = "./api/client";
6226
6125
  }
6227
6126
  const sameDirectory = serverDir === originalClientDir;
6228
6127
  let clientDir = originalClientDir;
6229
6128
  if (sameDirectory) {
6230
- clientDir = join2(originalClientDir, "sdk");
6129
+ clientDir = join(originalClientDir, "sdk");
6231
6130
  }
6232
6131
  const serverFramework = cfg.serverFramework || "hono";
6233
6132
  const generateTests = cfg.tests?.generate ?? false;
6234
6133
  const originalTestDir = cfg.tests?.output || "./api/tests";
6235
6134
  let testDir = originalTestDir;
6236
6135
  if (generateTests && (originalTestDir === serverDir || originalTestDir === originalClientDir)) {
6237
- testDir = join2(originalTestDir, "tests");
6136
+ testDir = join(originalTestDir, "tests");
6238
6137
  }
6239
6138
  const testFramework = cfg.tests?.framework || "vitest";
6240
6139
  console.log("\uD83D\uDCC1 Creating directories...");
6241
6140
  const dirs = [
6242
6141
  serverDir,
6243
- join2(serverDir, "types"),
6244
- join2(serverDir, "zod"),
6245
- join2(serverDir, "routes"),
6142
+ join(serverDir, "types"),
6143
+ join(serverDir, "zod"),
6144
+ join(serverDir, "routes"),
6246
6145
  clientDir,
6247
- join2(clientDir, "types"),
6248
- join2(clientDir, "zod"),
6249
- join2(clientDir, "params")
6146
+ join(clientDir, "types"),
6147
+ join(clientDir, "zod"),
6148
+ join(clientDir, "params")
6250
6149
  ];
6251
6150
  if (generateTests) {
6252
6151
  dirs.push(testDir);
@@ -6254,26 +6153,26 @@ async function generate(configPath) {
6254
6153
  await ensureDirs(dirs);
6255
6154
  const files = [];
6256
6155
  const includeSpec = emitIncludeSpec(graph);
6257
- files.push({ path: join2(serverDir, "include-spec.ts"), content: includeSpec });
6258
- files.push({ path: join2(clientDir, "include-spec.ts"), content: includeSpec });
6259
- files.push({ path: join2(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
6260
- files.push({ path: join2(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
6261
- files.push({ path: join2(clientDir, "base-client.ts"), content: emitBaseClient() });
6262
- files.push({ path: join2(clientDir, "where-types.ts"), content: emitWhereTypes() });
6156
+ files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
6157
+ files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
6158
+ files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
6159
+ files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
6160
+ files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
6161
+ files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
6263
6162
  files.push({
6264
- path: join2(serverDir, "include-builder.ts"),
6163
+ path: join(serverDir, "include-builder.ts"),
6265
6164
  content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
6266
6165
  });
6267
6166
  files.push({
6268
- path: join2(serverDir, "include-loader.ts"),
6167
+ path: join(serverDir, "include-loader.ts"),
6269
6168
  content: emitIncludeLoader(graph, model, cfg.includeMethodsDepth || 2, cfg.useJsExtensions)
6270
6169
  });
6271
- files.push({ path: join2(serverDir, "logger.ts"), content: emitLogger() });
6170
+ files.push({ path: join(serverDir, "logger.ts"), content: emitLogger() });
6272
6171
  if (getAuthStrategy(normalizedAuth) !== "none") {
6273
- files.push({ path: join2(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
6172
+ files.push({ path: join(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
6274
6173
  }
6275
6174
  files.push({
6276
- path: join2(serverDir, "core", "operations.ts"),
6175
+ path: join(serverDir, "core", "operations.ts"),
6277
6176
  content: emitCoreOperations()
6278
6177
  });
6279
6178
  if (process.env.SDK_DEBUG) {
@@ -6282,13 +6181,13 @@ async function generate(configPath) {
6282
6181
  for (const table of Object.values(model.tables)) {
6283
6182
  const numericMode = cfg.numericMode ?? "auto";
6284
6183
  const typesSrc = emitTypes(table, { numericMode }, model.enums);
6285
- files.push({ path: join2(serverDir, "types", `${table.name}.ts`), content: typesSrc });
6286
- files.push({ path: join2(clientDir, "types", `${table.name}.ts`), content: typesSrc });
6184
+ files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
6185
+ files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
6287
6186
  const zodSrc = emitZod(table, { numericMode }, model.enums);
6288
- files.push({ path: join2(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
6289
- files.push({ path: join2(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
6187
+ files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
6188
+ files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
6290
6189
  const paramsZodSrc = emitParamsZod(table, graph);
6291
- files.push({ path: join2(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
6190
+ files.push({ path: join(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
6292
6191
  let routeContent;
6293
6192
  if (serverFramework === "hono") {
6294
6193
  routeContent = emitHonoRoutes(table, graph, {
@@ -6302,11 +6201,11 @@ async function generate(configPath) {
6302
6201
  throw new Error(`Framework "${serverFramework}" is not yet supported. Currently only "hono" is available.`);
6303
6202
  }
6304
6203
  files.push({
6305
- path: join2(serverDir, "routes", `${table.name}.ts`),
6204
+ path: join(serverDir, "routes", `${table.name}.ts`),
6306
6205
  content: routeContent
6307
6206
  });
6308
6207
  files.push({
6309
- path: join2(clientDir, `${table.name}.ts`),
6208
+ path: join(clientDir, `${table.name}.ts`),
6310
6209
  content: emitClient(table, graph, {
6311
6210
  useJsExtensions: cfg.useJsExtensionsClient,
6312
6211
  includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
@@ -6315,12 +6214,12 @@ async function generate(configPath) {
6315
6214
  });
6316
6215
  }
6317
6216
  files.push({
6318
- path: join2(clientDir, "index.ts"),
6217
+ path: join(clientDir, "index.ts"),
6319
6218
  content: emitClientIndex(Object.values(model.tables), cfg.useJsExtensionsClient)
6320
6219
  });
6321
6220
  if (serverFramework === "hono") {
6322
6221
  files.push({
6323
- path: join2(serverDir, "router.ts"),
6222
+ path: join(serverDir, "router.ts"),
6324
6223
  content: emitHonoRouter(Object.values(model.tables), getAuthStrategy(normalizedAuth) !== "none", cfg.useJsExtensions, cfg.pullToken)
6325
6224
  });
6326
6225
  }
@@ -6330,73 +6229,64 @@ async function generate(configPath) {
6330
6229
  }
6331
6230
  const contract = generateUnifiedContract2(model, cfg, graph);
6332
6231
  files.push({
6333
- path: join2(serverDir, "CONTRACT.md"),
6232
+ path: join(serverDir, "CONTRACT.md"),
6334
6233
  content: generateUnifiedContractMarkdown2(contract)
6335
6234
  });
6336
6235
  files.push({
6337
- path: join2(clientDir, "CONTRACT.md"),
6236
+ path: join(clientDir, "CONTRACT.md"),
6338
6237
  content: generateUnifiedContractMarkdown2(contract)
6339
6238
  });
6340
6239
  const contractCode = emitUnifiedContract(model, cfg, graph);
6341
6240
  files.push({
6342
- path: join2(serverDir, "contract.ts"),
6241
+ path: join(serverDir, "contract.ts"),
6343
6242
  content: contractCode
6344
6243
  });
6345
6244
  const clientFiles = files.filter((f) => {
6346
6245
  return f.path.includes(clientDir);
6347
6246
  });
6348
6247
  files.push({
6349
- path: join2(serverDir, "sdk-bundle.ts"),
6248
+ path: join(serverDir, "sdk-bundle.ts"),
6350
6249
  content: emitSdkBundle(clientFiles, clientDir)
6351
6250
  });
6352
6251
  if (generateTests) {
6353
6252
  console.log("\uD83E\uDDEA Generating tests...");
6354
6253
  const relativeClientPath = relative(testDir, clientDir);
6355
6254
  files.push({
6356
- path: join2(testDir, "setup.ts"),
6255
+ path: join(testDir, "setup.ts"),
6357
6256
  content: emitTestSetup(relativeClientPath, testFramework)
6358
6257
  });
6359
6258
  files.push({
6360
- path: join2(testDir, "docker-compose.yml"),
6259
+ path: join(testDir, "docker-compose.yml"),
6361
6260
  content: emitDockerCompose()
6362
6261
  });
6363
6262
  files.push({
6364
- path: join2(testDir, "run-tests.sh"),
6263
+ path: join(testDir, "run-tests.sh"),
6365
6264
  content: emitTestScript(testFramework, testDir)
6366
6265
  });
6367
6266
  files.push({
6368
- path: join2(testDir, ".gitignore"),
6267
+ path: join(testDir, ".gitignore"),
6369
6268
  content: emitTestGitignore()
6370
6269
  });
6371
6270
  if (testFramework === "vitest") {
6372
6271
  files.push({
6373
- path: join2(testDir, "vitest.config.ts"),
6272
+ path: join(testDir, "vitest.config.ts"),
6374
6273
  content: emitVitestConfig()
6375
6274
  });
6376
6275
  }
6377
6276
  for (const table of Object.values(model.tables)) {
6378
6277
  files.push({
6379
- path: join2(testDir, `${table.name}.test.ts`),
6278
+ path: join(testDir, `${table.name}.test.ts`),
6380
6279
  content: emitTableTest(table, model, relativeClientPath, testFramework)
6381
6280
  });
6382
6281
  }
6383
6282
  }
6384
6283
  console.log("✍️ Writing files...");
6385
- await writeFiles(files);
6386
- const outDirStr = typeof cfg.outDir === "string" ? cfg.outDir : `${cfg.outDir?.server || "./api/server"}`;
6387
- await writeCache({
6388
- schemaHash,
6389
- lastRun: new Date().toISOString(),
6390
- filesGenerated: files.length,
6391
- config: {
6392
- outDir: cfg.outDir || "./api",
6393
- schema: cfg.schema || "public"
6394
- }
6395
- });
6396
- await appendToHistory(`Generate
6397
- ✅ Generated ${files.length} files
6398
- - Schema hash: ${schemaHash.substring(0, 12)}...`);
6399
- console.log(`✅ Generated ${files.length} files`);
6284
+ const writeResult = await writeFilesIfChanged(files);
6285
+ if (writeResult.written === 0) {
6286
+ console.log(`✅ All ${writeResult.unchanged} files up-to-date (no changes)`);
6287
+ } else {
6288
+ console.log(`✅ Updated ${writeResult.written} files, ${writeResult.unchanged} unchanged`);
6289
+ }
6400
6290
  console.log(` Server: ${serverDir}`);
6401
6291
  console.log(` Client: ${sameDirectory ? clientDir + " (in sdk subdir due to same output dir)" : clientDir}`);
6402
6292
  if (generateTests) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.5",
3
+ "version": "0.18.7",
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,8 @@
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 && bun test test/test-numeric-mode-integration.test.ts",
25
+ "test": "bun test:write-files && 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
+ "test:write-files": "bun test/test-write-files-if-changed.ts",
26
27
  "test:init": "bun test/test-init.ts",
27
28
  "test:gen": "bun test/test-gen.ts",
28
29
  "test:gen-with-tests": "bun test/test-gen-with-tests.ts",
package/dist/cache.d.ts DELETED
@@ -1,35 +0,0 @@
1
- import type { Model } from "./introspect";
2
- import type { Config } from "./types";
3
- export interface CacheData {
4
- schemaHash: string;
5
- lastRun: string;
6
- filesGenerated: number;
7
- config: {
8
- outDir: string | {
9
- server: string;
10
- client: string;
11
- };
12
- schema: string;
13
- };
14
- }
15
- /**
16
- * Compute a deterministic hash of the schema and config
17
- * Includes postgresdk version to trigger regeneration on upgrades
18
- */
19
- export declare function computeSchemaHash(model: Model, config: Config): string;
20
- /**
21
- * Get the cache directory path
22
- */
23
- export declare function getCacheDir(baseDir?: string): string;
24
- /**
25
- * Read cache data if it exists
26
- */
27
- export declare function readCache(baseDir?: string): Promise<CacheData | null>;
28
- /**
29
- * Write cache data
30
- */
31
- export declare function writeCache(data: CacheData, baseDir?: string): Promise<void>;
32
- /**
33
- * Append an entry to the history log
34
- */
35
- export declare function appendToHistory(entry: string, baseDir?: string): Promise<void>;