postgresdk 0.12.1 → 0.13.0
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 +34 -6
- package/dist/cli.js +164 -39
- package/dist/index.js +164 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,8 @@ const user = await sdk.users.create({ name: "Bob", email: "bob@example.com" });
|
|
|
121
121
|
|
|
122
122
|
// Read
|
|
123
123
|
const user = await sdk.users.getByPk(123);
|
|
124
|
-
const
|
|
124
|
+
const result = await sdk.users.list();
|
|
125
|
+
const users = result.data; // Array of users
|
|
125
126
|
|
|
126
127
|
// Update
|
|
127
128
|
const updated = await sdk.users.update(123, { name: "Robert" });
|
|
@@ -136,17 +137,19 @@ Automatically handles relationships with the `include` parameter:
|
|
|
136
137
|
|
|
137
138
|
```typescript
|
|
138
139
|
// 1:N relationship - Get authors with their books
|
|
139
|
-
const
|
|
140
|
+
const authorsResult = await sdk.authors.list({
|
|
140
141
|
include: { books: true }
|
|
141
142
|
});
|
|
143
|
+
const authors = authorsResult.data;
|
|
142
144
|
|
|
143
145
|
// M:N relationship - Get books with their tags
|
|
144
|
-
const
|
|
146
|
+
const booksResult = await sdk.books.list({
|
|
145
147
|
include: { tags: true }
|
|
146
148
|
});
|
|
149
|
+
const books = booksResult.data;
|
|
147
150
|
|
|
148
151
|
// Nested includes - Get authors with books and their tags
|
|
149
|
-
const
|
|
152
|
+
const nestedResult = await sdk.authors.list({
|
|
150
153
|
include: {
|
|
151
154
|
books: {
|
|
152
155
|
include: {
|
|
@@ -155,13 +158,15 @@ const authors = await sdk.authors.list({
|
|
|
155
158
|
}
|
|
156
159
|
}
|
|
157
160
|
});
|
|
161
|
+
const authorsWithBooksAndTags = nestedResult.data;
|
|
158
162
|
```
|
|
159
163
|
|
|
160
164
|
### Filtering & Pagination
|
|
161
165
|
|
|
166
|
+
All `list()` methods return pagination metadata:
|
|
167
|
+
|
|
162
168
|
```typescript
|
|
163
|
-
|
|
164
|
-
const users = await sdk.users.list({
|
|
169
|
+
const result = await sdk.users.list({
|
|
165
170
|
where: { status: "active" },
|
|
166
171
|
orderBy: "created_at",
|
|
167
172
|
order: "desc",
|
|
@@ -169,6 +174,17 @@ const users = await sdk.users.list({
|
|
|
169
174
|
offset: 40
|
|
170
175
|
});
|
|
171
176
|
|
|
177
|
+
// Access results
|
|
178
|
+
result.data; // User[] - array of records
|
|
179
|
+
result.total; // number - total matching records
|
|
180
|
+
result.limit; // number - page size used
|
|
181
|
+
result.offset; // number - offset used
|
|
182
|
+
result.hasMore; // boolean - more pages available
|
|
183
|
+
|
|
184
|
+
// Calculate pagination info
|
|
185
|
+
const totalPages = Math.ceil(result.total / result.limit);
|
|
186
|
+
const currentPage = Math.floor(result.offset / result.limit) + 1;
|
|
187
|
+
|
|
172
188
|
// Multi-column sorting
|
|
173
189
|
const sorted = await sdk.users.list({
|
|
174
190
|
orderBy: ["status", "created_at"],
|
|
@@ -184,6 +200,7 @@ const filtered = await sdk.users.list({
|
|
|
184
200
|
deleted_at: { $is: null } // NULL checks
|
|
185
201
|
}
|
|
186
202
|
});
|
|
203
|
+
// filtered.total respects WHERE clause for accurate counts
|
|
187
204
|
|
|
188
205
|
// OR logic - match any condition
|
|
189
206
|
const results = await sdk.users.list({
|
|
@@ -221,6 +238,17 @@ const nested = await sdk.users.list({
|
|
|
221
238
|
]
|
|
222
239
|
}
|
|
223
240
|
});
|
|
241
|
+
|
|
242
|
+
// Pagination with filtered results
|
|
243
|
+
let allResults = [];
|
|
244
|
+
let offset = 0;
|
|
245
|
+
const limit = 50;
|
|
246
|
+
do {
|
|
247
|
+
const page = await sdk.users.list({ where: { status: 'active' }, limit, offset });
|
|
248
|
+
allResults = allResults.concat(page.data);
|
|
249
|
+
offset += limit;
|
|
250
|
+
if (!page.hasMore) break;
|
|
251
|
+
} while (true);
|
|
224
252
|
```
|
|
225
253
|
|
|
226
254
|
See the generated SDK documentation for all available operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$like`, `$ilike`, `$is`, `$isNot`, `$or`, `$and`.
|
package/dist/cli.js
CHANGED
|
@@ -578,7 +578,7 @@ function generateIncludeMethods(table, graph, opts, allTables) {
|
|
|
578
578
|
path: newPath,
|
|
579
579
|
isMany: newIsMany,
|
|
580
580
|
targets: newTargets,
|
|
581
|
-
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
581
|
+
returnType: `{ data: (${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]; total: number; limit: number; offset: number; hasMore: boolean; }`,
|
|
582
582
|
includeSpec: buildIncludeSpec(newPath)
|
|
583
583
|
});
|
|
584
584
|
methods.push({
|
|
@@ -618,7 +618,7 @@ function generateIncludeMethods(table, graph, opts, allTables) {
|
|
|
618
618
|
path: combinedPath,
|
|
619
619
|
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
620
620
|
targets: [edge1.target, edge2.target],
|
|
621
|
-
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
621
|
+
returnType: `{ data: (Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]; total: number; limit: number; offset: number; hasMore: boolean; }`,
|
|
622
622
|
includeSpec: { [key1]: true, [key2]: true }
|
|
623
623
|
});
|
|
624
624
|
methods.push({
|
|
@@ -785,10 +785,13 @@ function generateResourceWithSDK(table, model, graph, config) {
|
|
|
785
785
|
const endpoints = [];
|
|
786
786
|
sdkMethods.push({
|
|
787
787
|
name: "list",
|
|
788
|
-
signature: `list(params?: ListParams): Promise
|
|
789
|
-
description: `List ${tableName} with filtering, sorting, and pagination
|
|
788
|
+
signature: `list(params?: ListParams): Promise<{ data: ${Type}[]; total: number; limit: number; offset: number; hasMore: boolean; }>`,
|
|
789
|
+
description: `List ${tableName} with filtering, sorting, and pagination. Returns paginated results with metadata.`,
|
|
790
790
|
example: `// Get all ${tableName}
|
|
791
|
-
const
|
|
791
|
+
const result = await sdk.${tableName}.list();
|
|
792
|
+
console.log(result.data); // array of records
|
|
793
|
+
console.log(result.total); // total matching records
|
|
794
|
+
console.log(result.hasMore); // true if more pages available
|
|
792
795
|
|
|
793
796
|
// With filters and pagination
|
|
794
797
|
const filtered = await sdk.${tableName}.list({
|
|
@@ -797,15 +800,19 @@ const filtered = await sdk.${tableName}.list({
|
|
|
797
800
|
where: { ${table.columns[0]?.name || "field"}: { $like: '%search%' } },
|
|
798
801
|
orderBy: '${table.columns[0]?.name || "created_at"}',
|
|
799
802
|
order: 'desc'
|
|
800
|
-
})
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// Calculate total pages
|
|
806
|
+
const totalPages = Math.ceil(filtered.total / filtered.limit);
|
|
807
|
+
const currentPage = Math.floor(filtered.offset / filtered.limit) + 1;`,
|
|
801
808
|
correspondsTo: `GET ${basePath}`
|
|
802
809
|
});
|
|
803
810
|
endpoints.push({
|
|
804
811
|
method: "GET",
|
|
805
812
|
path: basePath,
|
|
806
|
-
description: `List all ${tableName} records`,
|
|
813
|
+
description: `List all ${tableName} records with pagination metadata`,
|
|
807
814
|
queryParameters: generateQueryParams(table, enums),
|
|
808
|
-
responseBody:
|
|
815
|
+
responseBody: `{ data: ${Type}[]; total: number; limit: number; offset: number; hasMore: boolean; }`
|
|
809
816
|
});
|
|
810
817
|
if (hasSinglePK) {
|
|
811
818
|
sdkMethods.push({
|
|
@@ -895,7 +902,10 @@ console.log('Deleted:', deleted);`,
|
|
|
895
902
|
}, allTables);
|
|
896
903
|
for (const method of includeMethods) {
|
|
897
904
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
898
|
-
const exampleCall = isGetByPk ? `const result = await sdk.${tableName}.${method.name}('123e4567-e89b-12d3-a456-426614174000');` : `const
|
|
905
|
+
const exampleCall = isGetByPk ? `const result = await sdk.${tableName}.${method.name}('123e4567-e89b-12d3-a456-426614174000');` : `const result = await sdk.${tableName}.${method.name}();
|
|
906
|
+
console.log(result.data); // array of records with includes
|
|
907
|
+
console.log(result.total); // total count
|
|
908
|
+
console.log(result.hasMore); // more pages available
|
|
899
909
|
|
|
900
910
|
// With filters and pagination
|
|
901
911
|
const filtered = await sdk.${tableName}.${method.name}({
|
|
@@ -2768,6 +2778,13 @@ const listSchema = z.object({
|
|
|
2768
2778
|
order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional()
|
|
2769
2779
|
});
|
|
2770
2780
|
|
|
2781
|
+
/**
|
|
2782
|
+
* Register all CRUD routes for the ${fileTableName} table
|
|
2783
|
+
* @param app - Hono application instance
|
|
2784
|
+
* @param deps - Dependencies including database client and optional request hook
|
|
2785
|
+
* @param deps.pg - PostgreSQL client with query method
|
|
2786
|
+
* @param deps.onRequest - Optional hook that runs before each request (for audit logging, RLS, etc.)
|
|
2787
|
+
*/
|
|
2771
2788
|
export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }, onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void> }) {
|
|
2772
2789
|
const base = "/v1/${fileTableName}";
|
|
2773
2790
|
|
|
@@ -2847,34 +2864,50 @@ ${hasAuth ? `
|
|
|
2847
2864
|
if (result.needsIncludes && result.includeSpec) {
|
|
2848
2865
|
try {
|
|
2849
2866
|
const stitched = await loadIncludes(
|
|
2850
|
-
"${fileTableName}",
|
|
2851
|
-
result.data,
|
|
2852
|
-
result.includeSpec,
|
|
2853
|
-
deps.pg,
|
|
2867
|
+
"${fileTableName}",
|
|
2868
|
+
result.data,
|
|
2869
|
+
result.includeSpec,
|
|
2870
|
+
deps.pg,
|
|
2854
2871
|
${opts.includeMethodsDepth}
|
|
2855
2872
|
);
|
|
2856
|
-
return c.json(
|
|
2873
|
+
return c.json({
|
|
2874
|
+
data: stitched,
|
|
2875
|
+
total: result.total,
|
|
2876
|
+
limit: result.limit,
|
|
2877
|
+
offset: result.offset,
|
|
2878
|
+
hasMore: result.hasMore
|
|
2879
|
+
});
|
|
2857
2880
|
} catch (e: any) {
|
|
2858
2881
|
const strict = process.env.SDK_STRICT_INCLUDE === "1";
|
|
2859
2882
|
if (strict) {
|
|
2860
|
-
return c.json({
|
|
2861
|
-
error: "include-stitch-failed",
|
|
2883
|
+
return c.json({
|
|
2884
|
+
error: "include-stitch-failed",
|
|
2862
2885
|
message: e?.message,
|
|
2863
2886
|
...(process.env.SDK_DEBUG === "1" ? { stack: e?.stack } : {})
|
|
2864
2887
|
}, 500);
|
|
2865
2888
|
}
|
|
2866
2889
|
// Non-strict: return base rows with error metadata
|
|
2867
|
-
return c.json({
|
|
2868
|
-
data: result.data,
|
|
2869
|
-
|
|
2890
|
+
return c.json({
|
|
2891
|
+
data: result.data,
|
|
2892
|
+
total: result.total,
|
|
2893
|
+
limit: result.limit,
|
|
2894
|
+
offset: result.offset,
|
|
2895
|
+
hasMore: result.hasMore,
|
|
2896
|
+
includeError: {
|
|
2870
2897
|
message: e?.message,
|
|
2871
2898
|
...(process.env.SDK_DEBUG === "1" ? { stack: e?.stack } : {})
|
|
2872
2899
|
}
|
|
2873
2900
|
}, 200);
|
|
2874
2901
|
}
|
|
2875
2902
|
}
|
|
2876
|
-
|
|
2877
|
-
return c.json(
|
|
2903
|
+
|
|
2904
|
+
return c.json({
|
|
2905
|
+
data: result.data,
|
|
2906
|
+
total: result.total,
|
|
2907
|
+
limit: result.limit,
|
|
2908
|
+
offset: result.offset,
|
|
2909
|
+
hasMore: result.hasMore
|
|
2910
|
+
}, result.status as any);
|
|
2878
2911
|
});
|
|
2879
2912
|
|
|
2880
2913
|
// UPDATE
|
|
@@ -2955,21 +2988,36 @@ function emitClient(table, graph, opts, model) {
|
|
|
2955
2988
|
for (const method of includeMethods) {
|
|
2956
2989
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
2957
2990
|
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[]; }, "include">`;
|
|
2991
|
+
const relationshipDesc = method.path.map((p, i) => {
|
|
2992
|
+
const isLast = i === method.path.length - 1;
|
|
2993
|
+
const relation = method.isMany[i] ? "many" : "one";
|
|
2994
|
+
return isLast ? p : `${p} -> `;
|
|
2995
|
+
}).join("");
|
|
2958
2996
|
if (isGetByPk) {
|
|
2959
2997
|
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
2960
2998
|
const baseReturnType = method.returnType.replace(" | null", "");
|
|
2961
2999
|
includeMethodsCode += `
|
|
3000
|
+
/**
|
|
3001
|
+
* Get a ${table.name} record by primary key with included related ${relationshipDesc}
|
|
3002
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
3003
|
+
* @returns The record with nested ${method.path.join(" and ")} if found, null otherwise
|
|
3004
|
+
*/
|
|
2962
3005
|
async ${method.name}(pk: ${pkType}): Promise<${method.returnType}> {
|
|
2963
|
-
const results = await this.post
|
|
3006
|
+
const results = await this.post<{ data: ${baseReturnType}[]; total: number; limit: number; offset: number; hasMore: boolean; }>(\`\${this.resource}/list\`, {
|
|
2964
3007
|
where: ${pkWhere},
|
|
2965
3008
|
include: ${JSON.stringify(method.includeSpec)},
|
|
2966
|
-
limit: 1
|
|
3009
|
+
limit: 1
|
|
2967
3010
|
});
|
|
2968
|
-
return (results[0] as ${baseReturnType}) ?? null;
|
|
3011
|
+
return (results.data[0] as ${baseReturnType}) ?? null;
|
|
2969
3012
|
}
|
|
2970
3013
|
`;
|
|
2971
3014
|
} else {
|
|
2972
3015
|
includeMethodsCode += `
|
|
3016
|
+
/**
|
|
3017
|
+
* List ${table.name} records with included related ${relationshipDesc}
|
|
3018
|
+
* @param params - Query parameters (where, orderBy, order, limit, offset)
|
|
3019
|
+
* @returns Paginated results with nested ${method.path.join(" and ")} included
|
|
3020
|
+
*/
|
|
2973
3021
|
async ${method.name}(${baseParams}): Promise<${method.returnType}> {
|
|
2974
3022
|
return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...params, include: ${JSON.stringify(method.includeSpec)} });
|
|
2975
3023
|
}
|
|
@@ -2996,15 +3044,36 @@ ${otherTableImports.join(`
|
|
|
2996
3044
|
export class ${Type}Client extends BaseClient {
|
|
2997
3045
|
private readonly resource = "/v1/${table.name}";
|
|
2998
3046
|
|
|
3047
|
+
/**
|
|
3048
|
+
* Create a new ${table.name} record
|
|
3049
|
+
* @param data - The data to insert
|
|
3050
|
+
* @returns The created record
|
|
3051
|
+
*/
|
|
2999
3052
|
async create(data: Insert${Type}): Promise<Select${Type}> {
|
|
3000
3053
|
return this.post<Select${Type}>(this.resource, data);
|
|
3001
3054
|
}
|
|
3002
3055
|
|
|
3056
|
+
/**
|
|
3057
|
+
* Get a ${table.name} record by primary key
|
|
3058
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
3059
|
+
* @returns The record if found, null otherwise
|
|
3060
|
+
*/
|
|
3003
3061
|
async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
|
|
3004
3062
|
const path = ${pkPathExpr};
|
|
3005
3063
|
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
3006
3064
|
}
|
|
3007
3065
|
|
|
3066
|
+
/**
|
|
3067
|
+
* List ${table.name} records with pagination and filtering
|
|
3068
|
+
* @param params - Query parameters
|
|
3069
|
+
* @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
|
|
3070
|
+
* @param params.orderBy - Column(s) to sort by
|
|
3071
|
+
* @param params.order - Sort direction(s): "asc" or "desc"
|
|
3072
|
+
* @param params.limit - Maximum number of records to return (default: 50, max: 100)
|
|
3073
|
+
* @param params.offset - Number of records to skip for pagination
|
|
3074
|
+
* @param params.include - Related records to include (see listWith* methods for typed includes)
|
|
3075
|
+
* @returns Paginated results with data, total count, and hasMore flag
|
|
3076
|
+
*/
|
|
3008
3077
|
async list(params?: {
|
|
3009
3078
|
include?: any;
|
|
3010
3079
|
limit?: number;
|
|
@@ -3012,15 +3081,38 @@ export class ${Type}Client extends BaseClient {
|
|
|
3012
3081
|
where?: Where<Select${Type}>;
|
|
3013
3082
|
orderBy?: string | string[];
|
|
3014
3083
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
3015
|
-
}): Promise<
|
|
3016
|
-
|
|
3084
|
+
}): Promise<{
|
|
3085
|
+
data: Select${Type}[];
|
|
3086
|
+
total: number;
|
|
3087
|
+
limit: number;
|
|
3088
|
+
offset: number;
|
|
3089
|
+
hasMore: boolean;
|
|
3090
|
+
}> {
|
|
3091
|
+
return this.post<{
|
|
3092
|
+
data: Select${Type}[];
|
|
3093
|
+
total: number;
|
|
3094
|
+
limit: number;
|
|
3095
|
+
offset: number;
|
|
3096
|
+
hasMore: boolean;
|
|
3097
|
+
}>(\`\${this.resource}/list\`, params ?? {});
|
|
3017
3098
|
}
|
|
3018
3099
|
|
|
3100
|
+
/**
|
|
3101
|
+
* Update a ${table.name} record by primary key
|
|
3102
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
3103
|
+
* @param patch - Partial data to update
|
|
3104
|
+
* @returns The updated record if found, null otherwise
|
|
3105
|
+
*/
|
|
3019
3106
|
async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
|
|
3020
3107
|
const path = ${pkPathExpr};
|
|
3021
3108
|
return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
|
|
3022
3109
|
}
|
|
3023
3110
|
|
|
3111
|
+
/**
|
|
3112
|
+
* Delete a ${table.name} record by primary key
|
|
3113
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
3114
|
+
* @returns The deleted record if found, null otherwise
|
|
3115
|
+
*/
|
|
3024
3116
|
async delete(pk: ${pkType}): Promise<Select${Type} | null> {
|
|
3025
3117
|
const path = ${pkPathExpr};
|
|
3026
3118
|
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
@@ -3611,12 +3703,25 @@ function emitTypes(table, opts, enums) {
|
|
|
3611
3703
|
*
|
|
3612
3704
|
* To make changes, modify your schema or configuration and regenerate.
|
|
3613
3705
|
*/
|
|
3706
|
+
|
|
3707
|
+
/**
|
|
3708
|
+
* Type for inserting a new ${table.name} record.
|
|
3709
|
+
* Fields with defaults or nullable columns are optional.
|
|
3710
|
+
*/
|
|
3614
3711
|
export type Insert${Type} = {
|
|
3615
3712
|
${insertFields}
|
|
3616
3713
|
};
|
|
3617
3714
|
|
|
3715
|
+
/**
|
|
3716
|
+
* Type for updating an existing ${table.name} record.
|
|
3717
|
+
* All fields are optional, allowing partial updates.
|
|
3718
|
+
*/
|
|
3618
3719
|
export type Update${Type} = Partial<Insert${Type}>;
|
|
3619
3720
|
|
|
3721
|
+
/**
|
|
3722
|
+
* Type representing a ${table.name} record from the database.
|
|
3723
|
+
* All fields are included as returned by SELECT queries.
|
|
3724
|
+
*/
|
|
3620
3725
|
export type Select${Type} = {
|
|
3621
3726
|
${selectFields}
|
|
3622
3727
|
};
|
|
@@ -4349,7 +4454,7 @@ function buildWhereClause(
|
|
|
4349
4454
|
export async function listRecords(
|
|
4350
4455
|
ctx: OperationContext,
|
|
4351
4456
|
params: { where?: any; limit?: number; offset?: number; include?: any; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[] }
|
|
4352
|
-
): Promise<{ data?: any; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
4457
|
+
): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
4353
4458
|
try {
|
|
4354
4459
|
const { where: whereClause, limit = 50, offset = 0, include, orderBy, order } = params;
|
|
4355
4460
|
|
|
@@ -4394,20 +4499,34 @@ export async function listRecords(
|
|
|
4394
4499
|
const offsetParam = \`$\${paramIndex + 1}\`;
|
|
4395
4500
|
const allParams = [...whereParams, limit, offset];
|
|
4396
4501
|
|
|
4502
|
+
// Get total count for pagination
|
|
4503
|
+
const countText = \`SELECT COUNT(*) FROM "\${ctx.table}" \${whereSQL}\`;
|
|
4504
|
+
log.debug(\`LIST \${ctx.table} COUNT SQL:\`, countText, "params:", whereParams);
|
|
4505
|
+
const countResult = await ctx.pg.query(countText, whereParams);
|
|
4506
|
+
const total = parseInt(countResult.rows[0].count, 10);
|
|
4507
|
+
|
|
4508
|
+
// Get paginated data
|
|
4397
4509
|
const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
|
|
4398
4510
|
log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
|
|
4399
4511
|
|
|
4400
4512
|
const { rows } = await ctx.pg.query(text, allParams);
|
|
4401
4513
|
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4514
|
+
// Calculate hasMore
|
|
4515
|
+
const hasMore = offset + limit < total;
|
|
4516
|
+
|
|
4517
|
+
const metadata = {
|
|
4518
|
+
data: rows,
|
|
4519
|
+
total,
|
|
4520
|
+
limit,
|
|
4521
|
+
offset,
|
|
4522
|
+
hasMore,
|
|
4523
|
+
needsIncludes: !!include,
|
|
4524
|
+
includeSpec: include,
|
|
4525
|
+
status: 200
|
|
4526
|
+
};
|
|
4406
4527
|
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
log.debug(\`LIST \${ctx.table} include spec:\`, include);
|
|
4410
|
-
return { data: rows, needsIncludes: true, includeSpec: include, status: 200 };
|
|
4528
|
+
log.debug(\`LIST \${ctx.table} result: \${rows.length} rows, \${total} total, hasMore=\${hasMore}\`);
|
|
4529
|
+
return metadata;
|
|
4411
4530
|
} catch (e: any) {
|
|
4412
4531
|
log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
|
|
4413
4532
|
return {
|
|
@@ -4549,8 +4668,11 @@ describe('${Type} SDK Operations', () => {
|
|
|
4549
4668
|
});
|
|
4550
4669
|
|
|
4551
4670
|
it('should list ${tableName} relationships', async () => {
|
|
4552
|
-
const
|
|
4553
|
-
expect(
|
|
4671
|
+
const result = await sdk.${tableName}.list({ limit: 10 });
|
|
4672
|
+
expect(result).toBeDefined();
|
|
4673
|
+
expect(Array.isArray(result.data)).toBe(true);
|
|
4674
|
+
expect(typeof result.total).toBe('number');
|
|
4675
|
+
expect(typeof result.hasMore).toBe('boolean');
|
|
4554
4676
|
});
|
|
4555
4677
|
});
|
|
4556
4678
|
`;
|
|
@@ -5186,8 +5308,11 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
|
|
|
5186
5308
|
});
|
|
5187
5309
|
|
|
5188
5310
|
it('should list ${table.name}', async () => {
|
|
5189
|
-
const
|
|
5190
|
-
expect(
|
|
5311
|
+
const result = await sdk.${table.name}.list({ limit: 10 });
|
|
5312
|
+
expect(result).toBeDefined();
|
|
5313
|
+
expect(Array.isArray(result.data)).toBe(true);
|
|
5314
|
+
expect(typeof result.total).toBe('number');
|
|
5315
|
+
expect(typeof result.hasMore).toBe('boolean');
|
|
5191
5316
|
});
|
|
5192
5317
|
|
|
5193
5318
|
${hasData && hasSinglePK ? `it('should get ${table.name} by id', async () => {
|
package/dist/index.js
CHANGED
|
@@ -577,7 +577,7 @@ function generateIncludeMethods(table, graph, opts, allTables) {
|
|
|
577
577
|
path: newPath,
|
|
578
578
|
isMany: newIsMany,
|
|
579
579
|
targets: newTargets,
|
|
580
|
-
returnType: `(${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]`,
|
|
580
|
+
returnType: `{ data: (${buildReturnType(baseTableName, newPath, newIsMany, newTargets, graph)})[]; total: number; limit: number; offset: number; hasMore: boolean; }`,
|
|
581
581
|
includeSpec: buildIncludeSpec(newPath)
|
|
582
582
|
});
|
|
583
583
|
methods.push({
|
|
@@ -617,7 +617,7 @@ function generateIncludeMethods(table, graph, opts, allTables) {
|
|
|
617
617
|
path: combinedPath,
|
|
618
618
|
isMany: [edge1.kind === "many", edge2.kind === "many"],
|
|
619
619
|
targets: [edge1.target, edge2.target],
|
|
620
|
-
returnType: `(Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]`,
|
|
620
|
+
returnType: `{ data: (Select${pascal(baseTableName)} & { ${type1}; ${type2} })[]; total: number; limit: number; offset: number; hasMore: boolean; }`,
|
|
621
621
|
includeSpec: { [key1]: true, [key2]: true }
|
|
622
622
|
});
|
|
623
623
|
methods.push({
|
|
@@ -784,10 +784,13 @@ function generateResourceWithSDK(table, model, graph, config) {
|
|
|
784
784
|
const endpoints = [];
|
|
785
785
|
sdkMethods.push({
|
|
786
786
|
name: "list",
|
|
787
|
-
signature: `list(params?: ListParams): Promise
|
|
788
|
-
description: `List ${tableName} with filtering, sorting, and pagination
|
|
787
|
+
signature: `list(params?: ListParams): Promise<{ data: ${Type}[]; total: number; limit: number; offset: number; hasMore: boolean; }>`,
|
|
788
|
+
description: `List ${tableName} with filtering, sorting, and pagination. Returns paginated results with metadata.`,
|
|
789
789
|
example: `// Get all ${tableName}
|
|
790
|
-
const
|
|
790
|
+
const result = await sdk.${tableName}.list();
|
|
791
|
+
console.log(result.data); // array of records
|
|
792
|
+
console.log(result.total); // total matching records
|
|
793
|
+
console.log(result.hasMore); // true if more pages available
|
|
791
794
|
|
|
792
795
|
// With filters and pagination
|
|
793
796
|
const filtered = await sdk.${tableName}.list({
|
|
@@ -796,15 +799,19 @@ const filtered = await sdk.${tableName}.list({
|
|
|
796
799
|
where: { ${table.columns[0]?.name || "field"}: { $like: '%search%' } },
|
|
797
800
|
orderBy: '${table.columns[0]?.name || "created_at"}',
|
|
798
801
|
order: 'desc'
|
|
799
|
-
})
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// Calculate total pages
|
|
805
|
+
const totalPages = Math.ceil(filtered.total / filtered.limit);
|
|
806
|
+
const currentPage = Math.floor(filtered.offset / filtered.limit) + 1;`,
|
|
800
807
|
correspondsTo: `GET ${basePath}`
|
|
801
808
|
});
|
|
802
809
|
endpoints.push({
|
|
803
810
|
method: "GET",
|
|
804
811
|
path: basePath,
|
|
805
|
-
description: `List all ${tableName} records`,
|
|
812
|
+
description: `List all ${tableName} records with pagination metadata`,
|
|
806
813
|
queryParameters: generateQueryParams(table, enums),
|
|
807
|
-
responseBody:
|
|
814
|
+
responseBody: `{ data: ${Type}[]; total: number; limit: number; offset: number; hasMore: boolean; }`
|
|
808
815
|
});
|
|
809
816
|
if (hasSinglePK) {
|
|
810
817
|
sdkMethods.push({
|
|
@@ -894,7 +901,10 @@ console.log('Deleted:', deleted);`,
|
|
|
894
901
|
}, allTables);
|
|
895
902
|
for (const method of includeMethods) {
|
|
896
903
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
897
|
-
const exampleCall = isGetByPk ? `const result = await sdk.${tableName}.${method.name}('123e4567-e89b-12d3-a456-426614174000');` : `const
|
|
904
|
+
const exampleCall = isGetByPk ? `const result = await sdk.${tableName}.${method.name}('123e4567-e89b-12d3-a456-426614174000');` : `const result = await sdk.${tableName}.${method.name}();
|
|
905
|
+
console.log(result.data); // array of records with includes
|
|
906
|
+
console.log(result.total); // total count
|
|
907
|
+
console.log(result.hasMore); // more pages available
|
|
898
908
|
|
|
899
909
|
// With filters and pagination
|
|
900
910
|
const filtered = await sdk.${tableName}.${method.name}({
|
|
@@ -2008,6 +2018,13 @@ const listSchema = z.object({
|
|
|
2008
2018
|
order: z.union([z.enum(["asc", "desc"]), z.array(z.enum(["asc", "desc"]))]).optional()
|
|
2009
2019
|
});
|
|
2010
2020
|
|
|
2021
|
+
/**
|
|
2022
|
+
* Register all CRUD routes for the ${fileTableName} table
|
|
2023
|
+
* @param app - Hono application instance
|
|
2024
|
+
* @param deps - Dependencies including database client and optional request hook
|
|
2025
|
+
* @param deps.pg - PostgreSQL client with query method
|
|
2026
|
+
* @param deps.onRequest - Optional hook that runs before each request (for audit logging, RLS, etc.)
|
|
2027
|
+
*/
|
|
2011
2028
|
export function register${Type}Routes(app: Hono, deps: { pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }, onRequest?: (c: Context, pg: { query: (text: string, params?: any[]) => Promise<{ rows: any[] }> }) => Promise<void> }) {
|
|
2012
2029
|
const base = "/v1/${fileTableName}";
|
|
2013
2030
|
|
|
@@ -2087,34 +2104,50 @@ ${hasAuth ? `
|
|
|
2087
2104
|
if (result.needsIncludes && result.includeSpec) {
|
|
2088
2105
|
try {
|
|
2089
2106
|
const stitched = await loadIncludes(
|
|
2090
|
-
"${fileTableName}",
|
|
2091
|
-
result.data,
|
|
2092
|
-
result.includeSpec,
|
|
2093
|
-
deps.pg,
|
|
2107
|
+
"${fileTableName}",
|
|
2108
|
+
result.data,
|
|
2109
|
+
result.includeSpec,
|
|
2110
|
+
deps.pg,
|
|
2094
2111
|
${opts.includeMethodsDepth}
|
|
2095
2112
|
);
|
|
2096
|
-
return c.json(
|
|
2113
|
+
return c.json({
|
|
2114
|
+
data: stitched,
|
|
2115
|
+
total: result.total,
|
|
2116
|
+
limit: result.limit,
|
|
2117
|
+
offset: result.offset,
|
|
2118
|
+
hasMore: result.hasMore
|
|
2119
|
+
});
|
|
2097
2120
|
} catch (e: any) {
|
|
2098
2121
|
const strict = process.env.SDK_STRICT_INCLUDE === "1";
|
|
2099
2122
|
if (strict) {
|
|
2100
|
-
return c.json({
|
|
2101
|
-
error: "include-stitch-failed",
|
|
2123
|
+
return c.json({
|
|
2124
|
+
error: "include-stitch-failed",
|
|
2102
2125
|
message: e?.message,
|
|
2103
2126
|
...(process.env.SDK_DEBUG === "1" ? { stack: e?.stack } : {})
|
|
2104
2127
|
}, 500);
|
|
2105
2128
|
}
|
|
2106
2129
|
// Non-strict: return base rows with error metadata
|
|
2107
|
-
return c.json({
|
|
2108
|
-
data: result.data,
|
|
2109
|
-
|
|
2130
|
+
return c.json({
|
|
2131
|
+
data: result.data,
|
|
2132
|
+
total: result.total,
|
|
2133
|
+
limit: result.limit,
|
|
2134
|
+
offset: result.offset,
|
|
2135
|
+
hasMore: result.hasMore,
|
|
2136
|
+
includeError: {
|
|
2110
2137
|
message: e?.message,
|
|
2111
2138
|
...(process.env.SDK_DEBUG === "1" ? { stack: e?.stack } : {})
|
|
2112
2139
|
}
|
|
2113
2140
|
}, 200);
|
|
2114
2141
|
}
|
|
2115
2142
|
}
|
|
2116
|
-
|
|
2117
|
-
return c.json(
|
|
2143
|
+
|
|
2144
|
+
return c.json({
|
|
2145
|
+
data: result.data,
|
|
2146
|
+
total: result.total,
|
|
2147
|
+
limit: result.limit,
|
|
2148
|
+
offset: result.offset,
|
|
2149
|
+
hasMore: result.hasMore
|
|
2150
|
+
}, result.status as any);
|
|
2118
2151
|
});
|
|
2119
2152
|
|
|
2120
2153
|
// UPDATE
|
|
@@ -2195,21 +2228,36 @@ function emitClient(table, graph, opts, model) {
|
|
|
2195
2228
|
for (const method of includeMethods) {
|
|
2196
2229
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
2197
2230
|
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[]; }, "include">`;
|
|
2231
|
+
const relationshipDesc = method.path.map((p, i) => {
|
|
2232
|
+
const isLast = i === method.path.length - 1;
|
|
2233
|
+
const relation = method.isMany[i] ? "many" : "one";
|
|
2234
|
+
return isLast ? p : `${p} -> `;
|
|
2235
|
+
}).join("");
|
|
2198
2236
|
if (isGetByPk) {
|
|
2199
2237
|
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
2200
2238
|
const baseReturnType = method.returnType.replace(" | null", "");
|
|
2201
2239
|
includeMethodsCode += `
|
|
2240
|
+
/**
|
|
2241
|
+
* Get a ${table.name} record by primary key with included related ${relationshipDesc}
|
|
2242
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
2243
|
+
* @returns The record with nested ${method.path.join(" and ")} if found, null otherwise
|
|
2244
|
+
*/
|
|
2202
2245
|
async ${method.name}(pk: ${pkType}): Promise<${method.returnType}> {
|
|
2203
|
-
const results = await this.post
|
|
2246
|
+
const results = await this.post<{ data: ${baseReturnType}[]; total: number; limit: number; offset: number; hasMore: boolean; }>(\`\${this.resource}/list\`, {
|
|
2204
2247
|
where: ${pkWhere},
|
|
2205
2248
|
include: ${JSON.stringify(method.includeSpec)},
|
|
2206
|
-
limit: 1
|
|
2249
|
+
limit: 1
|
|
2207
2250
|
});
|
|
2208
|
-
return (results[0] as ${baseReturnType}) ?? null;
|
|
2251
|
+
return (results.data[0] as ${baseReturnType}) ?? null;
|
|
2209
2252
|
}
|
|
2210
2253
|
`;
|
|
2211
2254
|
} else {
|
|
2212
2255
|
includeMethodsCode += `
|
|
2256
|
+
/**
|
|
2257
|
+
* List ${table.name} records with included related ${relationshipDesc}
|
|
2258
|
+
* @param params - Query parameters (where, orderBy, order, limit, offset)
|
|
2259
|
+
* @returns Paginated results with nested ${method.path.join(" and ")} included
|
|
2260
|
+
*/
|
|
2213
2261
|
async ${method.name}(${baseParams}): Promise<${method.returnType}> {
|
|
2214
2262
|
return this.post<${method.returnType}>(\`\${this.resource}/list\`, { ...params, include: ${JSON.stringify(method.includeSpec)} });
|
|
2215
2263
|
}
|
|
@@ -2236,15 +2284,36 @@ ${otherTableImports.join(`
|
|
|
2236
2284
|
export class ${Type}Client extends BaseClient {
|
|
2237
2285
|
private readonly resource = "/v1/${table.name}";
|
|
2238
2286
|
|
|
2287
|
+
/**
|
|
2288
|
+
* Create a new ${table.name} record
|
|
2289
|
+
* @param data - The data to insert
|
|
2290
|
+
* @returns The created record
|
|
2291
|
+
*/
|
|
2239
2292
|
async create(data: Insert${Type}): Promise<Select${Type}> {
|
|
2240
2293
|
return this.post<Select${Type}>(this.resource, data);
|
|
2241
2294
|
}
|
|
2242
2295
|
|
|
2296
|
+
/**
|
|
2297
|
+
* Get a ${table.name} record by primary key
|
|
2298
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
2299
|
+
* @returns The record if found, null otherwise
|
|
2300
|
+
*/
|
|
2243
2301
|
async getByPk(pk: ${pkType}): Promise<Select${Type} | null> {
|
|
2244
2302
|
const path = ${pkPathExpr};
|
|
2245
2303
|
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
2246
2304
|
}
|
|
2247
2305
|
|
|
2306
|
+
/**
|
|
2307
|
+
* List ${table.name} records with pagination and filtering
|
|
2308
|
+
* @param params - Query parameters
|
|
2309
|
+
* @param params.where - Filter conditions using operators like $eq, $gt, $in, $like, etc.
|
|
2310
|
+
* @param params.orderBy - Column(s) to sort by
|
|
2311
|
+
* @param params.order - Sort direction(s): "asc" or "desc"
|
|
2312
|
+
* @param params.limit - Maximum number of records to return (default: 50, max: 100)
|
|
2313
|
+
* @param params.offset - Number of records to skip for pagination
|
|
2314
|
+
* @param params.include - Related records to include (see listWith* methods for typed includes)
|
|
2315
|
+
* @returns Paginated results with data, total count, and hasMore flag
|
|
2316
|
+
*/
|
|
2248
2317
|
async list(params?: {
|
|
2249
2318
|
include?: any;
|
|
2250
2319
|
limit?: number;
|
|
@@ -2252,15 +2321,38 @@ export class ${Type}Client extends BaseClient {
|
|
|
2252
2321
|
where?: Where<Select${Type}>;
|
|
2253
2322
|
orderBy?: string | string[];
|
|
2254
2323
|
order?: "asc" | "desc" | ("asc" | "desc")[];
|
|
2255
|
-
}): Promise<
|
|
2256
|
-
|
|
2324
|
+
}): Promise<{
|
|
2325
|
+
data: Select${Type}[];
|
|
2326
|
+
total: number;
|
|
2327
|
+
limit: number;
|
|
2328
|
+
offset: number;
|
|
2329
|
+
hasMore: boolean;
|
|
2330
|
+
}> {
|
|
2331
|
+
return this.post<{
|
|
2332
|
+
data: Select${Type}[];
|
|
2333
|
+
total: number;
|
|
2334
|
+
limit: number;
|
|
2335
|
+
offset: number;
|
|
2336
|
+
hasMore: boolean;
|
|
2337
|
+
}>(\`\${this.resource}/list\`, params ?? {});
|
|
2257
2338
|
}
|
|
2258
2339
|
|
|
2340
|
+
/**
|
|
2341
|
+
* Update a ${table.name} record by primary key
|
|
2342
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
2343
|
+
* @param patch - Partial data to update
|
|
2344
|
+
* @returns The updated record if found, null otherwise
|
|
2345
|
+
*/
|
|
2259
2346
|
async update(pk: ${pkType}, patch: Update${Type}): Promise<Select${Type} | null> {
|
|
2260
2347
|
const path = ${pkPathExpr};
|
|
2261
2348
|
return this.patch<Select${Type} | null>(\`\${this.resource}/\${path}\`, patch);
|
|
2262
2349
|
}
|
|
2263
2350
|
|
|
2351
|
+
/**
|
|
2352
|
+
* Delete a ${table.name} record by primary key
|
|
2353
|
+
* @param pk - The primary key value${hasCompositePk ? "s" : ""}
|
|
2354
|
+
* @returns The deleted record if found, null otherwise
|
|
2355
|
+
*/
|
|
2264
2356
|
async delete(pk: ${pkType}): Promise<Select${Type} | null> {
|
|
2265
2357
|
const path = ${pkPathExpr};
|
|
2266
2358
|
return this.del<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
@@ -2851,12 +2943,25 @@ function emitTypes(table, opts, enums) {
|
|
|
2851
2943
|
*
|
|
2852
2944
|
* To make changes, modify your schema or configuration and regenerate.
|
|
2853
2945
|
*/
|
|
2946
|
+
|
|
2947
|
+
/**
|
|
2948
|
+
* Type for inserting a new ${table.name} record.
|
|
2949
|
+
* Fields with defaults or nullable columns are optional.
|
|
2950
|
+
*/
|
|
2854
2951
|
export type Insert${Type} = {
|
|
2855
2952
|
${insertFields}
|
|
2856
2953
|
};
|
|
2857
2954
|
|
|
2955
|
+
/**
|
|
2956
|
+
* Type for updating an existing ${table.name} record.
|
|
2957
|
+
* All fields are optional, allowing partial updates.
|
|
2958
|
+
*/
|
|
2858
2959
|
export type Update${Type} = Partial<Insert${Type}>;
|
|
2859
2960
|
|
|
2961
|
+
/**
|
|
2962
|
+
* Type representing a ${table.name} record from the database.
|
|
2963
|
+
* All fields are included as returned by SELECT queries.
|
|
2964
|
+
*/
|
|
2860
2965
|
export type Select${Type} = {
|
|
2861
2966
|
${selectFields}
|
|
2862
2967
|
};
|
|
@@ -3589,7 +3694,7 @@ function buildWhereClause(
|
|
|
3589
3694
|
export async function listRecords(
|
|
3590
3695
|
ctx: OperationContext,
|
|
3591
3696
|
params: { where?: any; limit?: number; offset?: number; include?: any; orderBy?: string | string[]; order?: "asc" | "desc" | ("asc" | "desc")[] }
|
|
3592
|
-
): Promise<{ data?: any; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
3697
|
+
): Promise<{ data?: any; total?: number; limit?: number; offset?: number; hasMore?: boolean; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
3593
3698
|
try {
|
|
3594
3699
|
const { where: whereClause, limit = 50, offset = 0, include, orderBy, order } = params;
|
|
3595
3700
|
|
|
@@ -3634,20 +3739,34 @@ export async function listRecords(
|
|
|
3634
3739
|
const offsetParam = \`$\${paramIndex + 1}\`;
|
|
3635
3740
|
const allParams = [...whereParams, limit, offset];
|
|
3636
3741
|
|
|
3742
|
+
// Get total count for pagination
|
|
3743
|
+
const countText = \`SELECT COUNT(*) FROM "\${ctx.table}" \${whereSQL}\`;
|
|
3744
|
+
log.debug(\`LIST \${ctx.table} COUNT SQL:\`, countText, "params:", whereParams);
|
|
3745
|
+
const countResult = await ctx.pg.query(countText, whereParams);
|
|
3746
|
+
const total = parseInt(countResult.rows[0].count, 10);
|
|
3747
|
+
|
|
3748
|
+
// Get paginated data
|
|
3637
3749
|
const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} \${orderBySQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
|
|
3638
3750
|
log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
|
|
3639
3751
|
|
|
3640
3752
|
const { rows } = await ctx.pg.query(text, allParams);
|
|
3641
3753
|
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3754
|
+
// Calculate hasMore
|
|
3755
|
+
const hasMore = offset + limit < total;
|
|
3756
|
+
|
|
3757
|
+
const metadata = {
|
|
3758
|
+
data: rows,
|
|
3759
|
+
total,
|
|
3760
|
+
limit,
|
|
3761
|
+
offset,
|
|
3762
|
+
hasMore,
|
|
3763
|
+
needsIncludes: !!include,
|
|
3764
|
+
includeSpec: include,
|
|
3765
|
+
status: 200
|
|
3766
|
+
};
|
|
3646
3767
|
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
log.debug(\`LIST \${ctx.table} include spec:\`, include);
|
|
3650
|
-
return { data: rows, needsIncludes: true, includeSpec: include, status: 200 };
|
|
3768
|
+
log.debug(\`LIST \${ctx.table} result: \${rows.length} rows, \${total} total, hasMore=\${hasMore}\`);
|
|
3769
|
+
return metadata;
|
|
3651
3770
|
} catch (e: any) {
|
|
3652
3771
|
log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
|
|
3653
3772
|
return {
|
|
@@ -3789,8 +3908,11 @@ describe('${Type} SDK Operations', () => {
|
|
|
3789
3908
|
});
|
|
3790
3909
|
|
|
3791
3910
|
it('should list ${tableName} relationships', async () => {
|
|
3792
|
-
const
|
|
3793
|
-
expect(
|
|
3911
|
+
const result = await sdk.${tableName}.list({ limit: 10 });
|
|
3912
|
+
expect(result).toBeDefined();
|
|
3913
|
+
expect(Array.isArray(result.data)).toBe(true);
|
|
3914
|
+
expect(typeof result.total).toBe('number');
|
|
3915
|
+
expect(typeof result.hasMore).toBe('boolean');
|
|
3794
3916
|
});
|
|
3795
3917
|
});
|
|
3796
3918
|
`;
|
|
@@ -4426,8 +4548,11 @@ function generateTestCases(table, sampleData, updateData, hasForeignKeys = false
|
|
|
4426
4548
|
});
|
|
4427
4549
|
|
|
4428
4550
|
it('should list ${table.name}', async () => {
|
|
4429
|
-
const
|
|
4430
|
-
expect(
|
|
4551
|
+
const result = await sdk.${table.name}.list({ limit: 10 });
|
|
4552
|
+
expect(result).toBeDefined();
|
|
4553
|
+
expect(Array.isArray(result.data)).toBe(true);
|
|
4554
|
+
expect(typeof result.total).toBe('number');
|
|
4555
|
+
expect(typeof result.hasMore).toBe('boolean');
|
|
4431
4556
|
});
|
|
4432
4557
|
|
|
4433
4558
|
${hasData && hasSinglePK ? `it('should get ${table.name} by id', async () => {
|