postgresdk 0.18.21 → 0.18.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -146,6 +146,10 @@ export default {
146
146
  schema: "public", // Database schema to introspect
147
147
  outDir: "./api", // Output directory (or { client: "./sdk", server: "./api" })
148
148
  softDeleteColumn: null, // Column name for soft deletes (e.g., "deleted_at")
149
+ softDeleteColumnOverrides: { // Per-table overrides (null = disable soft delete for that table)
150
+ // captures: "hidden_at", // different column name for a specific table
151
+ // audit_logs: null, // hard deletes only for this table
152
+ },
149
153
  numericMode: "auto", // "auto" | "number" | "string" - How to type numeric columns
150
154
  includeMethodsDepth: 2, // Max depth for nested includes
151
155
  dateType: "date", // "date" | "string" - How to handle timestamps
@@ -738,6 +742,11 @@ const nested = await sdk.users.list({
738
742
  }
739
743
  });
740
744
 
745
+ // Soft deletes — when softDeleteColumn is configured, deleted records are hidden by default.
746
+ // Pass includeSoftDeleted: true to opt into seeing them (e.g., for admin/recovery UIs).
747
+ const allUsers = await sdk.users.list({ includeSoftDeleted: true });
748
+ const deletedUser = await sdk.users.getByPk("123", { includeSoftDeleted: true });
749
+
741
750
  // Pagination with filtered results
742
751
  let allResults = [];
743
752
  let offset = 0;
package/dist/cli.js CHANGED
@@ -3073,10 +3073,10 @@ function emitZod(table, opts, enums) {
3073
3073
  return `z.boolean()`;
3074
3074
  if (t === "int2" || t === "int4" || t === "int8") {
3075
3075
  if (opts.numericMode === "number")
3076
- return `z.number()`;
3076
+ return `z.number().int()`;
3077
3077
  if (opts.numericMode === "string")
3078
3078
  return `z.string()`;
3079
- return t === "int2" || t === "int4" ? `z.number()` : `z.string()`;
3079
+ return t === "int2" || t === "int4" ? `z.number().int()` : `z.string()`;
3080
3080
  }
3081
3081
  if (t === "numeric" || t === "float4" || t === "float8") {
3082
3082
  if (opts.numericMode === "number")
@@ -3273,7 +3273,8 @@ const deleteSchema = z.object({
3273
3273
 
3274
3274
  const getByPkQuerySchema = z.object({
3275
3275
  select: z.array(z.string()).min(1).optional(),
3276
- exclude: z.array(z.string()).min(1).optional()
3276
+ exclude: z.array(z.string()).min(1).optional(),
3277
+ includeSoftDeleted: z.boolean().optional()
3277
3278
  }).strict().refine(
3278
3279
  (data) => !(data.select && data.exclude),
3279
3280
  { message: "Cannot specify both 'select' and 'exclude' parameters" }
@@ -3288,7 +3289,8 @@ const listSchema = z.object({
3288
3289
  offset: z.number().int().min(0).optional(),
3289
3290
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
3290
3291
  order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
3291
- distinctOn: z.union([columnEnum, z.array(columnEnum)]).optional(),${hasVectorColumns ? `
3292
+ distinctOn: z.union([columnEnum, z.array(columnEnum)]).optional(),
3293
+ includeSoftDeleted: z.boolean().optional(),${hasVectorColumns ? `
3292
3294
  vector: z.object({
3293
3295
  field: z.string(),
3294
3296
  query: z.array(z.number()),
@@ -3385,12 +3387,15 @@ ${hasAuth ? `
3385
3387
  app.get(\`\${base}/${pkPath}\`, async (c) => {
3386
3388
  ${getPkParams}
3387
3389
 
3388
- // Parse query params for select/exclude
3390
+ // Parse query params coerce includeSoftDeleted string to boolean before Zod validation
3391
+ // (avoids Boolean("false")=true pitfall while keeping schema as single source of truth)
3389
3392
  const selectParam = c.req.query("select");
3390
3393
  const excludeParam = c.req.query("exclude");
3394
+ const includeSoftDeletedParam = c.req.query("includeSoftDeleted");
3391
3395
  const queryData: any = {};
3392
3396
  if (selectParam) queryData.select = selectParam.split(",");
3393
3397
  if (excludeParam) queryData.exclude = excludeParam.split(",");
3398
+ if (includeSoftDeletedParam !== undefined) queryData.includeSoftDeleted = includeSoftDeletedParam === "true";
3394
3399
 
3395
3400
  const queryParsed = getByPkQuerySchema.safeParse(queryData);
3396
3401
  if (!queryParsed.success) {
@@ -3403,7 +3408,7 @@ ${hasAuth ? `
3403
3408
  }
3404
3409
 
3405
3410
  const ctx = { ...baseCtx, select: queryParsed.data.select, exclude: queryParsed.data.exclude };
3406
- const result = await coreOps.getByPk(ctx, pkValues);
3411
+ const result = await coreOps.getByPk(ctx, pkValues, { includeSoftDeleted: queryParsed.data.includeSoftDeleted });
3407
3412
 
3408
3413
  if (result.error) {
3409
3414
  return c.json({ error: result.error }, result.status as any);
@@ -3909,14 +3914,14 @@ ${hasJsonbColumns ? ` /**
3909
3914
  * @param options - Select specific fields to return
3910
3915
  * @returns The record with only selected fields if found, null otherwise
3911
3916
  */
3912
- async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { select: string[] }): Promise<Partial<Select${Type}<TJsonb>> | null>;
3917
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { select: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}<TJsonb>> | null>;
3913
3918
  /**
3914
3919
  * Get a ${table.name} record by primary key with field exclusion
3915
3920
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3916
3921
  * @param options - Exclude specific fields from return
3917
3922
  * @returns The record without excluded fields if found, null otherwise
3918
3923
  */
3919
- async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { exclude: string[] }): Promise<Partial<Select${Type}<TJsonb>> | null>;
3924
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { exclude: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}<TJsonb>> | null>;
3920
3925
  /**
3921
3926
  * Get a ${table.name} record by primary key
3922
3927
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
@@ -3925,15 +3930,16 @@ ${hasJsonbColumns ? ` /**
3925
3930
  * // With JSONB type override:
3926
3931
  * const user = await client.getByPk<{ metadata: Metadata }>('user-id');
3927
3932
  */
3928
- async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options?: Omit<{ select?: string[]; exclude?: string[] }, 'select' | 'exclude'>): Promise<Select${Type}<TJsonb> | null>;
3933
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options?: { includeSoftDeleted?: boolean }): Promise<Select${Type}<TJsonb> | null>;
3929
3934
  async getByPk<TJsonb extends Partial<Select${Type}> = {}>(
3930
3935
  pk: ${pkType},
3931
- options?: { select?: string[]; exclude?: string[] }
3936
+ options?: { select?: string[]; exclude?: string[]; includeSoftDeleted?: boolean }
3932
3937
  ): Promise<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>> | null> {
3933
3938
  const path = ${pkPathExpr};
3934
3939
  const queryParams = new URLSearchParams();
3935
3940
  if (options?.select) queryParams.set('select', options.select.join(','));
3936
3941
  if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3942
+ if (options?.includeSoftDeleted) queryParams.set('includeSoftDeleted', 'true');
3937
3943
  const query = queryParams.toString();
3938
3944
  const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3939
3945
  return this.get<Select${Type}<TJsonb> | null>(url);
@@ -3943,28 +3949,29 @@ ${hasJsonbColumns ? ` /**
3943
3949
  * @param options - Select specific fields to return
3944
3950
  * @returns The record with only selected fields if found, null otherwise
3945
3951
  */
3946
- async getByPk(pk: ${pkType}, options: { select: string[] }): Promise<Partial<Select${Type}> | null>;
3952
+ async getByPk(pk: ${pkType}, options: { select: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}> | null>;
3947
3953
  /**
3948
3954
  * Get a ${table.name} record by primary key with field exclusion
3949
3955
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3950
3956
  * @param options - Exclude specific fields from return
3951
3957
  * @returns The record without excluded fields if found, null otherwise
3952
3958
  */
3953
- async getByPk(pk: ${pkType}, options: { exclude: string[] }): Promise<Partial<Select${Type}> | null>;
3959
+ async getByPk(pk: ${pkType}, options: { exclude: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}> | null>;
3954
3960
  /**
3955
3961
  * Get a ${table.name} record by primary key
3956
3962
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3957
3963
  * @returns The record with all fields if found, null otherwise
3958
3964
  */
3959
- async getByPk(pk: ${pkType}, options?: Omit<{ select?: string[]; exclude?: string[] }, 'select' | 'exclude'>): Promise<Select${Type} | null>;
3965
+ async getByPk(pk: ${pkType}, options?: { includeSoftDeleted?: boolean }): Promise<Select${Type} | null>;
3960
3966
  async getByPk(
3961
3967
  pk: ${pkType},
3962
- options?: { select?: string[]; exclude?: string[] }
3968
+ options?: { select?: string[]; exclude?: string[]; includeSoftDeleted?: boolean }
3963
3969
  ): Promise<Select${Type} | Partial<Select${Type}> | null> {
3964
3970
  const path = ${pkPathExpr};
3965
3971
  const queryParams = new URLSearchParams();
3966
3972
  if (options?.select) queryParams.set('select', options.select.join(','));
3967
3973
  if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3974
+ if (options?.includeSoftDeleted) queryParams.set('includeSoftDeleted', 'true');
3968
3975
  const query = queryParams.toString();
3969
3976
  const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3970
3977
  return this.get<Select${Type} | null>(url);
@@ -3991,6 +3998,7 @@ ${hasJsonbColumns ? ` /**
3991
3998
  orderBy?: string | string[];
3992
3999
  order?: "asc" | "desc" | ("asc" | "desc")[];
3993
4000
  distinctOn?: string | string[];
4001
+ includeSoftDeleted?: boolean;
3994
4002
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3995
4003
  /**
3996
4004
  * List ${table.name} records with field exclusion
@@ -4013,6 +4021,7 @@ ${hasJsonbColumns ? ` /**
4013
4021
  orderBy?: string | string[];
4014
4022
  order?: "asc" | "desc" | ("asc" | "desc")[];
4015
4023
  distinctOn?: string | string[];
4024
+ includeSoftDeleted?: boolean;
4016
4025
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4017
4026
  /**
4018
4027
  * List ${table.name} records with pagination and filtering
@@ -4046,6 +4055,7 @@ ${hasJsonbColumns ? ` /**
4046
4055
  orderBy?: string | string[];
4047
4056
  order?: "asc" | "desc" | ("asc" | "desc")[];
4048
4057
  distinctOn?: string | string[];
4058
+ includeSoftDeleted?: boolean;
4049
4059
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4050
4060
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
4051
4061
  include?: ${Type}IncludeSpec;
@@ -4064,6 +4074,7 @@ ${hasJsonbColumns ? ` /**
4064
4074
  orderBy?: string | string[];
4065
4075
  order?: "asc" | "desc" | ("asc" | "desc")[];
4066
4076
  distinctOn?: string | string[];
4077
+ includeSoftDeleted?: boolean;
4067
4078
  }): Promise<PaginatedResponse<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>>> {
4068
4079
  return this.post<PaginatedResponse<Select${Type}<TJsonb> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
4069
4080
  }` : ` /**
@@ -4087,6 +4098,7 @@ ${hasJsonbColumns ? ` /**
4087
4098
  orderBy?: string | string[];
4088
4099
  order?: "asc" | "desc" | ("asc" | "desc")[];
4089
4100
  distinctOn?: string | string[];
4101
+ includeSoftDeleted?: boolean;
4090
4102
  }): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4091
4103
  /**
4092
4104
  * List ${table.name} records with field exclusion
@@ -4109,6 +4121,7 @@ ${hasJsonbColumns ? ` /**
4109
4121
  orderBy?: string | string[];
4110
4122
  order?: "asc" | "desc" | ("asc" | "desc")[];
4111
4123
  distinctOn?: string | string[];
4124
+ includeSoftDeleted?: boolean;
4112
4125
  }): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4113
4126
  /**
4114
4127
  * List ${table.name} records with pagination and filtering
@@ -4136,6 +4149,7 @@ ${hasJsonbColumns ? ` /**
4136
4149
  orderBy?: string | string[];
4137
4150
  order?: "asc" | "desc" | ("asc" | "desc")[];
4138
4151
  distinctOn?: string | string[];
4152
+ includeSoftDeleted?: boolean;
4139
4153
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
4140
4154
  async list(params?: {
4141
4155
  include?: ${Type}IncludeSpec;
@@ -4154,6 +4168,7 @@ ${hasJsonbColumns ? ` /**
4154
4168
  orderBy?: string | string[];
4155
4169
  order?: "asc" | "desc" | ("asc" | "desc")[];
4156
4170
  distinctOn?: string | string[];
4171
+ includeSoftDeleted?: boolean;
4157
4172
  }): Promise<PaginatedResponse<Select${Type} | Partial<Select${Type}>>> {
4158
4173
  return this.post<PaginatedResponse<Select${Type} & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
4159
4174
  }`}
@@ -5980,7 +5995,8 @@ export async function createRecord(
5980
5995
  */
5981
5996
  export async function getByPk(
5982
5997
  ctx: OperationContext,
5983
- pkValues: any[]
5998
+ pkValues: any[],
5999
+ opts?: { includeSoftDeleted?: boolean }
5984
6000
  ): Promise<{ data?: any; error?: string; status: number }> {
5985
6001
  try {
5986
6002
  const hasCompositePk = ctx.pkColumns.length > 1;
@@ -5989,7 +6005,8 @@ export async function getByPk(
5989
6005
  : \`"\${ctx.pkColumns[0]}" = $1\`;
5990
6006
 
5991
6007
  const columns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5992
- const text = \`SELECT \${columns} FROM "\${ctx.table}" WHERE \${wherePkSql} LIMIT 1\`;
6008
+ const softDeleteFilter = ctx.softDeleteColumn && !opts?.includeSoftDeleted ? \` AND "\${ctx.softDeleteColumn}" IS NULL\` : "";
6009
+ const text = \`SELECT \${columns} FROM "\${ctx.table}" WHERE \${wherePkSql}\${softDeleteFilter} LIMIT 1\`;
5993
6010
  log.debug(\`GET \${ctx.table} by PK:\`, pkValues, "SQL:", text);
5994
6011
 
5995
6012
  const { rows } = await ctx.pg.query(text, pkValues);
@@ -6362,6 +6379,7 @@ export async function listRecords(
6362
6379
  orderBy?: string | string[];
6363
6380
  order?: "asc" | "desc" | ("asc" | "desc")[];
6364
6381
  distinctOn?: string | string[];
6382
+ includeSoftDeleted?: boolean;
6365
6383
  vector?: {
6366
6384
  field: string;
6367
6385
  query: number[];
@@ -6372,7 +6390,7 @@ export async function listRecords(
6372
6390
  }
6373
6391
  ): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
6374
6392
  try {
6375
- const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, trigram, distinctOn } = params;
6393
+ const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, trigram, distinctOn, includeSoftDeleted } = params;
6376
6394
 
6377
6395
  // DISTINCT ON support
6378
6396
  const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
@@ -6408,8 +6426,8 @@ export async function listRecords(
6408
6426
  const whereParts: string[] = [];
6409
6427
  let whereParams: any[] = [];
6410
6428
 
6411
- // Add soft delete filter if applicable
6412
- if (ctx.softDeleteColumn) {
6429
+ // Add soft delete filter if applicable (skip if caller opts into seeing soft-deleted records)
6430
+ if (ctx.softDeleteColumn && !includeSoftDeleted) {
6413
6431
  whereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
6414
6432
  }
6415
6433
 
@@ -6447,7 +6465,7 @@ export async function listRecords(
6447
6465
 
6448
6466
  if (hasQueryParam && !hasThreshold && whereParams.length > 0) {
6449
6467
  const countWhereParts: string[] = [];
6450
- if (ctx.softDeleteColumn) {
6468
+ if (ctx.softDeleteColumn && !includeSoftDeleted) {
6451
6469
  countWhereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
6452
6470
  }
6453
6471
  if (whereClause) {
@@ -7418,6 +7436,12 @@ init_utils();
7418
7436
  var __filename2 = fileURLToPath(import.meta.url);
7419
7437
  var __dirname2 = dirname2(__filename2);
7420
7438
  var { version: CLI_VERSION } = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
7439
+ function resolveSoftDeleteColumn(cfg, tableName) {
7440
+ const overrides = cfg.softDeleteColumnOverrides;
7441
+ if (overrides && tableName in overrides)
7442
+ return overrides[tableName] ?? null;
7443
+ return cfg.softDeleteColumn ?? null;
7444
+ }
7421
7445
  async function generate(configPath) {
7422
7446
  if (!existsSync2(configPath)) {
7423
7447
  throw new Error(`Config file not found: ${configPath}
@@ -7517,7 +7541,7 @@ async function generate(configPath) {
7517
7541
  let routeContent;
7518
7542
  if (serverFramework === "hono") {
7519
7543
  routeContent = emitHonoRoutes(table, graph, {
7520
- softDeleteColumn: cfg.softDeleteColumn || null,
7544
+ softDeleteColumn: resolveSoftDeleteColumn(cfg, table.name),
7521
7545
  includeMethodsDepth: cfg.includeMethodsDepth || 2,
7522
7546
  authStrategy: getAuthStrategy(normalizedAuth),
7523
7547
  useJsExtensions: cfg.useJsExtensions,
package/dist/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  import "dotenv/config";
2
+ import type { Config } from "./types";
3
+ /** Resolves the effective soft delete column for a given table, respecting per-table overrides. */
4
+ export declare function resolveSoftDeleteColumn(cfg: Pick<Config, "softDeleteColumn" | "softDeleteColumnOverrides">, tableName: string): string | null;
2
5
  export declare function generate(configPath: string): Promise<void>;
package/dist/index.js CHANGED
@@ -2112,10 +2112,10 @@ function emitZod(table, opts, enums) {
2112
2112
  return `z.boolean()`;
2113
2113
  if (t === "int2" || t === "int4" || t === "int8") {
2114
2114
  if (opts.numericMode === "number")
2115
- return `z.number()`;
2115
+ return `z.number().int()`;
2116
2116
  if (opts.numericMode === "string")
2117
2117
  return `z.string()`;
2118
- return t === "int2" || t === "int4" ? `z.number()` : `z.string()`;
2118
+ return t === "int2" || t === "int4" ? `z.number().int()` : `z.string()`;
2119
2119
  }
2120
2120
  if (t === "numeric" || t === "float4" || t === "float8") {
2121
2121
  if (opts.numericMode === "number")
@@ -2312,7 +2312,8 @@ const deleteSchema = z.object({
2312
2312
 
2313
2313
  const getByPkQuerySchema = z.object({
2314
2314
  select: z.array(z.string()).min(1).optional(),
2315
- exclude: z.array(z.string()).min(1).optional()
2315
+ exclude: z.array(z.string()).min(1).optional(),
2316
+ includeSoftDeleted: z.boolean().optional()
2316
2317
  }).strict().refine(
2317
2318
  (data) => !(data.select && data.exclude),
2318
2319
  { message: "Cannot specify both 'select' and 'exclude' parameters" }
@@ -2327,7 +2328,8 @@ const listSchema = z.object({
2327
2328
  offset: z.number().int().min(0).optional(),
2328
2329
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
2329
2330
  order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
2330
- distinctOn: z.union([columnEnum, z.array(columnEnum)]).optional(),${hasVectorColumns ? `
2331
+ distinctOn: z.union([columnEnum, z.array(columnEnum)]).optional(),
2332
+ includeSoftDeleted: z.boolean().optional(),${hasVectorColumns ? `
2331
2333
  vector: z.object({
2332
2334
  field: z.string(),
2333
2335
  query: z.array(z.number()),
@@ -2424,12 +2426,15 @@ ${hasAuth ? `
2424
2426
  app.get(\`\${base}/${pkPath}\`, async (c) => {
2425
2427
  ${getPkParams}
2426
2428
 
2427
- // Parse query params for select/exclude
2429
+ // Parse query params coerce includeSoftDeleted string to boolean before Zod validation
2430
+ // (avoids Boolean("false")=true pitfall while keeping schema as single source of truth)
2428
2431
  const selectParam = c.req.query("select");
2429
2432
  const excludeParam = c.req.query("exclude");
2433
+ const includeSoftDeletedParam = c.req.query("includeSoftDeleted");
2430
2434
  const queryData: any = {};
2431
2435
  if (selectParam) queryData.select = selectParam.split(",");
2432
2436
  if (excludeParam) queryData.exclude = excludeParam.split(",");
2437
+ if (includeSoftDeletedParam !== undefined) queryData.includeSoftDeleted = includeSoftDeletedParam === "true";
2433
2438
 
2434
2439
  const queryParsed = getByPkQuerySchema.safeParse(queryData);
2435
2440
  if (!queryParsed.success) {
@@ -2442,7 +2447,7 @@ ${hasAuth ? `
2442
2447
  }
2443
2448
 
2444
2449
  const ctx = { ...baseCtx, select: queryParsed.data.select, exclude: queryParsed.data.exclude };
2445
- const result = await coreOps.getByPk(ctx, pkValues);
2450
+ const result = await coreOps.getByPk(ctx, pkValues, { includeSoftDeleted: queryParsed.data.includeSoftDeleted });
2446
2451
 
2447
2452
  if (result.error) {
2448
2453
  return c.json({ error: result.error }, result.status as any);
@@ -2948,14 +2953,14 @@ ${hasJsonbColumns ? ` /**
2948
2953
  * @param options - Select specific fields to return
2949
2954
  * @returns The record with only selected fields if found, null otherwise
2950
2955
  */
2951
- async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { select: string[] }): Promise<Partial<Select${Type}<TJsonb>> | null>;
2956
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { select: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}<TJsonb>> | null>;
2952
2957
  /**
2953
2958
  * Get a ${table.name} record by primary key with field exclusion
2954
2959
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2955
2960
  * @param options - Exclude specific fields from return
2956
2961
  * @returns The record without excluded fields if found, null otherwise
2957
2962
  */
2958
- async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { exclude: string[] }): Promise<Partial<Select${Type}<TJsonb>> | null>;
2963
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options: { exclude: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}<TJsonb>> | null>;
2959
2964
  /**
2960
2965
  * Get a ${table.name} record by primary key
2961
2966
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
@@ -2964,15 +2969,16 @@ ${hasJsonbColumns ? ` /**
2964
2969
  * // With JSONB type override:
2965
2970
  * const user = await client.getByPk<{ metadata: Metadata }>('user-id');
2966
2971
  */
2967
- async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options?: Omit<{ select?: string[]; exclude?: string[] }, 'select' | 'exclude'>): Promise<Select${Type}<TJsonb> | null>;
2972
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(pk: ${pkType}, options?: { includeSoftDeleted?: boolean }): Promise<Select${Type}<TJsonb> | null>;
2968
2973
  async getByPk<TJsonb extends Partial<Select${Type}> = {}>(
2969
2974
  pk: ${pkType},
2970
- options?: { select?: string[]; exclude?: string[] }
2975
+ options?: { select?: string[]; exclude?: string[]; includeSoftDeleted?: boolean }
2971
2976
  ): Promise<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>> | null> {
2972
2977
  const path = ${pkPathExpr};
2973
2978
  const queryParams = new URLSearchParams();
2974
2979
  if (options?.select) queryParams.set('select', options.select.join(','));
2975
2980
  if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
2981
+ if (options?.includeSoftDeleted) queryParams.set('includeSoftDeleted', 'true');
2976
2982
  const query = queryParams.toString();
2977
2983
  const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
2978
2984
  return this.get<Select${Type}<TJsonb> | null>(url);
@@ -2982,28 +2988,29 @@ ${hasJsonbColumns ? ` /**
2982
2988
  * @param options - Select specific fields to return
2983
2989
  * @returns The record with only selected fields if found, null otherwise
2984
2990
  */
2985
- async getByPk(pk: ${pkType}, options: { select: string[] }): Promise<Partial<Select${Type}> | null>;
2991
+ async getByPk(pk: ${pkType}, options: { select: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}> | null>;
2986
2992
  /**
2987
2993
  * Get a ${table.name} record by primary key with field exclusion
2988
2994
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2989
2995
  * @param options - Exclude specific fields from return
2990
2996
  * @returns The record without excluded fields if found, null otherwise
2991
2997
  */
2992
- async getByPk(pk: ${pkType}, options: { exclude: string[] }): Promise<Partial<Select${Type}> | null>;
2998
+ async getByPk(pk: ${pkType}, options: { exclude: string[]; includeSoftDeleted?: boolean }): Promise<Partial<Select${Type}> | null>;
2993
2999
  /**
2994
3000
  * Get a ${table.name} record by primary key
2995
3001
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2996
3002
  * @returns The record with all fields if found, null otherwise
2997
3003
  */
2998
- async getByPk(pk: ${pkType}, options?: Omit<{ select?: string[]; exclude?: string[] }, 'select' | 'exclude'>): Promise<Select${Type} | null>;
3004
+ async getByPk(pk: ${pkType}, options?: { includeSoftDeleted?: boolean }): Promise<Select${Type} | null>;
2999
3005
  async getByPk(
3000
3006
  pk: ${pkType},
3001
- options?: { select?: string[]; exclude?: string[] }
3007
+ options?: { select?: string[]; exclude?: string[]; includeSoftDeleted?: boolean }
3002
3008
  ): Promise<Select${Type} | Partial<Select${Type}> | null> {
3003
3009
  const path = ${pkPathExpr};
3004
3010
  const queryParams = new URLSearchParams();
3005
3011
  if (options?.select) queryParams.set('select', options.select.join(','));
3006
3012
  if (options?.exclude) queryParams.set('exclude', options.exclude.join(','));
3013
+ if (options?.includeSoftDeleted) queryParams.set('includeSoftDeleted', 'true');
3007
3014
  const query = queryParams.toString();
3008
3015
  const url = query ? \`\${this.resource}/\${path}?\${query}\` : \`\${this.resource}/\${path}\`;
3009
3016
  return this.get<Select${Type} | null>(url);
@@ -3030,6 +3037,7 @@ ${hasJsonbColumns ? ` /**
3030
3037
  orderBy?: string | string[];
3031
3038
  order?: "asc" | "desc" | ("asc" | "desc")[];
3032
3039
  distinctOn?: string | string[];
3040
+ includeSoftDeleted?: boolean;
3033
3041
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3034
3042
  /**
3035
3043
  * List ${table.name} records with field exclusion
@@ -3052,6 +3060,7 @@ ${hasJsonbColumns ? ` /**
3052
3060
  orderBy?: string | string[];
3053
3061
  order?: "asc" | "desc" | ("asc" | "desc")[];
3054
3062
  distinctOn?: string | string[];
3063
+ includeSoftDeleted?: boolean;
3055
3064
  }): Promise<PaginatedResponse<Partial<Select${Type}<TJsonb>> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3056
3065
  /**
3057
3066
  * List ${table.name} records with pagination and filtering
@@ -3085,6 +3094,7 @@ ${hasJsonbColumns ? ` /**
3085
3094
  orderBy?: string | string[];
3086
3095
  order?: "asc" | "desc" | ("asc" | "desc")[];
3087
3096
  distinctOn?: string | string[];
3097
+ includeSoftDeleted?: boolean;
3088
3098
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3089
3099
  async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3090
3100
  include?: ${Type}IncludeSpec;
@@ -3103,6 +3113,7 @@ ${hasJsonbColumns ? ` /**
3103
3113
  orderBy?: string | string[];
3104
3114
  order?: "asc" | "desc" | ("asc" | "desc")[];
3105
3115
  distinctOn?: string | string[];
3116
+ includeSoftDeleted?: boolean;
3106
3117
  }): Promise<PaginatedResponse<Select${Type}<TJsonb> | Partial<Select${Type}<TJsonb>>>> {
3107
3118
  return this.post<PaginatedResponse<Select${Type}<TJsonb> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3108
3119
  }` : ` /**
@@ -3126,6 +3137,7 @@ ${hasJsonbColumns ? ` /**
3126
3137
  orderBy?: string | string[];
3127
3138
  order?: "asc" | "desc" | ("asc" | "desc")[];
3128
3139
  distinctOn?: string | string[];
3140
+ includeSoftDeleted?: boolean;
3129
3141
  }): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3130
3142
  /**
3131
3143
  * List ${table.name} records with field exclusion
@@ -3148,6 +3160,7 @@ ${hasJsonbColumns ? ` /**
3148
3160
  orderBy?: string | string[];
3149
3161
  order?: "asc" | "desc" | ("asc" | "desc")[];
3150
3162
  distinctOn?: string | string[];
3163
+ includeSoftDeleted?: boolean;
3151
3164
  }): Promise<PaginatedResponse<Partial<Select${Type}> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3152
3165
  /**
3153
3166
  * List ${table.name} records with pagination and filtering
@@ -3175,6 +3188,7 @@ ${hasJsonbColumns ? ` /**
3175
3188
  orderBy?: string | string[];
3176
3189
  order?: "asc" | "desc" | ("asc" | "desc")[];
3177
3190
  distinctOn?: string | string[];
3191
+ includeSoftDeleted?: boolean;
3178
3192
  }): Promise<PaginatedResponse<${Type}WithIncludes<TInclude> & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>;
3179
3193
  async list(params?: {
3180
3194
  include?: ${Type}IncludeSpec;
@@ -3193,6 +3207,7 @@ ${hasJsonbColumns ? ` /**
3193
3207
  orderBy?: string | string[];
3194
3208
  order?: "asc" | "desc" | ("asc" | "desc")[];
3195
3209
  distinctOn?: string | string[];
3210
+ includeSoftDeleted?: boolean;
3196
3211
  }): Promise<PaginatedResponse<Select${Type} | Partial<Select${Type}>>> {
3197
3212
  return this.post<PaginatedResponse<Select${Type} & { _similarity?: number }${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3198
3213
  }`}
@@ -5019,7 +5034,8 @@ export async function createRecord(
5019
5034
  */
5020
5035
  export async function getByPk(
5021
5036
  ctx: OperationContext,
5022
- pkValues: any[]
5037
+ pkValues: any[],
5038
+ opts?: { includeSoftDeleted?: boolean }
5023
5039
  ): Promise<{ data?: any; error?: string; status: number }> {
5024
5040
  try {
5025
5041
  const hasCompositePk = ctx.pkColumns.length > 1;
@@ -5028,7 +5044,8 @@ export async function getByPk(
5028
5044
  : \`"\${ctx.pkColumns[0]}" = $1\`;
5029
5045
 
5030
5046
  const columns = buildColumnList(ctx.select, ctx.exclude, ctx.allColumnNames);
5031
- const text = \`SELECT \${columns} FROM "\${ctx.table}" WHERE \${wherePkSql} LIMIT 1\`;
5047
+ const softDeleteFilter = ctx.softDeleteColumn && !opts?.includeSoftDeleted ? \` AND "\${ctx.softDeleteColumn}" IS NULL\` : "";
5048
+ const text = \`SELECT \${columns} FROM "\${ctx.table}" WHERE \${wherePkSql}\${softDeleteFilter} LIMIT 1\`;
5032
5049
  log.debug(\`GET \${ctx.table} by PK:\`, pkValues, "SQL:", text);
5033
5050
 
5034
5051
  const { rows } = await ctx.pg.query(text, pkValues);
@@ -5401,6 +5418,7 @@ export async function listRecords(
5401
5418
  orderBy?: string | string[];
5402
5419
  order?: "asc" | "desc" | ("asc" | "desc")[];
5403
5420
  distinctOn?: string | string[];
5421
+ includeSoftDeleted?: boolean;
5404
5422
  vector?: {
5405
5423
  field: string;
5406
5424
  query: number[];
@@ -5411,7 +5429,7 @@ export async function listRecords(
5411
5429
  }
5412
5430
  ): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
5413
5431
  try {
5414
- const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, trigram, distinctOn } = params;
5432
+ const { where: whereClause, limit = 50, offset = 0, include, orderBy, order, vector, trigram, distinctOn, includeSoftDeleted } = params;
5415
5433
 
5416
5434
  // DISTINCT ON support
5417
5435
  const distinctCols: string[] | null = distinctOn ? (Array.isArray(distinctOn) ? distinctOn : [distinctOn]) : null;
@@ -5447,8 +5465,8 @@ export async function listRecords(
5447
5465
  const whereParts: string[] = [];
5448
5466
  let whereParams: any[] = [];
5449
5467
 
5450
- // Add soft delete filter if applicable
5451
- if (ctx.softDeleteColumn) {
5468
+ // Add soft delete filter if applicable (skip if caller opts into seeing soft-deleted records)
5469
+ if (ctx.softDeleteColumn && !includeSoftDeleted) {
5452
5470
  whereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
5453
5471
  }
5454
5472
 
@@ -5486,7 +5504,7 @@ export async function listRecords(
5486
5504
 
5487
5505
  if (hasQueryParam && !hasThreshold && whereParams.length > 0) {
5488
5506
  const countWhereParts: string[] = [];
5489
- if (ctx.softDeleteColumn) {
5507
+ if (ctx.softDeleteColumn && !includeSoftDeleted) {
5490
5508
  countWhereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
5491
5509
  }
5492
5510
  if (whereClause) {
@@ -6457,6 +6475,12 @@ init_utils();
6457
6475
  var __filename2 = fileURLToPath(import.meta.url);
6458
6476
  var __dirname2 = dirname2(__filename2);
6459
6477
  var { version: CLI_VERSION } = JSON.parse(readFileSync(join2(__dirname2, "../package.json"), "utf-8"));
6478
+ function resolveSoftDeleteColumn(cfg, tableName) {
6479
+ const overrides = cfg.softDeleteColumnOverrides;
6480
+ if (overrides && tableName in overrides)
6481
+ return overrides[tableName] ?? null;
6482
+ return cfg.softDeleteColumn ?? null;
6483
+ }
6460
6484
  async function generate(configPath) {
6461
6485
  if (!existsSync2(configPath)) {
6462
6486
  throw new Error(`Config file not found: ${configPath}
@@ -6556,7 +6580,7 @@ async function generate(configPath) {
6556
6580
  let routeContent;
6557
6581
  if (serverFramework === "hono") {
6558
6582
  routeContent = emitHonoRoutes(table, graph, {
6559
- softDeleteColumn: cfg.softDeleteColumn || null,
6583
+ softDeleteColumn: resolveSoftDeleteColumn(cfg, table.name),
6560
6584
  includeMethodsDepth: cfg.includeMethodsDepth || 2,
6561
6585
  authStrategy: getAuthStrategy(normalizedAuth),
6562
6586
  useJsExtensions: cfg.useJsExtensions,
@@ -6709,5 +6733,6 @@ async function generate(configPath) {
6709
6733
  console.log(` Then run: postgresdk pull`);
6710
6734
  }
6711
6735
  export {
6736
+ resolveSoftDeleteColumn,
6712
6737
  generate
6713
6738
  };
package/dist/types.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface Config {
23
23
  server: string;
24
24
  };
25
25
  softDeleteColumn?: string | null;
26
+ /** Per-table overrides for soft delete column. Use `null` to disable soft deletes for a specific table. */
27
+ softDeleteColumnOverrides?: Record<string, string | null>;
26
28
  dateType?: "date" | "string";
27
29
  numericMode?: "string" | "number" | "auto";
28
30
  includeMethodsDepth?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.18.21",
3
+ "version": "0.18.23",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "scripts": {
24
24
  "build": "bun build src/cli.ts src/index.ts --outdir dist --target node --format esm --external=pg --external=zod --external=hono --external=prompts --external=node:* && tsc -p tsconfig.build.json --emitDeclarationOnly",
25
- "test": "bun test: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 && bun test test/test-jsonb-array-serialization.test.ts && bun test test/test-trigram-search.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 && bun test test/test-jsonb-array-serialization.test.ts && bun test test/test-trigram-search.test.ts && bun test test/test-soft-delete-config.test.ts",
26
26
  "test:write-files": "bun test/test-write-files-if-changed.ts",
27
27
  "test:init": "bun test/test-init.ts",
28
28
  "test:gen": "bun test/test-gen.ts",