postgresdk 0.18.17 → 0.18.18

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
@@ -489,8 +489,8 @@ var require_config = __commonJS(() => {
489
489
  });
490
490
 
491
491
  // src/utils.ts
492
- import { mkdir, writeFile, readFile } from "fs/promises";
493
- import { dirname } from "path";
492
+ import { mkdir, writeFile, readFile, readdir, unlink } from "fs/promises";
493
+ import { dirname, join } from "path";
494
494
  import { existsSync } from "fs";
495
495
  async function writeFilesIfChanged(files) {
496
496
  let written = 0;
@@ -515,6 +515,28 @@ async function ensureDirs(dirs) {
515
515
  for (const d of dirs)
516
516
  await mkdir(d, { recursive: true });
517
517
  }
518
+ async function deleteStaleFiles(generatedPaths, dirsToScan) {
519
+ let deleted = 0;
520
+ const filesDeleted = [];
521
+ for (const dir of dirsToScan) {
522
+ if (!existsSync(dir))
523
+ continue;
524
+ const entries = await readdir(dir, { withFileTypes: true });
525
+ for (const entry of entries) {
526
+ if (!entry.isFile())
527
+ continue;
528
+ if (!/\.(ts|md|yml|sh)$/.test(entry.name))
529
+ continue;
530
+ const fullPath = join(dir, entry.name);
531
+ if (!generatedPaths.has(fullPath)) {
532
+ await unlink(fullPath);
533
+ deleted++;
534
+ filesDeleted.push(fullPath);
535
+ }
536
+ }
537
+ }
538
+ return { deleted, filesDeleted };
539
+ }
518
540
  var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
519
541
  var init_utils = () => {};
520
542
 
@@ -2559,7 +2581,7 @@ __export(exports_cli_pull, {
2559
2581
  pullCommand: () => pullCommand
2560
2582
  });
2561
2583
  import { writeFile as writeFile2, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
2562
- import { join as join2, dirname as dirname3, resolve as resolve2 } from "path";
2584
+ import { join as join3, dirname as dirname3, resolve as resolve2 } from "path";
2563
2585
  import { existsSync as existsSync4 } from "fs";
2564
2586
  import { pathToFileURL as pathToFileURL2 } from "url";
2565
2587
  async function pullCommand(args) {
@@ -2645,7 +2667,7 @@ Options:`);
2645
2667
  let filesUnchanged = 0;
2646
2668
  const changedFiles = [];
2647
2669
  for (const [path, content] of Object.entries(sdk.files)) {
2648
- const fullPath = join2(config.output, path);
2670
+ const fullPath = join3(config.output, path);
2649
2671
  await mkdir2(dirname3(fullPath), { recursive: true });
2650
2672
  let shouldWrite = true;
2651
2673
  if (existsSync4(fullPath)) {
@@ -2662,7 +2684,7 @@ Options:`);
2662
2684
  console.log(` ✓ ${path}`);
2663
2685
  }
2664
2686
  }
2665
- const metadataPath = join2(config.output, ".postgresdk.json");
2687
+ const metadataPath = join3(config.output, ".postgresdk.json");
2666
2688
  const metadata = {
2667
2689
  version: sdk.version,
2668
2690
  pulledFrom: config.from
@@ -2692,7 +2714,7 @@ var init_cli_pull = () => {};
2692
2714
 
2693
2715
  // src/index.ts
2694
2716
  var import_config = __toESM(require_config(), 1);
2695
- import { join, relative, dirname as dirname2 } from "node:path";
2717
+ import { join as join2, relative, dirname as dirname2 } from "node:path";
2696
2718
  import { pathToFileURL, fileURLToPath } from "node:url";
2697
2719
  import { existsSync as existsSync2, readFileSync } from "node:fs";
2698
2720
 
@@ -3607,6 +3629,7 @@ function emitClient(table, graph, opts, model) {
3607
3629
  where?: Where<Select${Type}>;
3608
3630
  orderBy?: string | string[];
3609
3631
  order?: "asc" | "desc" | ("asc" | "desc")[];
3632
+ distinctOn?: string | string[];
3610
3633
  ${paramName}?: {
3611
3634
  select?: string[];
3612
3635
  exclude?: string[];
@@ -3639,6 +3662,7 @@ function emitClient(table, graph, opts, model) {
3639
3662
  where?: Where<Select${Type}>;
3640
3663
  orderBy?: string | string[];
3641
3664
  order?: "asc" | "desc" | ("asc" | "desc")[];
3665
+ distinctOn?: string | string[];
3642
3666
  ${includeParams};
3643
3667
  }`;
3644
3668
  } else if (pattern.type === "nested" && pattern.nestedKey) {
@@ -3654,6 +3678,7 @@ function emitClient(table, graph, opts, model) {
3654
3678
  where?: Where<Select${Type}>;
3655
3679
  orderBy?: string | string[];
3656
3680
  order?: "asc" | "desc" | ("asc" | "desc")[];
3681
+ distinctOn?: string | string[];
3657
3682
  ${paramName}?: {
3658
3683
  select?: string[];
3659
3684
  exclude?: string[];
@@ -7247,7 +7272,7 @@ init_emit_sdk_contract();
7247
7272
  init_utils();
7248
7273
  var __filename2 = fileURLToPath(import.meta.url);
7249
7274
  var __dirname2 = dirname2(__filename2);
7250
- var { version: CLI_VERSION } = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
7275
+ var { version: CLI_VERSION } = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
7251
7276
  async function generate(configPath) {
7252
7277
  if (!existsSync2(configPath)) {
7253
7278
  throw new Error(`Config file not found: ${configPath}
@@ -7280,26 +7305,26 @@ async function generate(configPath) {
7280
7305
  const sameDirectory = serverDir === originalClientDir;
7281
7306
  let clientDir = originalClientDir;
7282
7307
  if (sameDirectory) {
7283
- clientDir = join(originalClientDir, "sdk");
7308
+ clientDir = join2(originalClientDir, "sdk");
7284
7309
  }
7285
7310
  const serverFramework = cfg.serverFramework || "hono";
7286
7311
  const generateTests = cfg.tests?.generate ?? false;
7287
7312
  const originalTestDir = cfg.tests?.output || "./api/tests";
7288
7313
  let testDir = originalTestDir;
7289
7314
  if (generateTests && (originalTestDir === serverDir || originalTestDir === originalClientDir)) {
7290
- testDir = join(originalTestDir, "tests");
7315
+ testDir = join2(originalTestDir, "tests");
7291
7316
  }
7292
7317
  const testFramework = cfg.tests?.framework || "vitest";
7293
7318
  console.log("\uD83D\uDCC1 Creating directories...");
7294
7319
  const dirs = [
7295
7320
  serverDir,
7296
- join(serverDir, "types"),
7297
- join(serverDir, "zod"),
7298
- join(serverDir, "routes"),
7321
+ join2(serverDir, "types"),
7322
+ join2(serverDir, "zod"),
7323
+ join2(serverDir, "routes"),
7299
7324
  clientDir,
7300
- join(clientDir, "types"),
7301
- join(clientDir, "zod"),
7302
- join(clientDir, "params")
7325
+ join2(clientDir, "types"),
7326
+ join2(clientDir, "zod"),
7327
+ join2(clientDir, "params")
7303
7328
  ];
7304
7329
  if (generateTests) {
7305
7330
  dirs.push(testDir);
@@ -7307,28 +7332,28 @@ async function generate(configPath) {
7307
7332
  await ensureDirs(dirs);
7308
7333
  const files = [];
7309
7334
  const includeSpec = emitIncludeSpec(graph);
7310
- files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
7311
- files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
7335
+ files.push({ path: join2(serverDir, "include-spec.ts"), content: includeSpec });
7336
+ files.push({ path: join2(clientDir, "include-spec.ts"), content: includeSpec });
7312
7337
  const includeResolver = emitIncludeResolver(graph, cfg.useJsExtensions);
7313
- files.push({ path: join(clientDir, "include-resolver.ts"), content: includeResolver });
7314
- files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
7315
- files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
7316
- files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
7317
- files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
7338
+ files.push({ path: join2(clientDir, "include-resolver.ts"), content: includeResolver });
7339
+ files.push({ path: join2(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
7340
+ files.push({ path: join2(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
7341
+ files.push({ path: join2(clientDir, "base-client.ts"), content: emitBaseClient() });
7342
+ files.push({ path: join2(clientDir, "where-types.ts"), content: emitWhereTypes() });
7318
7343
  files.push({
7319
- path: join(serverDir, "include-builder.ts"),
7344
+ path: join2(serverDir, "include-builder.ts"),
7320
7345
  content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
7321
7346
  });
7322
7347
  files.push({
7323
- path: join(serverDir, "include-loader.ts"),
7348
+ path: join2(serverDir, "include-loader.ts"),
7324
7349
  content: emitIncludeLoader(graph, model, cfg.includeMethodsDepth || 2, cfg.useJsExtensions)
7325
7350
  });
7326
- files.push({ path: join(serverDir, "logger.ts"), content: emitLogger() });
7351
+ files.push({ path: join2(serverDir, "logger.ts"), content: emitLogger() });
7327
7352
  if (getAuthStrategy(normalizedAuth) !== "none") {
7328
- files.push({ path: join(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
7353
+ files.push({ path: join2(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
7329
7354
  }
7330
7355
  files.push({
7331
- path: join(serverDir, "core", "operations.ts"),
7356
+ path: join2(serverDir, "core", "operations.ts"),
7332
7357
  content: emitCoreOperations()
7333
7358
  });
7334
7359
  if (process.env.SDK_DEBUG) {
@@ -7337,13 +7362,13 @@ async function generate(configPath) {
7337
7362
  for (const table of Object.values(model.tables)) {
7338
7363
  const numericMode = cfg.numericMode ?? "auto";
7339
7364
  const typesSrc = emitTypes(table, { numericMode }, model.enums);
7340
- files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
7341
- files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
7365
+ files.push({ path: join2(serverDir, "types", `${table.name}.ts`), content: typesSrc });
7366
+ files.push({ path: join2(clientDir, "types", `${table.name}.ts`), content: typesSrc });
7342
7367
  const zodSrc = emitZod(table, { numericMode }, model.enums);
7343
- files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
7344
- files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
7368
+ files.push({ path: join2(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
7369
+ files.push({ path: join2(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
7345
7370
  const paramsZodSrc = emitParamsZod(table, graph);
7346
- files.push({ path: join(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
7371
+ files.push({ path: join2(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
7347
7372
  let routeContent;
7348
7373
  if (serverFramework === "hono") {
7349
7374
  routeContent = emitHonoRoutes(table, graph, {
@@ -7357,11 +7382,11 @@ async function generate(configPath) {
7357
7382
  throw new Error(`Framework "${serverFramework}" is not yet supported. Currently only "hono" is available.`);
7358
7383
  }
7359
7384
  files.push({
7360
- path: join(serverDir, "routes", `${table.name}.ts`),
7385
+ path: join2(serverDir, "routes", `${table.name}.ts`),
7361
7386
  content: routeContent
7362
7387
  });
7363
7388
  files.push({
7364
- path: join(clientDir, `${table.name}.ts`),
7389
+ path: join2(clientDir, `${table.name}.ts`),
7365
7390
  content: emitClient(table, graph, {
7366
7391
  useJsExtensions: cfg.useJsExtensionsClient,
7367
7392
  includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
@@ -7370,12 +7395,12 @@ async function generate(configPath) {
7370
7395
  });
7371
7396
  }
7372
7397
  files.push({
7373
- path: join(clientDir, "index.ts"),
7398
+ path: join2(clientDir, "index.ts"),
7374
7399
  content: emitClientIndex(Object.values(model.tables), cfg.useJsExtensionsClient, graph, { maxDepth: cfg.includeMethodsDepth ?? 2, skipJunctionTables: cfg.skipJunctionTables ?? true })
7375
7400
  });
7376
7401
  if (serverFramework === "hono") {
7377
7402
  files.push({
7378
- path: join(serverDir, "router.ts"),
7403
+ path: join2(serverDir, "router.ts"),
7379
7404
  content: emitHonoRouter(Object.values(model.tables), getAuthStrategy(normalizedAuth) !== "none", cfg.useJsExtensions, cfg.pullToken)
7380
7405
  });
7381
7406
  }
@@ -7385,63 +7410,88 @@ async function generate(configPath) {
7385
7410
  }
7386
7411
  const contract = generateUnifiedContract2(model, cfg, graph);
7387
7412
  files.push({
7388
- path: join(serverDir, "CONTRACT.md"),
7413
+ path: join2(serverDir, "CONTRACT.md"),
7389
7414
  content: generateUnifiedContractMarkdown2(contract)
7390
7415
  });
7391
7416
  files.push({
7392
- path: join(clientDir, "CONTRACT.md"),
7417
+ path: join2(clientDir, "CONTRACT.md"),
7393
7418
  content: generateUnifiedContractMarkdown2(contract)
7394
7419
  });
7395
7420
  const contractCode = emitUnifiedContract(model, cfg, graph);
7396
7421
  files.push({
7397
- path: join(serverDir, "contract.ts"),
7422
+ path: join2(serverDir, "contract.ts"),
7398
7423
  content: contractCode
7399
7424
  });
7400
7425
  const clientFiles = files.filter((f) => {
7401
7426
  return f.path.includes(clientDir);
7402
7427
  });
7403
7428
  files.push({
7404
- path: join(serverDir, "sdk-bundle.ts"),
7429
+ path: join2(serverDir, "sdk-bundle.ts"),
7405
7430
  content: emitSdkBundle(clientFiles, clientDir, CLI_VERSION)
7406
7431
  });
7407
7432
  if (generateTests) {
7408
7433
  console.log("\uD83E\uDDEA Generating tests...");
7409
7434
  const relativeClientPath = relative(testDir, clientDir);
7410
7435
  files.push({
7411
- path: join(testDir, "setup.ts"),
7436
+ path: join2(testDir, "setup.ts"),
7412
7437
  content: emitTestSetup(relativeClientPath, testFramework)
7413
7438
  });
7414
7439
  files.push({
7415
- path: join(testDir, "docker-compose.yml"),
7440
+ path: join2(testDir, "docker-compose.yml"),
7416
7441
  content: emitDockerCompose()
7417
7442
  });
7418
7443
  files.push({
7419
- path: join(testDir, "run-tests.sh"),
7444
+ path: join2(testDir, "run-tests.sh"),
7420
7445
  content: emitTestScript(testFramework, testDir)
7421
7446
  });
7422
7447
  files.push({
7423
- path: join(testDir, ".gitignore"),
7448
+ path: join2(testDir, ".gitignore"),
7424
7449
  content: emitTestGitignore()
7425
7450
  });
7426
7451
  if (testFramework === "vitest") {
7427
7452
  files.push({
7428
- path: join(testDir, "vitest.config.ts"),
7453
+ path: join2(testDir, "vitest.config.ts"),
7429
7454
  content: emitVitestConfig()
7430
7455
  });
7431
7456
  }
7432
7457
  for (const table of Object.values(model.tables)) {
7433
7458
  files.push({
7434
- path: join(testDir, `${table.name}.test.ts`),
7459
+ path: join2(testDir, `${table.name}.test.ts`),
7435
7460
  content: emitTableTest(table, model, relativeClientPath, testFramework)
7436
7461
  });
7437
7462
  }
7438
7463
  }
7439
7464
  console.log("✍️ Writing files...");
7440
7465
  const writeResult = await writeFilesIfChanged(files);
7441
- if (writeResult.written === 0) {
7466
+ let deleteResult = { deleted: 0, filesDeleted: [] };
7467
+ if (cfg.clean !== false) {
7468
+ const dirsToScan = [
7469
+ serverDir,
7470
+ join2(serverDir, "types"),
7471
+ join2(serverDir, "zod"),
7472
+ join2(serverDir, "routes"),
7473
+ join2(serverDir, "core"),
7474
+ clientDir,
7475
+ join2(clientDir, "types"),
7476
+ join2(clientDir, "zod"),
7477
+ join2(clientDir, "params")
7478
+ ];
7479
+ if (generateTests)
7480
+ dirsToScan.push(testDir);
7481
+ const generatedPaths = new Set(files.map((f) => f.path));
7482
+ deleteResult = await deleteStaleFiles(generatedPaths, dirsToScan);
7483
+ }
7484
+ if (writeResult.written === 0 && deleteResult.deleted === 0) {
7442
7485
  console.log(`✅ All ${writeResult.unchanged} files up-to-date (no changes)`);
7443
7486
  } else {
7444
- console.log(`✅ Updated ${writeResult.written} files, ${writeResult.unchanged} unchanged`);
7487
+ const parts = [];
7488
+ if (writeResult.written > 0)
7489
+ parts.push(`updated ${writeResult.written} files`);
7490
+ if (deleteResult.deleted > 0)
7491
+ parts.push(`deleted ${deleteResult.deleted} stale files`);
7492
+ if (writeResult.unchanged > 0)
7493
+ parts.push(`${writeResult.unchanged} unchanged`);
7494
+ console.log(`✅ ${parts.join(", ")}`);
7445
7495
  }
7446
7496
  console.log(` Server: ${serverDir}`);
7447
7497
  console.log(` Client: ${sameDirectory ? clientDir + " (in sdk subdir due to same output dir)" : clientDir}`);
@@ -7480,10 +7530,10 @@ var import_config2 = __toESM(require_config(), 1);
7480
7530
  import { resolve as resolve3 } from "node:path";
7481
7531
  import { readFileSync as readFileSync3 } from "node:fs";
7482
7532
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7483
- import { dirname as dirname4, join as join3 } from "node:path";
7533
+ import { dirname as dirname4, join as join4 } from "node:path";
7484
7534
  var __filename3 = fileURLToPath2(import.meta.url);
7485
7535
  var __dirname3 = dirname4(__filename3);
7486
- var packageJson = JSON.parse(readFileSync3(join3(__dirname3, "../package.json"), "utf-8"));
7536
+ var packageJson = JSON.parse(readFileSync3(join4(__dirname3, "../package.json"), "utf-8"));
7487
7537
  var VERSION = packageJson.version;
7488
7538
  var args = process.argv.slice(2);
7489
7539
  var command = args[0];
package/dist/index.js CHANGED
@@ -488,8 +488,8 @@ var require_config = __commonJS(() => {
488
488
  });
489
489
 
490
490
  // src/utils.ts
491
- import { mkdir, writeFile, readFile } from "fs/promises";
492
- import { dirname } from "path";
491
+ import { mkdir, writeFile, readFile, readdir, unlink } from "fs/promises";
492
+ import { dirname, join } from "path";
493
493
  import { existsSync } from "fs";
494
494
  async function writeFilesIfChanged(files) {
495
495
  let written = 0;
@@ -514,6 +514,28 @@ async function ensureDirs(dirs) {
514
514
  for (const d of dirs)
515
515
  await mkdir(d, { recursive: true });
516
516
  }
517
+ async function deleteStaleFiles(generatedPaths, dirsToScan) {
518
+ let deleted = 0;
519
+ const filesDeleted = [];
520
+ for (const dir of dirsToScan) {
521
+ if (!existsSync(dir))
522
+ continue;
523
+ const entries = await readdir(dir, { withFileTypes: true });
524
+ for (const entry of entries) {
525
+ if (!entry.isFile())
526
+ continue;
527
+ if (!/\.(ts|md|yml|sh)$/.test(entry.name))
528
+ continue;
529
+ const fullPath = join(dir, entry.name);
530
+ if (!generatedPaths.has(fullPath)) {
531
+ await unlink(fullPath);
532
+ deleted++;
533
+ filesDeleted.push(fullPath);
534
+ }
535
+ }
536
+ }
537
+ return { deleted, filesDeleted };
538
+ }
517
539
  var pascal = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
518
540
  var init_utils = () => {};
519
541
 
@@ -1731,7 +1753,7 @@ var init_emit_sdk_contract = __esm(() => {
1731
1753
 
1732
1754
  // src/index.ts
1733
1755
  var import_config = __toESM(require_config(), 1);
1734
- import { join, relative, dirname as dirname2 } from "node:path";
1756
+ import { join as join2, relative, dirname as dirname2 } from "node:path";
1735
1757
  import { pathToFileURL, fileURLToPath } from "node:url";
1736
1758
  import { existsSync as existsSync2, readFileSync } from "node:fs";
1737
1759
 
@@ -2646,6 +2668,7 @@ function emitClient(table, graph, opts, model) {
2646
2668
  where?: Where<Select${Type}>;
2647
2669
  orderBy?: string | string[];
2648
2670
  order?: "asc" | "desc" | ("asc" | "desc")[];
2671
+ distinctOn?: string | string[];
2649
2672
  ${paramName}?: {
2650
2673
  select?: string[];
2651
2674
  exclude?: string[];
@@ -2678,6 +2701,7 @@ function emitClient(table, graph, opts, model) {
2678
2701
  where?: Where<Select${Type}>;
2679
2702
  orderBy?: string | string[];
2680
2703
  order?: "asc" | "desc" | ("asc" | "desc")[];
2704
+ distinctOn?: string | string[];
2681
2705
  ${includeParams};
2682
2706
  }`;
2683
2707
  } else if (pattern.type === "nested" && pattern.nestedKey) {
@@ -2693,6 +2717,7 @@ function emitClient(table, graph, opts, model) {
2693
2717
  where?: Where<Select${Type}>;
2694
2718
  orderBy?: string | string[];
2695
2719
  order?: "asc" | "desc" | ("asc" | "desc")[];
2720
+ distinctOn?: string | string[];
2696
2721
  ${paramName}?: {
2697
2722
  select?: string[];
2698
2723
  exclude?: string[];
@@ -6286,7 +6311,7 @@ init_emit_sdk_contract();
6286
6311
  init_utils();
6287
6312
  var __filename2 = fileURLToPath(import.meta.url);
6288
6313
  var __dirname2 = dirname2(__filename2);
6289
- var { version: CLI_VERSION } = JSON.parse(readFileSync(join(__dirname2, "../package.json"), "utf-8"));
6314
+ var { version: CLI_VERSION } = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
6290
6315
  async function generate(configPath) {
6291
6316
  if (!existsSync2(configPath)) {
6292
6317
  throw new Error(`Config file not found: ${configPath}
@@ -6319,26 +6344,26 @@ async function generate(configPath) {
6319
6344
  const sameDirectory = serverDir === originalClientDir;
6320
6345
  let clientDir = originalClientDir;
6321
6346
  if (sameDirectory) {
6322
- clientDir = join(originalClientDir, "sdk");
6347
+ clientDir = join2(originalClientDir, "sdk");
6323
6348
  }
6324
6349
  const serverFramework = cfg.serverFramework || "hono";
6325
6350
  const generateTests = cfg.tests?.generate ?? false;
6326
6351
  const originalTestDir = cfg.tests?.output || "./api/tests";
6327
6352
  let testDir = originalTestDir;
6328
6353
  if (generateTests && (originalTestDir === serverDir || originalTestDir === originalClientDir)) {
6329
- testDir = join(originalTestDir, "tests");
6354
+ testDir = join2(originalTestDir, "tests");
6330
6355
  }
6331
6356
  const testFramework = cfg.tests?.framework || "vitest";
6332
6357
  console.log("\uD83D\uDCC1 Creating directories...");
6333
6358
  const dirs = [
6334
6359
  serverDir,
6335
- join(serverDir, "types"),
6336
- join(serverDir, "zod"),
6337
- join(serverDir, "routes"),
6360
+ join2(serverDir, "types"),
6361
+ join2(serverDir, "zod"),
6362
+ join2(serverDir, "routes"),
6338
6363
  clientDir,
6339
- join(clientDir, "types"),
6340
- join(clientDir, "zod"),
6341
- join(clientDir, "params")
6364
+ join2(clientDir, "types"),
6365
+ join2(clientDir, "zod"),
6366
+ join2(clientDir, "params")
6342
6367
  ];
6343
6368
  if (generateTests) {
6344
6369
  dirs.push(testDir);
@@ -6346,28 +6371,28 @@ async function generate(configPath) {
6346
6371
  await ensureDirs(dirs);
6347
6372
  const files = [];
6348
6373
  const includeSpec = emitIncludeSpec(graph);
6349
- files.push({ path: join(serverDir, "include-spec.ts"), content: includeSpec });
6350
- files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
6374
+ files.push({ path: join2(serverDir, "include-spec.ts"), content: includeSpec });
6375
+ files.push({ path: join2(clientDir, "include-spec.ts"), content: includeSpec });
6351
6376
  const includeResolver = emitIncludeResolver(graph, cfg.useJsExtensions);
6352
- files.push({ path: join(clientDir, "include-resolver.ts"), content: includeResolver });
6353
- files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
6354
- files.push({ path: join(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
6355
- files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
6356
- files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
6377
+ files.push({ path: join2(clientDir, "include-resolver.ts"), content: includeResolver });
6378
+ files.push({ path: join2(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
6379
+ files.push({ path: join2(clientDir, "types", "shared.ts"), content: emitSharedTypes() });
6380
+ files.push({ path: join2(clientDir, "base-client.ts"), content: emitBaseClient() });
6381
+ files.push({ path: join2(clientDir, "where-types.ts"), content: emitWhereTypes() });
6357
6382
  files.push({
6358
- path: join(serverDir, "include-builder.ts"),
6383
+ path: join2(serverDir, "include-builder.ts"),
6359
6384
  content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
6360
6385
  });
6361
6386
  files.push({
6362
- path: join(serverDir, "include-loader.ts"),
6387
+ path: join2(serverDir, "include-loader.ts"),
6363
6388
  content: emitIncludeLoader(graph, model, cfg.includeMethodsDepth || 2, cfg.useJsExtensions)
6364
6389
  });
6365
- files.push({ path: join(serverDir, "logger.ts"), content: emitLogger() });
6390
+ files.push({ path: join2(serverDir, "logger.ts"), content: emitLogger() });
6366
6391
  if (getAuthStrategy(normalizedAuth) !== "none") {
6367
- files.push({ path: join(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
6392
+ files.push({ path: join2(serverDir, "auth.ts"), content: emitAuth(normalizedAuth) });
6368
6393
  }
6369
6394
  files.push({
6370
- path: join(serverDir, "core", "operations.ts"),
6395
+ path: join2(serverDir, "core", "operations.ts"),
6371
6396
  content: emitCoreOperations()
6372
6397
  });
6373
6398
  if (process.env.SDK_DEBUG) {
@@ -6376,13 +6401,13 @@ async function generate(configPath) {
6376
6401
  for (const table of Object.values(model.tables)) {
6377
6402
  const numericMode = cfg.numericMode ?? "auto";
6378
6403
  const typesSrc = emitTypes(table, { numericMode }, model.enums);
6379
- files.push({ path: join(serverDir, "types", `${table.name}.ts`), content: typesSrc });
6380
- files.push({ path: join(clientDir, "types", `${table.name}.ts`), content: typesSrc });
6404
+ files.push({ path: join2(serverDir, "types", `${table.name}.ts`), content: typesSrc });
6405
+ files.push({ path: join2(clientDir, "types", `${table.name}.ts`), content: typesSrc });
6381
6406
  const zodSrc = emitZod(table, { numericMode }, model.enums);
6382
- files.push({ path: join(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
6383
- files.push({ path: join(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
6407
+ files.push({ path: join2(serverDir, "zod", `${table.name}.ts`), content: zodSrc });
6408
+ files.push({ path: join2(clientDir, "zod", `${table.name}.ts`), content: zodSrc });
6384
6409
  const paramsZodSrc = emitParamsZod(table, graph);
6385
- files.push({ path: join(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
6410
+ files.push({ path: join2(clientDir, "params", `${table.name}.ts`), content: paramsZodSrc });
6386
6411
  let routeContent;
6387
6412
  if (serverFramework === "hono") {
6388
6413
  routeContent = emitHonoRoutes(table, graph, {
@@ -6396,11 +6421,11 @@ async function generate(configPath) {
6396
6421
  throw new Error(`Framework "${serverFramework}" is not yet supported. Currently only "hono" is available.`);
6397
6422
  }
6398
6423
  files.push({
6399
- path: join(serverDir, "routes", `${table.name}.ts`),
6424
+ path: join2(serverDir, "routes", `${table.name}.ts`),
6400
6425
  content: routeContent
6401
6426
  });
6402
6427
  files.push({
6403
- path: join(clientDir, `${table.name}.ts`),
6428
+ path: join2(clientDir, `${table.name}.ts`),
6404
6429
  content: emitClient(table, graph, {
6405
6430
  useJsExtensions: cfg.useJsExtensionsClient,
6406
6431
  includeMethodsDepth: cfg.includeMethodsDepth ?? 2,
@@ -6409,12 +6434,12 @@ async function generate(configPath) {
6409
6434
  });
6410
6435
  }
6411
6436
  files.push({
6412
- path: join(clientDir, "index.ts"),
6437
+ path: join2(clientDir, "index.ts"),
6413
6438
  content: emitClientIndex(Object.values(model.tables), cfg.useJsExtensionsClient, graph, { maxDepth: cfg.includeMethodsDepth ?? 2, skipJunctionTables: cfg.skipJunctionTables ?? true })
6414
6439
  });
6415
6440
  if (serverFramework === "hono") {
6416
6441
  files.push({
6417
- path: join(serverDir, "router.ts"),
6442
+ path: join2(serverDir, "router.ts"),
6418
6443
  content: emitHonoRouter(Object.values(model.tables), getAuthStrategy(normalizedAuth) !== "none", cfg.useJsExtensions, cfg.pullToken)
6419
6444
  });
6420
6445
  }
@@ -6424,63 +6449,88 @@ async function generate(configPath) {
6424
6449
  }
6425
6450
  const contract = generateUnifiedContract2(model, cfg, graph);
6426
6451
  files.push({
6427
- path: join(serverDir, "CONTRACT.md"),
6452
+ path: join2(serverDir, "CONTRACT.md"),
6428
6453
  content: generateUnifiedContractMarkdown2(contract)
6429
6454
  });
6430
6455
  files.push({
6431
- path: join(clientDir, "CONTRACT.md"),
6456
+ path: join2(clientDir, "CONTRACT.md"),
6432
6457
  content: generateUnifiedContractMarkdown2(contract)
6433
6458
  });
6434
6459
  const contractCode = emitUnifiedContract(model, cfg, graph);
6435
6460
  files.push({
6436
- path: join(serverDir, "contract.ts"),
6461
+ path: join2(serverDir, "contract.ts"),
6437
6462
  content: contractCode
6438
6463
  });
6439
6464
  const clientFiles = files.filter((f) => {
6440
6465
  return f.path.includes(clientDir);
6441
6466
  });
6442
6467
  files.push({
6443
- path: join(serverDir, "sdk-bundle.ts"),
6468
+ path: join2(serverDir, "sdk-bundle.ts"),
6444
6469
  content: emitSdkBundle(clientFiles, clientDir, CLI_VERSION)
6445
6470
  });
6446
6471
  if (generateTests) {
6447
6472
  console.log("\uD83E\uDDEA Generating tests...");
6448
6473
  const relativeClientPath = relative(testDir, clientDir);
6449
6474
  files.push({
6450
- path: join(testDir, "setup.ts"),
6475
+ path: join2(testDir, "setup.ts"),
6451
6476
  content: emitTestSetup(relativeClientPath, testFramework)
6452
6477
  });
6453
6478
  files.push({
6454
- path: join(testDir, "docker-compose.yml"),
6479
+ path: join2(testDir, "docker-compose.yml"),
6455
6480
  content: emitDockerCompose()
6456
6481
  });
6457
6482
  files.push({
6458
- path: join(testDir, "run-tests.sh"),
6483
+ path: join2(testDir, "run-tests.sh"),
6459
6484
  content: emitTestScript(testFramework, testDir)
6460
6485
  });
6461
6486
  files.push({
6462
- path: join(testDir, ".gitignore"),
6487
+ path: join2(testDir, ".gitignore"),
6463
6488
  content: emitTestGitignore()
6464
6489
  });
6465
6490
  if (testFramework === "vitest") {
6466
6491
  files.push({
6467
- path: join(testDir, "vitest.config.ts"),
6492
+ path: join2(testDir, "vitest.config.ts"),
6468
6493
  content: emitVitestConfig()
6469
6494
  });
6470
6495
  }
6471
6496
  for (const table of Object.values(model.tables)) {
6472
6497
  files.push({
6473
- path: join(testDir, `${table.name}.test.ts`),
6498
+ path: join2(testDir, `${table.name}.test.ts`),
6474
6499
  content: emitTableTest(table, model, relativeClientPath, testFramework)
6475
6500
  });
6476
6501
  }
6477
6502
  }
6478
6503
  console.log("✍️ Writing files...");
6479
6504
  const writeResult = await writeFilesIfChanged(files);
6480
- if (writeResult.written === 0) {
6505
+ let deleteResult = { deleted: 0, filesDeleted: [] };
6506
+ if (cfg.clean !== false) {
6507
+ const dirsToScan = [
6508
+ serverDir,
6509
+ join2(serverDir, "types"),
6510
+ join2(serverDir, "zod"),
6511
+ join2(serverDir, "routes"),
6512
+ join2(serverDir, "core"),
6513
+ clientDir,
6514
+ join2(clientDir, "types"),
6515
+ join2(clientDir, "zod"),
6516
+ join2(clientDir, "params")
6517
+ ];
6518
+ if (generateTests)
6519
+ dirsToScan.push(testDir);
6520
+ const generatedPaths = new Set(files.map((f) => f.path));
6521
+ deleteResult = await deleteStaleFiles(generatedPaths, dirsToScan);
6522
+ }
6523
+ if (writeResult.written === 0 && deleteResult.deleted === 0) {
6481
6524
  console.log(`✅ All ${writeResult.unchanged} files up-to-date (no changes)`);
6482
6525
  } else {
6483
- console.log(`✅ Updated ${writeResult.written} files, ${writeResult.unchanged} unchanged`);
6526
+ const parts = [];
6527
+ if (writeResult.written > 0)
6528
+ parts.push(`updated ${writeResult.written} files`);
6529
+ if (deleteResult.deleted > 0)
6530
+ parts.push(`deleted ${deleteResult.deleted} stale files`);
6531
+ if (writeResult.unchanged > 0)
6532
+ parts.push(`${writeResult.unchanged} unchanged`);
6533
+ console.log(`✅ ${parts.join(", ")}`);
6484
6534
  }
6485
6535
  console.log(` Server: ${serverDir}`);
6486
6536
  console.log(` Client: ${sameDirectory ? clientDir + " (in sdk subdir due to same output dir)" : clientDir}`);
package/dist/types.d.ts CHANGED
@@ -34,6 +34,7 @@ export interface Config {
34
34
  pull?: PullConfig;
35
35
  useJsExtensions?: boolean;
36
36
  useJsExtensionsClient?: boolean;
37
+ clean?: boolean;
37
38
  tests?: {
38
39
  generate?: boolean;
39
40
  output?: string;
package/dist/utils.d.ts CHANGED
@@ -20,3 +20,11 @@ export declare function writeFilesIfChanged(files: Array<{
20
20
  */
21
21
  export declare function hashContent(content: string): string;
22
22
  export declare function ensureDirs(dirs: string[]): Promise<void>;
23
+ /**
24
+ * Delete files in the given directories that are not in the set of generated paths.
25
+ * Used to remove stale files for tables that no longer exist in the schema.
26
+ */
27
+ export declare function deleteStaleFiles(generatedPaths: Set<string>, dirsToScan: string[]): Promise<{
28
+ deleted: number;
29
+ filesDeleted: string[];
30
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.17",
3
+ "version": "0.18.18",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {