postgresdk 0.16.1 → 0.16.4

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.
Files changed (3) hide show
  1. package/dist/cli.js +163 -90
  2. package/dist/index.js +163 -90
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1035,7 +1035,7 @@ function postgresTypeToTsType(column, enums) {
1035
1035
  return "string";
1036
1036
  case "json":
1037
1037
  case "jsonb":
1038
- return "Record<string, any>";
1038
+ return "JsonValue";
1039
1039
  case "uuid":
1040
1040
  return "string";
1041
1041
  case "text[]":
@@ -2940,9 +2940,14 @@ export interface PaginatedResponse<T> {
2940
2940
 
2941
2941
  // src/emit-routes-hono.ts
2942
2942
  init_utils();
2943
+ function isVectorType(pgType) {
2944
+ const t = pgType.toLowerCase();
2945
+ return t === "vector" || t === "halfvec" || t === "sparsevec" || t === "bit";
2946
+ }
2943
2947
  function emitHonoRoutes(table, _graph, opts) {
2944
2948
  const fileTableName = table.name;
2945
2949
  const Type = pascal(table.name);
2950
+ const hasVectorColumns = table.columns.some((c) => isVectorType(c.pgType));
2946
2951
  const rawPk = table.pk;
2947
2952
  const pkCols = Array.isArray(rawPk) ? rawPk : rawPk ? [rawPk] : [];
2948
2953
  const safePkCols = pkCols.length ? pkCols : ["id"];
@@ -2978,13 +2983,13 @@ const listSchema = z.object({
2978
2983
  limit: z.number().int().positive().max(1000).optional(),
2979
2984
  offset: z.number().int().min(0).optional(),
2980
2985
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
2981
- order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
2986
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),${hasVectorColumns ? `
2982
2987
  vector: z.object({
2983
2988
  field: z.string(),
2984
2989
  query: z.array(z.number()),
2985
2990
  metric: z.enum(["cosine", "l2", "inner"]).optional(),
2986
2991
  maxDistance: z.number().optional()
2987
- }).optional()
2992
+ }).optional()` : ""}
2988
2993
  });
2989
2994
 
2990
2995
  /**
@@ -3166,14 +3171,25 @@ ${hasAuth ? `
3166
3171
  // src/emit-client.ts
3167
3172
  init_utils();
3168
3173
  init_emit_include_methods();
3169
- function isVectorType(pgType) {
3174
+ function isVectorType2(pgType) {
3170
3175
  const t = pgType.toLowerCase();
3171
3176
  return t === "vector" || t === "halfvec" || t === "sparsevec" || t === "bit";
3172
3177
  }
3178
+ function isJsonbType(pgType) {
3179
+ const t = pgType.toLowerCase();
3180
+ return t === "json" || t === "jsonb";
3181
+ }
3173
3182
  function emitClient(table, graph, opts, model) {
3174
3183
  const Type = pascal(table.name);
3175
3184
  const ext = opts.useJsExtensions ? ".js" : "";
3176
- const hasVectorColumns = table.columns.some((c) => isVectorType(c.pgType));
3185
+ const hasVectorColumns = table.columns.some((c) => isVectorType2(c.pgType));
3186
+ const hasJsonbColumns = table.columns.some((c) => isJsonbType(c.pgType));
3187
+ if (process.env.SDK_DEBUG) {
3188
+ const vectorCols = table.columns.filter((c) => isVectorType2(c.pgType));
3189
+ if (vectorCols.length > 0) {
3190
+ console.log(`[DEBUG] Table ${table.name}: Found ${vectorCols.length} vector columns:`, vectorCols.map((c) => `${c.name} (${c.pgType})`));
3191
+ }
3192
+ }
3177
3193
  const pkCols = Array.isArray(table.pk) ? table.pk : table.pk ? [table.pk] : [];
3178
3194
  const safePk = pkCols.length ? pkCols : ["id"];
3179
3195
  const hasCompositePk = safePk.length > 1;
@@ -3253,60 +3269,58 @@ ${typeImports}
3253
3269
  ${otherTableImports.join(`
3254
3270
  `)}
3255
3271
 
3256
- /**
3257
- * Helper type to merge JSONB type overrides into base types
3258
- * @example
3259
- * type UserWithMetadata = MergeJsonb<SelectUser, { metadata: { tags: string[] } }>;
3260
- */
3261
- type MergeJsonb<TBase, TJsonb> = Omit<TBase, keyof TJsonb> & TJsonb;
3262
-
3263
3272
  /**
3264
3273
  * Client for ${table.name} table operations
3265
3274
  */
3266
3275
  export class ${Type}Client extends BaseClient {
3267
3276
  private readonly resource = "/v1/${table.name}";
3268
3277
 
3269
- /**
3278
+ ${hasJsonbColumns ? ` /**
3270
3279
  * Create a new ${table.name} record
3271
3280
  * @param data - The data to insert
3272
3281
  * @returns The created record
3273
- */
3274
- async create(data: Insert${Type}): Promise<Select${Type}>;
3275
- /**
3276
- * Create a new ${table.name} record with JSONB type overrides
3277
- * @param data - The data to insert
3278
- * @returns The created record with typed JSONB fields
3279
3282
  * @example
3283
+ * // With JSONB type override:
3280
3284
  * type Metadata = { tags: string[]; prefs: { theme: 'light' | 'dark' } };
3281
3285
  * const user = await client.create<{ metadata: Metadata }>({ name: 'Alice', metadata: { tags: [], prefs: { theme: 'light' } } });
3282
3286
  */
3283
- async create<TJsonb extends Partial<Select${Type}>>(
3284
- data: MergeJsonb<Insert${Type}, TJsonb>
3285
- ): Promise<MergeJsonb<Select${Type}, TJsonb>>;
3286
- async create(data: any): Promise<any> {
3287
- return this.post<any>(this.resource, data);
3288
- }
3287
+ async create<TJsonb extends Partial<Select${Type}> = {}>(
3288
+ data: Insert${Type}<TJsonb>
3289
+ ): Promise<Select${Type}<TJsonb>> {
3290
+ return this.post<Select${Type}<TJsonb>>(this.resource, data);
3291
+ }` : ` /**
3292
+ * Create a new ${table.name} record
3293
+ * @param data - The data to insert
3294
+ * @returns The created record
3295
+ */
3296
+ async create(data: Insert${Type}): Promise<Select${Type}> {
3297
+ return this.post<Select${Type}>(this.resource, data);
3298
+ }`}
3289
3299
 
3290
- /**
3300
+ ${hasJsonbColumns ? ` /**
3291
3301
  * Get a ${table.name} record by primary key
3292
3302
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3293
3303
  * @returns The record if found, null otherwise
3304
+ * @example
3305
+ * // With JSONB type override:
3306
+ * const user = await client.getByPk<{ metadata: Metadata }>('user-id');
3294
3307
  */
3295
- async getByPk(pk: ${pkType}): Promise<Select${Type} | null>;
3296
- /**
3297
- * Get a ${table.name} record by primary key with JSONB type overrides
3308
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(
3309
+ pk: ${pkType}
3310
+ ): Promise<Select${Type}<TJsonb> | null> {
3311
+ const path = ${pkPathExpr};
3312
+ return this.get<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`);
3313
+ }` : ` /**
3314
+ * Get a ${table.name} record by primary key
3298
3315
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3299
- * @returns The record with typed JSONB fields if found, null otherwise
3316
+ * @returns The record if found, null otherwise
3300
3317
  */
3301
- async getByPk<TJsonb extends Partial<Select${Type}>>(
3302
- pk: ${pkType}
3303
- ): Promise<MergeJsonb<Select${Type}, TJsonb> | null>;
3304
- async getByPk(pk: ${pkType}): Promise<any> {
3318
+ async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
3305
3319
  const path = ${pkPathExpr};
3306
- return this.get<any>(\`\${this.resource}/\${path}\`);
3307
- }
3320
+ return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
3321
+ }`}
3308
3322
 
3309
- /**
3323
+ ${hasJsonbColumns ? ` /**
3310
3324
  * List ${table.name} records with pagination and filtering
3311
3325
  * @param params - Query parameters
3312
3326
  * @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
@@ -3316,45 +3330,41 @@ export class ${Type}Client extends BaseClient {
3316
3330
  * @param params.offset - Number of records to skip for pagination
3317
3331
  * @param params.include - Related records to include (see listWith* methods for typed includes)
3318
3332
  * @returns Paginated results with data, total count, and hasMore flag
3333
+ * @example
3334
+ * // With JSONB type override:
3335
+ * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
3319
3336
  */
3320
- async list(params?: {
3321
- include?: any;
3322
- limit?: number;
3323
- offset?: number;
3324
- where?: Where<Select${Type}>;
3325
- orderBy?: string | string[];
3326
- order?: "asc" | "desc" | ("asc" | "desc")[];
3327
- }): Promise<PaginatedResponse<Select${Type}>>;${hasVectorColumns ? `
3328
- /**
3329
- * List ${table.name} records with vector similarity search
3330
- * @param params - Query parameters with vector search enabled
3331
- * @param params.vector - Vector similarity search configuration
3332
- * @returns Paginated results with _distance field included
3333
- */
3334
- async list(params: {
3337
+ async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
3335
3338
  include?: any;
3336
3339
  limit?: number;
3337
3340
  offset?: number;
3338
- where?: Where<Select${Type}>;
3339
- vector: {
3341
+ where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
3342
+ vector?: {
3340
3343
  field: string;
3341
3344
  query: number[];
3342
3345
  metric?: "cosine" | "l2" | "inner";
3343
3346
  maxDistance?: number;
3344
- };
3347
+ };` : ""}
3345
3348
  orderBy?: string | string[];
3346
3349
  order?: "asc" | "desc" | ("asc" | "desc")[];
3347
- }): Promise<PaginatedResponse<Select${Type} & { _distance: number }>>;` : ""}
3348
- /**
3349
- * List ${table.name} records with pagination and filtering, with JSONB type overrides
3350
- * @param params - Query parameters with typed JSONB fields in where clause
3351
- * @returns Paginated results with typed JSONB fields
3350
+ }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
3351
+ return this.post<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3352
+ }` : ` /**
3353
+ * List ${table.name} records with pagination and filtering
3354
+ * @param params - Query parameters
3355
+ * @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
3356
+ * @param params.orderBy - Column(s) to sort by
3357
+ * @param params.order - Sort direction(s): "asc" or "desc"
3358
+ * @param params.limit - Maximum number of records to return (default: 50, max: 100)
3359
+ * @param params.offset - Number of records to skip for pagination
3360
+ * @param params.include - Related records to include (see listWith* methods for typed includes)
3361
+ * @returns Paginated results with data, total count, and hasMore flag
3352
3362
  */
3353
- async list<TJsonb extends Partial<Select${Type}>>(params?: {
3363
+ async list(params?: {
3354
3364
  include?: any;
3355
3365
  limit?: number;
3356
3366
  offset?: number;
3357
- where?: Where<MergeJsonb<Select${Type}, TJsonb>>;${hasVectorColumns ? `
3367
+ where?: Where<Select${Type}>;${hasVectorColumns ? `
3358
3368
  vector?: {
3359
3369
  field: string;
3360
3370
  query: number[];
@@ -3363,51 +3373,58 @@ export class ${Type}Client extends BaseClient {
3363
3373
  };` : ""}
3364
3374
  orderBy?: string | string[];
3365
3375
  order?: "asc" | "desc" | ("asc" | "desc")[];
3366
- }): Promise<PaginatedResponse<MergeJsonb<Select${Type}, TJsonb>>>;
3367
- async list(params?: any): Promise<any> {
3368
- return this.post<any>(\`\${this.resource}/list\`, params ?? {});
3369
- }
3376
+ }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
3377
+ return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
3378
+ }`}
3370
3379
 
3371
- /**
3380
+ ${hasJsonbColumns ? ` /**
3372
3381
  * Update a ${table.name} record by primary key
3373
3382
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3374
3383
  * @param patch - Partial data to update
3375
3384
  * @returns The updated record if found, null otherwise
3385
+ * @example
3386
+ * // With JSONB type override:
3387
+ * const user = await client.update<{ metadata: Metadata }>('user-id', { metadata: { tags: ['new'] } });
3376
3388
  */
3377
- async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null>;
3378
- /**
3379
- * Update a ${table.name} record by primary key with JSONB type overrides
3389
+ async update<TJsonb extends Partial<Select${Type}> = {}>(
3390
+ pk: ${pkType},
3391
+ patch: Update${Type}<TJsonb>
3392
+ ): Promise<Select${Type}<TJsonb> | null> {
3393
+ const path = ${pkPathExpr};
3394
+ return this.patch<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`, patch);
3395
+ }` : ` /**
3396
+ * Update a ${table.name} record by primary key
3380
3397
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3381
- * @param patch - Partial data to update with typed JSONB fields
3382
- * @returns The updated record with typed JSONB fields if found, null otherwise
3398
+ * @param patch - Partial data to update
3399
+ * @returns The updated record if found, null otherwise
3383
3400
  */
3384
- async update<TJsonb extends Partial<Select${Type}>>(
3385
- pk: ${pkType},
3386
- patch: MergeJsonb<Update${Type}, TJsonb>
3387
- ): Promise<MergeJsonb<Select${Type}, TJsonb> | null>;
3388
- async update(pk: ${pkType}, patch: any): Promise<any> {
3401
+ async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
3389
3402
  const path = ${pkPathExpr};
3390
- return this.patch<any>(\`\${this.resource}/\${path}\`, patch);
3391
- }
3403
+ return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
3404
+ }`}
3392
3405
 
3393
- /**
3406
+ ${hasJsonbColumns ? ` /**
3394
3407
  * Delete a ${table.name} record by primary key
3395
3408
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3396
3409
  * @returns The deleted record if found, null otherwise
3410
+ * @example
3411
+ * // With JSONB type override:
3412
+ * const user = await client.delete<{ metadata: Metadata }>('user-id');
3397
3413
  */
3398
- async delete(pk: ${pkType}): Promise<Select${Type} | null>;
3399
- /**
3400
- * Delete a ${table.name} record by primary key with JSONB type overrides
3414
+ async delete<TJsonb extends Partial<Select${Type}> = {}>(
3415
+ pk: ${pkType}
3416
+ ): Promise<Select${Type}<TJsonb> | null> {
3417
+ const path = ${pkPathExpr};
3418
+ return this.del<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`);
3419
+ }` : ` /**
3420
+ * Delete a ${table.name} record by primary key
3401
3421
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
3402
- * @returns The deleted record with typed JSONB fields if found, null otherwise
3422
+ * @returns The deleted record if found, null otherwise
3403
3423
  */
3404
- async delete<TJsonb extends Partial<Select${Type}>>(
3405
- pk: ${pkType}
3406
- ): Promise<MergeJsonb<Select${Type}, TJsonb> | null>;
3407
- async delete(pk: ${pkType}): Promise<any> {
3424
+ async delete(pk: ${pkType}): Promise<Select${Type} | null> {
3408
3425
  const path = ${pkPathExpr};
3409
- return this.del<any>(\`\${this.resource}/\${path}\`);
3410
- }
3426
+ return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
3427
+ }`}
3411
3428
  ${includeMethodsCode}}
3412
3429
  `;
3413
3430
  }
@@ -3975,9 +3992,14 @@ function tsTypeFor(pgType, opts, enums) {
3975
3992
  return "unknown";
3976
3993
  return "string";
3977
3994
  }
3995
+ function isJsonbType2(pgType) {
3996
+ const t = pgType.toLowerCase();
3997
+ return t === "json" || t === "jsonb";
3998
+ }
3978
3999
  var pascal2 = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
3979
4000
  function emitTypes(table, opts, enums) {
3980
4001
  const Type = pascal2(table.name);
4002
+ const hasJsonbColumns = table.columns.some((col) => isJsonbType2(col.pgType));
3981
4003
  const insertFields = table.columns.map((col) => {
3982
4004
  const base = tsTypeFor(col.pgType, opts, enums);
3983
4005
  const optional = col.hasDefault || col.nullable ? "?" : "";
@@ -3991,6 +4013,57 @@ function emitTypes(table, opts, enums) {
3991
4013
  return ` ${col.name}: ${valueType};`;
3992
4014
  }).join(`
3993
4015
  `);
4016
+ const baseInsertType = `type _Insert${Type}Base = {
4017
+ ${insertFields}
4018
+ };`;
4019
+ const baseSelectType = `type _Select${Type}Base = {
4020
+ ${selectFields}
4021
+ };`;
4022
+ if (hasJsonbColumns) {
4023
+ return `/**
4024
+ * AUTO-GENERATED FILE - DO NOT EDIT
4025
+ *
4026
+ * This file was automatically generated by PostgreSDK.
4027
+ * Any manual changes will be overwritten on the next generation.
4028
+ *
4029
+ * To make changes, modify your schema or configuration and regenerate.
4030
+ */
4031
+
4032
+ ${baseInsertType}
4033
+
4034
+ ${baseSelectType}
4035
+
4036
+ /**
4037
+ * Type for inserting a new ${table.name} record.
4038
+ * Fields with defaults or nullable columns are optional.
4039
+ *
4040
+ * Generic parameter TJsonb allows overriding JSONB column types:
4041
+ * @example Insert${Type}<{ metadata: MyMetadataType }>
4042
+ */
4043
+ export type Insert${Type}<TJsonb extends Partial<_Insert${Type}Base> = {}> =
4044
+ Omit<_Insert${Type}Base, keyof TJsonb> & TJsonb;
4045
+
4046
+ /**
4047
+ * Type for updating an existing ${table.name} record.
4048
+ * All fields are optional, allowing partial updates.
4049
+ *
4050
+ * Generic parameter TJsonb allows overriding JSONB column types:
4051
+ * @example Update${Type}<{ metadata: MyMetadataType }>
4052
+ */
4053
+ export type Update${Type}<TJsonb extends Partial<_Select${Type}Base> = {}> =
4054
+ Partial<Omit<_Insert${Type}Base, keyof TJsonb> & TJsonb>;
4055
+
4056
+ /**
4057
+ * Type representing a ${table.name} record from the database.
4058
+ * All fields are included as returned by SELECT queries.
4059
+ *
4060
+ * Generic parameter TJsonb allows overriding JSONB column types:
4061
+ * @example Select${Type}<{ metadata: MyMetadataType }>
4062
+ */
4063
+ export type Select${Type}<TJsonb extends Partial<_Select${Type}Base> = {}> =
4064
+ Omit<_Select${Type}Base, keyof TJsonb> & TJsonb;
4065
+ `;
4066
+ }
3994
4067
  return `/**
3995
4068
  * AUTO-GENERATED FILE - DO NOT EDIT
3996
4069
  *
package/dist/index.js CHANGED
@@ -1034,7 +1034,7 @@ function postgresTypeToTsType(column, enums) {
1034
1034
  return "string";
1035
1035
  case "json":
1036
1036
  case "jsonb":
1037
- return "Record<string, any>";
1037
+ return "JsonValue";
1038
1038
  case "uuid":
1039
1039
  return "string";
1040
1040
  case "text[]":
@@ -2111,9 +2111,14 @@ export interface PaginatedResponse<T> {
2111
2111
 
2112
2112
  // src/emit-routes-hono.ts
2113
2113
  init_utils();
2114
+ function isVectorType(pgType) {
2115
+ const t = pgType.toLowerCase();
2116
+ return t === "vector" || t === "halfvec" || t === "sparsevec" || t === "bit";
2117
+ }
2114
2118
  function emitHonoRoutes(table, _graph, opts) {
2115
2119
  const fileTableName = table.name;
2116
2120
  const Type = pascal(table.name);
2121
+ const hasVectorColumns = table.columns.some((c) => isVectorType(c.pgType));
2117
2122
  const rawPk = table.pk;
2118
2123
  const pkCols = Array.isArray(rawPk) ? rawPk : rawPk ? [rawPk] : [];
2119
2124
  const safePkCols = pkCols.length ? pkCols : ["id"];
@@ -2149,13 +2154,13 @@ const listSchema = z.object({
2149
2154
  limit: z.number().int().positive().max(1000).optional(),
2150
2155
  offset: z.number().int().min(0).optional(),
2151
2156
  orderBy: z.union([columnEnum, z.array(columnEnum)]).optional(),
2152
- order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),
2157
+ order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional(),${hasVectorColumns ? `
2153
2158
  vector: z.object({
2154
2159
  field: z.string(),
2155
2160
  query: z.array(z.number()),
2156
2161
  metric: z.enum(["cosine", "l2", "inner"]).optional(),
2157
2162
  maxDistance: z.number().optional()
2158
- }).optional()
2163
+ }).optional()` : ""}
2159
2164
  });
2160
2165
 
2161
2166
  /**
@@ -2337,14 +2342,25 @@ ${hasAuth ? `
2337
2342
  // src/emit-client.ts
2338
2343
  init_utils();
2339
2344
  init_emit_include_methods();
2340
- function isVectorType(pgType) {
2345
+ function isVectorType2(pgType) {
2341
2346
  const t = pgType.toLowerCase();
2342
2347
  return t === "vector" || t === "halfvec" || t === "sparsevec" || t === "bit";
2343
2348
  }
2349
+ function isJsonbType(pgType) {
2350
+ const t = pgType.toLowerCase();
2351
+ return t === "json" || t === "jsonb";
2352
+ }
2344
2353
  function emitClient(table, graph, opts, model) {
2345
2354
  const Type = pascal(table.name);
2346
2355
  const ext = opts.useJsExtensions ? ".js" : "";
2347
- const hasVectorColumns = table.columns.some((c) => isVectorType(c.pgType));
2356
+ const hasVectorColumns = table.columns.some((c) => isVectorType2(c.pgType));
2357
+ const hasJsonbColumns = table.columns.some((c) => isJsonbType(c.pgType));
2358
+ if (process.env.SDK_DEBUG) {
2359
+ const vectorCols = table.columns.filter((c) => isVectorType2(c.pgType));
2360
+ if (vectorCols.length > 0) {
2361
+ console.log(`[DEBUG] Table ${table.name}: Found ${vectorCols.length} vector columns:`, vectorCols.map((c) => `${c.name} (${c.pgType})`));
2362
+ }
2363
+ }
2348
2364
  const pkCols = Array.isArray(table.pk) ? table.pk : table.pk ? [table.pk] : [];
2349
2365
  const safePk = pkCols.length ? pkCols : ["id"];
2350
2366
  const hasCompositePk = safePk.length > 1;
@@ -2424,60 +2440,58 @@ ${typeImports}
2424
2440
  ${otherTableImports.join(`
2425
2441
  `)}
2426
2442
 
2427
- /**
2428
- * Helper type to merge JSONB type overrides into base types
2429
- * @example
2430
- * type UserWithMetadata = MergeJsonb<SelectUser, { metadata: { tags: string[] } }>;
2431
- */
2432
- type MergeJsonb<TBase, TJsonb> = Omit<TBase, keyof TJsonb> & TJsonb;
2433
-
2434
2443
  /**
2435
2444
  * Client for ${table.name} table operations
2436
2445
  */
2437
2446
  export class ${Type}Client extends BaseClient {
2438
2447
  private readonly resource = "/v1/${table.name}";
2439
2448
 
2440
- /**
2449
+ ${hasJsonbColumns ? ` /**
2441
2450
  * Create a new ${table.name} record
2442
2451
  * @param data - The data to insert
2443
2452
  * @returns The created record
2444
- */
2445
- async create(data: Insert${Type}): Promise<Select${Type}>;
2446
- /**
2447
- * Create a new ${table.name} record with JSONB type overrides
2448
- * @param data - The data to insert
2449
- * @returns The created record with typed JSONB fields
2450
2453
  * @example
2454
+ * // With JSONB type override:
2451
2455
  * type Metadata = { tags: string[]; prefs: { theme: 'light' | 'dark' } };
2452
2456
  * const user = await client.create<{ metadata: Metadata }>({ name: 'Alice', metadata: { tags: [], prefs: { theme: 'light' } } });
2453
2457
  */
2454
- async create<TJsonb extends Partial<Select${Type}>>(
2455
- data: MergeJsonb<Insert${Type}, TJsonb>
2456
- ): Promise<MergeJsonb<Select${Type}, TJsonb>>;
2457
- async create(data: any): Promise<any> {
2458
- return this.post<any>(this.resource, data);
2459
- }
2458
+ async create<TJsonb extends Partial<Select${Type}> = {}>(
2459
+ data: Insert${Type}<TJsonb>
2460
+ ): Promise<Select${Type}<TJsonb>> {
2461
+ return this.post<Select${Type}<TJsonb>>(this.resource, data);
2462
+ }` : ` /**
2463
+ * Create a new ${table.name} record
2464
+ * @param data - The data to insert
2465
+ * @returns The created record
2466
+ */
2467
+ async create(data: Insert${Type}): Promise<Select${Type}> {
2468
+ return this.post<Select${Type}>(this.resource, data);
2469
+ }`}
2460
2470
 
2461
- /**
2471
+ ${hasJsonbColumns ? ` /**
2462
2472
  * Get a ${table.name} record by primary key
2463
2473
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2464
2474
  * @returns The record if found, null otherwise
2475
+ * @example
2476
+ * // With JSONB type override:
2477
+ * const user = await client.getByPk<{ metadata: Metadata }>('user-id');
2465
2478
  */
2466
- async getByPk(pk: ${pkType}): Promise<Select${Type} | null>;
2467
- /**
2468
- * Get a ${table.name} record by primary key with JSONB type overrides
2479
+ async getByPk<TJsonb extends Partial<Select${Type}> = {}>(
2480
+ pk: ${pkType}
2481
+ ): Promise<Select${Type}<TJsonb> | null> {
2482
+ const path = ${pkPathExpr};
2483
+ return this.get<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`);
2484
+ }` : ` /**
2485
+ * Get a ${table.name} record by primary key
2469
2486
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2470
- * @returns The record with typed JSONB fields if found, null otherwise
2487
+ * @returns The record if found, null otherwise
2471
2488
  */
2472
- async getByPk<TJsonb extends Partial<Select${Type}>>(
2473
- pk: ${pkType}
2474
- ): Promise<MergeJsonb<Select${Type}, TJsonb> | null>;
2475
- async getByPk(pk: ${pkType}): Promise<any> {
2489
+ async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
2476
2490
  const path = ${pkPathExpr};
2477
- return this.get<any>(\`\${this.resource}/\${path}\`);
2478
- }
2491
+ return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
2492
+ }`}
2479
2493
 
2480
- /**
2494
+ ${hasJsonbColumns ? ` /**
2481
2495
  * List ${table.name} records with pagination and filtering
2482
2496
  * @param params - Query parameters
2483
2497
  * @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
@@ -2487,45 +2501,41 @@ export class ${Type}Client extends BaseClient {
2487
2501
  * @param params.offset - Number of records to skip for pagination
2488
2502
  * @param params.include - Related records to include (see listWith* methods for typed includes)
2489
2503
  * @returns Paginated results with data, total count, and hasMore flag
2504
+ * @example
2505
+ * // With JSONB type override:
2506
+ * const users = await client.list<{ metadata: Metadata }>({ where: { status: 'active' } });
2490
2507
  */
2491
- async list(params?: {
2492
- include?: any;
2493
- limit?: number;
2494
- offset?: number;
2495
- where?: Where<Select${Type}>;
2496
- orderBy?: string | string[];
2497
- order?: "asc" | "desc" | ("asc" | "desc")[];
2498
- }): Promise<PaginatedResponse<Select${Type}>>;${hasVectorColumns ? `
2499
- /**
2500
- * List ${table.name} records with vector similarity search
2501
- * @param params - Query parameters with vector search enabled
2502
- * @param params.vector - Vector similarity search configuration
2503
- * @returns Paginated results with _distance field included
2504
- */
2505
- async list(params: {
2508
+ async list<TJsonb extends Partial<Select${Type}> = {}>(params?: {
2506
2509
  include?: any;
2507
2510
  limit?: number;
2508
2511
  offset?: number;
2509
- where?: Where<Select${Type}>;
2510
- vector: {
2512
+ where?: Where<Select${Type}<TJsonb>>;${hasVectorColumns ? `
2513
+ vector?: {
2511
2514
  field: string;
2512
2515
  query: number[];
2513
2516
  metric?: "cosine" | "l2" | "inner";
2514
2517
  maxDistance?: number;
2515
- };
2518
+ };` : ""}
2516
2519
  orderBy?: string | string[];
2517
2520
  order?: "asc" | "desc" | ("asc" | "desc")[];
2518
- }): Promise<PaginatedResponse<Select${Type} & { _distance: number }>>;` : ""}
2519
- /**
2520
- * List ${table.name} records with pagination and filtering, with JSONB type overrides
2521
- * @param params - Query parameters with typed JSONB fields in where clause
2522
- * @returns Paginated results with typed JSONB fields
2521
+ }): Promise<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
2522
+ return this.post<PaginatedResponse<Select${Type}<TJsonb>${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
2523
+ }` : ` /**
2524
+ * List ${table.name} records with pagination and filtering
2525
+ * @param params - Query parameters
2526
+ * @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
2527
+ * @param params.orderBy - Column(s) to sort by
2528
+ * @param params.order - Sort direction(s): "asc" or "desc"
2529
+ * @param params.limit - Maximum number of records to return (default: 50, max: 100)
2530
+ * @param params.offset - Number of records to skip for pagination
2531
+ * @param params.include - Related records to include (see listWith* methods for typed includes)
2532
+ * @returns Paginated results with data, total count, and hasMore flag
2523
2533
  */
2524
- async list<TJsonb extends Partial<Select${Type}>>(params?: {
2534
+ async list(params?: {
2525
2535
  include?: any;
2526
2536
  limit?: number;
2527
2537
  offset?: number;
2528
- where?: Where<MergeJsonb<Select${Type}, TJsonb>>;${hasVectorColumns ? `
2538
+ where?: Where<Select${Type}>;${hasVectorColumns ? `
2529
2539
  vector?: {
2530
2540
  field: string;
2531
2541
  query: number[];
@@ -2534,51 +2544,58 @@ export class ${Type}Client extends BaseClient {
2534
2544
  };` : ""}
2535
2545
  orderBy?: string | string[];
2536
2546
  order?: "asc" | "desc" | ("asc" | "desc")[];
2537
- }): Promise<PaginatedResponse<MergeJsonb<Select${Type}, TJsonb>>>;
2538
- async list(params?: any): Promise<any> {
2539
- return this.post<any>(\`\${this.resource}/list\`, params ?? {});
2540
- }
2547
+ }): Promise<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>> {
2548
+ return this.post<PaginatedResponse<Select${Type}${hasVectorColumns ? " & { _distance?: number }" : ""}>>(\`\${this.resource}/list\`, params ?? {});
2549
+ }`}
2541
2550
 
2542
- /**
2551
+ ${hasJsonbColumns ? ` /**
2543
2552
  * Update a ${table.name} record by primary key
2544
2553
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2545
2554
  * @param patch - Partial data to update
2546
2555
  * @returns The updated record if found, null otherwise
2556
+ * @example
2557
+ * // With JSONB type override:
2558
+ * const user = await client.update<{ metadata: Metadata }>('user-id', { metadata: { tags: ['new'] } });
2547
2559
  */
2548
- async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null>;
2549
- /**
2550
- * Update a ${table.name} record by primary key with JSONB type overrides
2560
+ async update<TJsonb extends Partial<Select${Type}> = {}>(
2561
+ pk: ${pkType},
2562
+ patch: Update${Type}<TJsonb>
2563
+ ): Promise<Select${Type}<TJsonb> | null> {
2564
+ const path = ${pkPathExpr};
2565
+ return this.patch<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`, patch);
2566
+ }` : ` /**
2567
+ * Update a ${table.name} record by primary key
2551
2568
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2552
- * @param patch - Partial data to update with typed JSONB fields
2553
- * @returns The updated record with typed JSONB fields if found, null otherwise
2569
+ * @param patch - Partial data to update
2570
+ * @returns The updated record if found, null otherwise
2554
2571
  */
2555
- async update<TJsonb extends Partial<Select${Type}>>(
2556
- pk: ${pkType},
2557
- patch: MergeJsonb<Update${Type}, TJsonb>
2558
- ): Promise<MergeJsonb<Select${Type}, TJsonb> | null>;
2559
- async update(pk: ${pkType}, patch: any): Promise<any> {
2572
+ async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
2560
2573
  const path = ${pkPathExpr};
2561
- return this.patch<any>(\`\${this.resource}/\${path}\`, patch);
2562
- }
2574
+ return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
2575
+ }`}
2563
2576
 
2564
- /**
2577
+ ${hasJsonbColumns ? ` /**
2565
2578
  * Delete a ${table.name} record by primary key
2566
2579
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2567
2580
  * @returns The deleted record if found, null otherwise
2581
+ * @example
2582
+ * // With JSONB type override:
2583
+ * const user = await client.delete<{ metadata: Metadata }>('user-id');
2568
2584
  */
2569
- async delete(pk: ${pkType}): Promise<Select${Type} | null>;
2570
- /**
2571
- * Delete a ${table.name} record by primary key with JSONB type overrides
2585
+ async delete<TJsonb extends Partial<Select${Type}> = {}>(
2586
+ pk: ${pkType}
2587
+ ): Promise<Select${Type}<TJsonb> | null> {
2588
+ const path = ${pkPathExpr};
2589
+ return this.del<Select${Type}<TJsonb> | null>(\`\${this.resource}/\${path}\`);
2590
+ }` : ` /**
2591
+ * Delete a ${table.name} record by primary key
2572
2592
  * @param pk - The primary key value${hasCompositePk ? "s" : ""}
2573
- * @returns The deleted record with typed JSONB fields if found, null otherwise
2593
+ * @returns The deleted record if found, null otherwise
2574
2594
  */
2575
- async delete<TJsonb extends Partial<Select${Type}>>(
2576
- pk: ${pkType}
2577
- ): Promise<MergeJsonb<Select${Type}, TJsonb> | null>;
2578
- async delete(pk: ${pkType}): Promise<any> {
2595
+ async delete(pk: ${pkType}): Promise<Select${Type} | null> {
2579
2596
  const path = ${pkPathExpr};
2580
- return this.del<any>(\`\${this.resource}/\${path}\`);
2581
- }
2597
+ return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
2598
+ }`}
2582
2599
  ${includeMethodsCode}}
2583
2600
  `;
2584
2601
  }
@@ -3146,9 +3163,14 @@ function tsTypeFor(pgType, opts, enums) {
3146
3163
  return "unknown";
3147
3164
  return "string";
3148
3165
  }
3166
+ function isJsonbType2(pgType) {
3167
+ const t = pgType.toLowerCase();
3168
+ return t === "json" || t === "jsonb";
3169
+ }
3149
3170
  var pascal2 = (s) => s.split(/[_\s-]+/).map((w) => w?.[0] ? w[0].toUpperCase() + w.slice(1) : "").join("");
3150
3171
  function emitTypes(table, opts, enums) {
3151
3172
  const Type = pascal2(table.name);
3173
+ const hasJsonbColumns = table.columns.some((col) => isJsonbType2(col.pgType));
3152
3174
  const insertFields = table.columns.map((col) => {
3153
3175
  const base = tsTypeFor(col.pgType, opts, enums);
3154
3176
  const optional = col.hasDefault || col.nullable ? "?" : "";
@@ -3162,6 +3184,57 @@ function emitTypes(table, opts, enums) {
3162
3184
  return ` ${col.name}: ${valueType};`;
3163
3185
  }).join(`
3164
3186
  `);
3187
+ const baseInsertType = `type _Insert${Type}Base = {
3188
+ ${insertFields}
3189
+ };`;
3190
+ const baseSelectType = `type _Select${Type}Base = {
3191
+ ${selectFields}
3192
+ };`;
3193
+ if (hasJsonbColumns) {
3194
+ return `/**
3195
+ * AUTO-GENERATED FILE - DO NOT EDIT
3196
+ *
3197
+ * This file was automatically generated by PostgreSDK.
3198
+ * Any manual changes will be overwritten on the next generation.
3199
+ *
3200
+ * To make changes, modify your schema or configuration and regenerate.
3201
+ */
3202
+
3203
+ ${baseInsertType}
3204
+
3205
+ ${baseSelectType}
3206
+
3207
+ /**
3208
+ * Type for inserting a new ${table.name} record.
3209
+ * Fields with defaults or nullable columns are optional.
3210
+ *
3211
+ * Generic parameter TJsonb allows overriding JSONB column types:
3212
+ * @example Insert${Type}<{ metadata: MyMetadataType }>
3213
+ */
3214
+ export type Insert${Type}<TJsonb extends Partial<_Insert${Type}Base> = {}> =
3215
+ Omit<_Insert${Type}Base, keyof TJsonb> & TJsonb;
3216
+
3217
+ /**
3218
+ * Type for updating an existing ${table.name} record.
3219
+ * All fields are optional, allowing partial updates.
3220
+ *
3221
+ * Generic parameter TJsonb allows overriding JSONB column types:
3222
+ * @example Update${Type}<{ metadata: MyMetadataType }>
3223
+ */
3224
+ export type Update${Type}<TJsonb extends Partial<_Select${Type}Base> = {}> =
3225
+ Partial<Omit<_Insert${Type}Base, keyof TJsonb> & TJsonb>;
3226
+
3227
+ /**
3228
+ * Type representing a ${table.name} record from the database.
3229
+ * All fields are included as returned by SELECT queries.
3230
+ *
3231
+ * Generic parameter TJsonb allows overriding JSONB column types:
3232
+ * @example Select${Type}<{ metadata: MyMetadataType }>
3233
+ */
3234
+ export type Select${Type}<TJsonb extends Partial<_Select${Type}Base> = {}> =
3235
+ Omit<_Select${Type}Base, keyof TJsonb> & TJsonb;
3236
+ `;
3237
+ }
3165
3238
  return `/**
3166
3239
  * AUTO-GENERATED FILE - DO NOT EDIT
3167
3240
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresdk",
3
- "version": "0.16.1",
3
+ "version": "0.16.4",
4
4
  "description": "Generate a typed server/client SDK from a Postgres schema (includes, Zod, Hono).",
5
5
  "type": "module",
6
6
  "bin": {