postgresdk 0.9.9 → 0.10.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/cli.js +593 -85
- package/dist/emit-where-types.d.ts +4 -0
- package/dist/index.js +534 -42
- package/package.json +11 -2
package/dist/index.js
CHANGED
|
@@ -1169,6 +1169,187 @@ function generateUnifiedContractMarkdown(contract) {
|
|
|
1169
1169
|
lines.push("");
|
|
1170
1170
|
}
|
|
1171
1171
|
}
|
|
1172
|
+
lines.push("## Filtering with WHERE Clauses");
|
|
1173
|
+
lines.push("");
|
|
1174
|
+
lines.push("The SDK provides type-safe WHERE clause filtering with support for various operators.");
|
|
1175
|
+
lines.push("");
|
|
1176
|
+
lines.push("### Basic Filtering");
|
|
1177
|
+
lines.push("");
|
|
1178
|
+
lines.push("**Direct equality:**");
|
|
1179
|
+
lines.push("");
|
|
1180
|
+
lines.push("```typescript");
|
|
1181
|
+
lines.push("// Find users with specific email");
|
|
1182
|
+
lines.push("const users = await sdk.users.list({");
|
|
1183
|
+
lines.push(" where: { email: 'user@example.com' }");
|
|
1184
|
+
lines.push("});");
|
|
1185
|
+
lines.push("");
|
|
1186
|
+
lines.push("// Multiple conditions (AND)");
|
|
1187
|
+
lines.push("const activeUsers = await sdk.users.list({");
|
|
1188
|
+
lines.push(" where: {");
|
|
1189
|
+
lines.push(" status: 'active',");
|
|
1190
|
+
lines.push(" role: 'admin'");
|
|
1191
|
+
lines.push(" }");
|
|
1192
|
+
lines.push("});");
|
|
1193
|
+
lines.push("```");
|
|
1194
|
+
lines.push("");
|
|
1195
|
+
lines.push("### Comparison Operators");
|
|
1196
|
+
lines.push("");
|
|
1197
|
+
lines.push("Use comparison operators for numeric, date, and other comparable fields:");
|
|
1198
|
+
lines.push("");
|
|
1199
|
+
lines.push("```typescript");
|
|
1200
|
+
lines.push("// Greater than / Less than");
|
|
1201
|
+
lines.push("const adults = await sdk.users.list({");
|
|
1202
|
+
lines.push(" where: { age: { $gt: 18 } }");
|
|
1203
|
+
lines.push("});");
|
|
1204
|
+
lines.push("");
|
|
1205
|
+
lines.push("// Range queries");
|
|
1206
|
+
lines.push("const workingAge = await sdk.users.list({");
|
|
1207
|
+
lines.push(" where: {");
|
|
1208
|
+
lines.push(" age: { $gte: 18, $lte: 65 }");
|
|
1209
|
+
lines.push(" }");
|
|
1210
|
+
lines.push("});");
|
|
1211
|
+
lines.push("");
|
|
1212
|
+
lines.push("// Not equal");
|
|
1213
|
+
lines.push("const notPending = await sdk.orders.list({");
|
|
1214
|
+
lines.push(" where: { status: { $ne: 'pending' } }");
|
|
1215
|
+
lines.push("});");
|
|
1216
|
+
lines.push("```");
|
|
1217
|
+
lines.push("");
|
|
1218
|
+
lines.push("### String Operators");
|
|
1219
|
+
lines.push("");
|
|
1220
|
+
lines.push("Pattern matching for string fields:");
|
|
1221
|
+
lines.push("");
|
|
1222
|
+
lines.push("```typescript");
|
|
1223
|
+
lines.push("// Case-sensitive LIKE");
|
|
1224
|
+
lines.push("const johnsmiths = await sdk.users.list({");
|
|
1225
|
+
lines.push(" where: { name: { $like: '%Smith%' } }");
|
|
1226
|
+
lines.push("});");
|
|
1227
|
+
lines.push("");
|
|
1228
|
+
lines.push("// Case-insensitive ILIKE");
|
|
1229
|
+
lines.push("const gmailUsers = await sdk.users.list({");
|
|
1230
|
+
lines.push(" where: { email: { $ilike: '%@gmail.com' } }");
|
|
1231
|
+
lines.push("});");
|
|
1232
|
+
lines.push("```");
|
|
1233
|
+
lines.push("");
|
|
1234
|
+
lines.push("### Array Operators");
|
|
1235
|
+
lines.push("");
|
|
1236
|
+
lines.push("Filter by multiple possible values:");
|
|
1237
|
+
lines.push("");
|
|
1238
|
+
lines.push("```typescript");
|
|
1239
|
+
lines.push("// IN - match any value in array");
|
|
1240
|
+
lines.push("const specificUsers = await sdk.users.list({");
|
|
1241
|
+
lines.push(" where: {");
|
|
1242
|
+
lines.push(" id: { $in: ['id1', 'id2', 'id3'] }");
|
|
1243
|
+
lines.push(" }");
|
|
1244
|
+
lines.push("});");
|
|
1245
|
+
lines.push("");
|
|
1246
|
+
lines.push("// NOT IN - exclude values");
|
|
1247
|
+
lines.push("const nonSystemUsers = await sdk.users.list({");
|
|
1248
|
+
lines.push(" where: {");
|
|
1249
|
+
lines.push(" role: { $nin: ['admin', 'system'] }");
|
|
1250
|
+
lines.push(" }");
|
|
1251
|
+
lines.push("});");
|
|
1252
|
+
lines.push("```");
|
|
1253
|
+
lines.push("");
|
|
1254
|
+
lines.push("### NULL Checks");
|
|
1255
|
+
lines.push("");
|
|
1256
|
+
lines.push("Check for null or non-null values:");
|
|
1257
|
+
lines.push("");
|
|
1258
|
+
lines.push("```typescript");
|
|
1259
|
+
lines.push("// IS NULL");
|
|
1260
|
+
lines.push("const activeRecords = await sdk.records.list({");
|
|
1261
|
+
lines.push(" where: { deleted_at: { $is: null } }");
|
|
1262
|
+
lines.push("});");
|
|
1263
|
+
lines.push("");
|
|
1264
|
+
lines.push("// IS NOT NULL");
|
|
1265
|
+
lines.push("const deletedRecords = await sdk.records.list({");
|
|
1266
|
+
lines.push(" where: { deleted_at: { $isNot: null } }");
|
|
1267
|
+
lines.push("});");
|
|
1268
|
+
lines.push("```");
|
|
1269
|
+
lines.push("");
|
|
1270
|
+
lines.push("### Combining Operators");
|
|
1271
|
+
lines.push("");
|
|
1272
|
+
lines.push("Mix multiple operators for complex queries:");
|
|
1273
|
+
lines.push("");
|
|
1274
|
+
lines.push("```typescript");
|
|
1275
|
+
lines.push("const filteredUsers = await sdk.users.list({");
|
|
1276
|
+
lines.push(" where: {");
|
|
1277
|
+
lines.push(" age: { $gte: 18, $lt: 65 },");
|
|
1278
|
+
lines.push(" email: { $ilike: '%@company.com' },");
|
|
1279
|
+
lines.push(" status: { $in: ['active', 'pending'] },");
|
|
1280
|
+
lines.push(" deleted_at: { $is: null }");
|
|
1281
|
+
lines.push(" },");
|
|
1282
|
+
lines.push(" limit: 50,");
|
|
1283
|
+
lines.push(" offset: 0");
|
|
1284
|
+
lines.push("});");
|
|
1285
|
+
lines.push("```");
|
|
1286
|
+
lines.push("");
|
|
1287
|
+
lines.push("### Available Operators");
|
|
1288
|
+
lines.push("");
|
|
1289
|
+
lines.push("| Operator | Description | Example | Types |");
|
|
1290
|
+
lines.push("|----------|-------------|---------|-------|");
|
|
1291
|
+
lines.push("| `$eq` | Equal to | `{ age: { $eq: 25 } }` | All |");
|
|
1292
|
+
lines.push("| `$ne` | Not equal to | `{ status: { $ne: 'inactive' } }` | All |");
|
|
1293
|
+
lines.push("| `$gt` | Greater than | `{ price: { $gt: 100 } }` | Number, Date |");
|
|
1294
|
+
lines.push("| `$gte` | Greater than or equal | `{ age: { $gte: 18 } }` | Number, Date |");
|
|
1295
|
+
lines.push("| `$lt` | Less than | `{ quantity: { $lt: 10 } }` | Number, Date |");
|
|
1296
|
+
lines.push("| `$lte` | Less than or equal | `{ age: { $lte: 65 } }` | Number, Date |");
|
|
1297
|
+
lines.push("| `$in` | In array | `{ id: { $in: ['a', 'b'] } }` | All |");
|
|
1298
|
+
lines.push("| `$nin` | Not in array | `{ role: { $nin: ['admin'] } }` | All |");
|
|
1299
|
+
lines.push("| `$like` | Pattern match (case-sensitive) | `{ name: { $like: '%John%' } }` | String |");
|
|
1300
|
+
lines.push("| `$ilike` | Pattern match (case-insensitive) | `{ email: { $ilike: '%@GMAIL%' } }` | String |");
|
|
1301
|
+
lines.push("| `$is` | IS NULL | `{ deleted_at: { $is: null } }` | Nullable fields |");
|
|
1302
|
+
lines.push("| `$isNot` | IS NOT NULL | `{ created_by: { $isNot: null } }` | Nullable fields |");
|
|
1303
|
+
lines.push("");
|
|
1304
|
+
lines.push("### Logical Operators");
|
|
1305
|
+
lines.push("");
|
|
1306
|
+
lines.push("Combine conditions using `$or` and `$and` (supports 2 levels of nesting):");
|
|
1307
|
+
lines.push("");
|
|
1308
|
+
lines.push("| Operator | Description | Example |");
|
|
1309
|
+
lines.push("|----------|-------------|---------|");
|
|
1310
|
+
lines.push("| `$or` | Match any condition | `{ $or: [{ status: 'active' }, { role: 'admin' }] }` |");
|
|
1311
|
+
lines.push("| `$and` | Match all conditions (explicit) | `{ $and: [{ age: { $gte: 18 } }, { status: 'verified' }] }` |");
|
|
1312
|
+
lines.push("");
|
|
1313
|
+
lines.push("```typescript");
|
|
1314
|
+
lines.push("// OR - match any condition");
|
|
1315
|
+
lines.push("const results = await sdk.users.list({");
|
|
1316
|
+
lines.push(" where: {");
|
|
1317
|
+
lines.push(" $or: [");
|
|
1318
|
+
lines.push(" { email: { $ilike: '%@gmail.com' } },");
|
|
1319
|
+
lines.push(" { status: 'premium' }");
|
|
1320
|
+
lines.push(" ]");
|
|
1321
|
+
lines.push(" }");
|
|
1322
|
+
lines.push("});");
|
|
1323
|
+
lines.push("");
|
|
1324
|
+
lines.push("// Mixed AND + OR (implicit AND at root level)");
|
|
1325
|
+
lines.push("const complex = await sdk.users.list({");
|
|
1326
|
+
lines.push(" where: {");
|
|
1327
|
+
lines.push(" status: 'active', // AND");
|
|
1328
|
+
lines.push(" $or: [");
|
|
1329
|
+
lines.push(" { age: { $lt: 18 } },");
|
|
1330
|
+
lines.push(" { age: { $gt: 65 } }");
|
|
1331
|
+
lines.push(" ]");
|
|
1332
|
+
lines.push(" }");
|
|
1333
|
+
lines.push("});");
|
|
1334
|
+
lines.push("");
|
|
1335
|
+
lines.push("// Nested (2 levels max)");
|
|
1336
|
+
lines.push("const nested = await sdk.users.list({");
|
|
1337
|
+
lines.push(" where: {");
|
|
1338
|
+
lines.push(" $and: [");
|
|
1339
|
+
lines.push(" {");
|
|
1340
|
+
lines.push(" $or: [");
|
|
1341
|
+
lines.push(" { firstName: { $ilike: '%john%' } },");
|
|
1342
|
+
lines.push(" { lastName: { $ilike: '%john%' } }");
|
|
1343
|
+
lines.push(" ]");
|
|
1344
|
+
lines.push(" },");
|
|
1345
|
+
lines.push(" { status: 'active' }");
|
|
1346
|
+
lines.push(" ]");
|
|
1347
|
+
lines.push(" }");
|
|
1348
|
+
lines.push("});");
|
|
1349
|
+
lines.push("```");
|
|
1350
|
+
lines.push("");
|
|
1351
|
+
lines.push("**Note:** The WHERE clause types are fully type-safe. TypeScript will only allow operators that are valid for each field type.");
|
|
1352
|
+
lines.push("");
|
|
1172
1353
|
lines.push("## Resources");
|
|
1173
1354
|
lines.push("");
|
|
1174
1355
|
for (const resource of contract.resources) {
|
|
@@ -1493,7 +1674,14 @@ function buildGraph(model) {
|
|
|
1493
1674
|
|
|
1494
1675
|
// src/emit-include-spec.ts
|
|
1495
1676
|
function emitIncludeSpec(graph) {
|
|
1496
|
-
let out =
|
|
1677
|
+
let out = `/**
|
|
1678
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
1679
|
+
*
|
|
1680
|
+
* This file was automatically generated by PostgreSDK.
|
|
1681
|
+
* Any manual changes will be overwritten on the next generation.
|
|
1682
|
+
*
|
|
1683
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
1684
|
+
*/
|
|
1497
1685
|
`;
|
|
1498
1686
|
const tables = Object.keys(graph);
|
|
1499
1687
|
for (const table of tables) {
|
|
@@ -1522,7 +1710,14 @@ function toPascal(s) {
|
|
|
1522
1710
|
|
|
1523
1711
|
// src/emit-include-builder.ts
|
|
1524
1712
|
function emitIncludeBuilder(graph, maxDepth) {
|
|
1525
|
-
return
|
|
1713
|
+
return `/**
|
|
1714
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
1715
|
+
*
|
|
1716
|
+
* This file was automatically generated by PostgreSDK.
|
|
1717
|
+
* Any manual changes will be overwritten on the next generation.
|
|
1718
|
+
*
|
|
1719
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
1720
|
+
*/
|
|
1526
1721
|
export const RELATION_GRAPH = ${JSON.stringify(graph, null, 2)} as const;
|
|
1527
1722
|
type TableName = keyof typeof RELATION_GRAPH;
|
|
1528
1723
|
|
|
@@ -1670,7 +1865,14 @@ function emitHonoRoutes(table, _graph, opts) {
|
|
|
1670
1865
|
const hasAuth = opts.authStrategy && opts.authStrategy !== "none";
|
|
1671
1866
|
const ext = opts.useJsExtensions ? ".js" : "";
|
|
1672
1867
|
const authImport = hasAuth ? `import { authMiddleware } from "../auth${ext}";` : "";
|
|
1673
|
-
return
|
|
1868
|
+
return `/**
|
|
1869
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
1870
|
+
*
|
|
1871
|
+
* This file was automatically generated by PostgreSDK.
|
|
1872
|
+
* Any manual changes will be overwritten on the next generation.
|
|
1873
|
+
*
|
|
1874
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
1875
|
+
*/
|
|
1674
1876
|
import { Hono } from "hono";
|
|
1675
1877
|
import { z } from "zod";
|
|
1676
1878
|
import { Insert${Type}Schema, Update${Type}Schema } from "../zod/${fileTableName}${ext}";
|
|
@@ -1850,7 +2052,7 @@ function emitClient(table, graph, opts, model) {
|
|
|
1850
2052
|
let includeMethodsCode = "";
|
|
1851
2053
|
for (const method of includeMethods) {
|
|
1852
2054
|
const isGetByPk = method.name.startsWith("getByPk");
|
|
1853
|
-
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?:
|
|
2055
|
+
const baseParams = isGetByPk ? "" : `params?: Omit<{ limit?: number; offset?: number; where?: Where<Select${Type}>; orderBy?: string; order?: "asc" | "desc"; }, "include">`;
|
|
1854
2056
|
if (isGetByPk) {
|
|
1855
2057
|
const pkWhere = hasCompositePk ? `{ ${safePk.map((col) => `${col}: pk.${col}`).join(", ")} }` : `{ ${safePk[0] || "id"}: pk }`;
|
|
1856
2058
|
const baseReturnType = method.returnType.replace(" | null", "");
|
|
@@ -1872,8 +2074,16 @@ function emitClient(table, graph, opts, model) {
|
|
|
1872
2074
|
`;
|
|
1873
2075
|
}
|
|
1874
2076
|
}
|
|
1875
|
-
return
|
|
2077
|
+
return `/**
|
|
2078
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2079
|
+
*
|
|
2080
|
+
* This file was automatically generated by PostgreSDK.
|
|
2081
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2082
|
+
*
|
|
2083
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2084
|
+
*/
|
|
1876
2085
|
import { BaseClient } from "./base-client${ext}";
|
|
2086
|
+
import type { Where } from "./where-types${ext}";
|
|
1877
2087
|
${typeImports}
|
|
1878
2088
|
${otherTableImports.join(`
|
|
1879
2089
|
`)}
|
|
@@ -1893,10 +2103,11 @@ export class ${Type}Client extends BaseClient {
|
|
|
1893
2103
|
return this.get<Select${Type} | null>(\`\${this.resource}/\${path}\`);
|
|
1894
2104
|
}
|
|
1895
2105
|
|
|
1896
|
-
async list(params?: {
|
|
1897
|
-
|
|
2106
|
+
async list(params?: {
|
|
2107
|
+
include?: any;
|
|
2108
|
+
limit?: number;
|
|
1898
2109
|
offset?: number;
|
|
1899
|
-
where?:
|
|
2110
|
+
where?: Where<Select${Type}>;
|
|
1900
2111
|
orderBy?: string;
|
|
1901
2112
|
order?: "asc" | "desc";
|
|
1902
2113
|
}): Promise<Select${Type}[]> {
|
|
@@ -1917,7 +2128,14 @@ ${includeMethodsCode}}
|
|
|
1917
2128
|
}
|
|
1918
2129
|
function emitClientIndex(tables, useJsExtensions) {
|
|
1919
2130
|
const ext = useJsExtensions ? ".js" : "";
|
|
1920
|
-
let out =
|
|
2131
|
+
let out = `/**
|
|
2132
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2133
|
+
*
|
|
2134
|
+
* This file was automatically generated by PostgreSDK.
|
|
2135
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2136
|
+
*
|
|
2137
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2138
|
+
*/
|
|
1921
2139
|
`;
|
|
1922
2140
|
out += `import { BaseClient, type AuthConfig } from "./base-client${ext}";
|
|
1923
2141
|
`;
|
|
@@ -1958,6 +2176,17 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
1958
2176
|
out += `export { BaseClient } from "./base-client${ext}";
|
|
1959
2177
|
`;
|
|
1960
2178
|
out += `
|
|
2179
|
+
// Include specification types for custom queries
|
|
2180
|
+
`;
|
|
2181
|
+
out += `export type {
|
|
2182
|
+
`;
|
|
2183
|
+
for (const t of tables) {
|
|
2184
|
+
out += ` ${pascal(t.name)}IncludeSpec,
|
|
2185
|
+
`;
|
|
2186
|
+
}
|
|
2187
|
+
out += `} from "./include-spec${ext}";
|
|
2188
|
+
`;
|
|
2189
|
+
out += `
|
|
1961
2190
|
// Zod schemas for form validation
|
|
1962
2191
|
`;
|
|
1963
2192
|
for (const t of tables) {
|
|
@@ -1979,7 +2208,14 @@ export type { AuthConfig, HeaderMap, AuthHeadersProvider } from "./base-client${
|
|
|
1979
2208
|
|
|
1980
2209
|
// src/emit-base-client.ts
|
|
1981
2210
|
function emitBaseClient() {
|
|
1982
|
-
return
|
|
2211
|
+
return `/**
|
|
2212
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2213
|
+
*
|
|
2214
|
+
* This file was automatically generated by PostgreSDK.
|
|
2215
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2216
|
+
*
|
|
2217
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2218
|
+
*/
|
|
1983
2219
|
|
|
1984
2220
|
export type HeaderMap = Record<string, string>;
|
|
1985
2221
|
export type AuthHeadersProvider = () => Promise<HeaderMap> | HeaderMap;
|
|
@@ -2126,7 +2362,14 @@ function emitIncludeLoader(graph, model, maxDepth, useJsExtensions) {
|
|
|
2126
2362
|
fkIndex[t.name] = t.fks.map((f) => ({ from: f.from, toTable: f.toTable, to: f.to }));
|
|
2127
2363
|
}
|
|
2128
2364
|
const ext = useJsExtensions ? ".js" : "";
|
|
2129
|
-
return
|
|
2365
|
+
return `/**
|
|
2366
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2367
|
+
*
|
|
2368
|
+
* This file was automatically generated by PostgreSDK.
|
|
2369
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2370
|
+
*
|
|
2371
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2372
|
+
*/
|
|
2130
2373
|
import { RELATION_GRAPH } from "./include-builder${ext}";
|
|
2131
2374
|
|
|
2132
2375
|
// Minimal types to keep the file self-contained
|
|
@@ -2447,7 +2690,14 @@ function emitTypes(table, opts) {
|
|
|
2447
2690
|
return ` ${col.name}: ${valueType};`;
|
|
2448
2691
|
}).join(`
|
|
2449
2692
|
`);
|
|
2450
|
-
return
|
|
2693
|
+
return `/**
|
|
2694
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2695
|
+
*
|
|
2696
|
+
* This file was automatically generated by PostgreSDK.
|
|
2697
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2698
|
+
*
|
|
2699
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2700
|
+
*/
|
|
2451
2701
|
export type Insert${Type} = {
|
|
2452
2702
|
${insertFields}
|
|
2453
2703
|
};
|
|
@@ -2462,7 +2712,14 @@ ${selectFields}
|
|
|
2462
2712
|
|
|
2463
2713
|
// src/emit-logger.ts
|
|
2464
2714
|
function emitLogger() {
|
|
2465
|
-
return
|
|
2715
|
+
return `/**
|
|
2716
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2717
|
+
*
|
|
2718
|
+
* This file was automatically generated by PostgreSDK.
|
|
2719
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2720
|
+
*
|
|
2721
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2722
|
+
*/
|
|
2466
2723
|
const DEBUG = process.env.SDK_DEBUG === "1" || process.env.SDK_DEBUG === "true";
|
|
2467
2724
|
|
|
2468
2725
|
export const logger = {
|
|
@@ -2495,6 +2752,76 @@ export function safe<T extends (c: any) => any>(handler: T) {
|
|
|
2495
2752
|
`;
|
|
2496
2753
|
}
|
|
2497
2754
|
|
|
2755
|
+
// src/emit-where-types.ts
|
|
2756
|
+
function emitWhereTypes() {
|
|
2757
|
+
return `/**
|
|
2758
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2759
|
+
*
|
|
2760
|
+
* This file was automatically generated by PostgreSDK.
|
|
2761
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2762
|
+
*
|
|
2763
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2764
|
+
*/
|
|
2765
|
+
|
|
2766
|
+
/**
|
|
2767
|
+
* WHERE clause operators for filtering
|
|
2768
|
+
*/
|
|
2769
|
+
export type WhereOperator<T> = {
|
|
2770
|
+
/** Equal to */
|
|
2771
|
+
$eq?: T;
|
|
2772
|
+
/** Not equal to */
|
|
2773
|
+
$ne?: T;
|
|
2774
|
+
/** Greater than */
|
|
2775
|
+
$gt?: T;
|
|
2776
|
+
/** Greater than or equal to */
|
|
2777
|
+
$gte?: T;
|
|
2778
|
+
/** Less than */
|
|
2779
|
+
$lt?: T;
|
|
2780
|
+
/** Less than or equal to */
|
|
2781
|
+
$lte?: T;
|
|
2782
|
+
/** In array */
|
|
2783
|
+
$in?: T[];
|
|
2784
|
+
/** Not in array */
|
|
2785
|
+
$nin?: T[];
|
|
2786
|
+
/** LIKE pattern match (strings only) */
|
|
2787
|
+
$like?: T extends string ? string : never;
|
|
2788
|
+
/** Case-insensitive LIKE (strings only) */
|
|
2789
|
+
$ilike?: T extends string ? string : never;
|
|
2790
|
+
/** IS NULL */
|
|
2791
|
+
$is?: null;
|
|
2792
|
+
/** IS NOT NULL */
|
|
2793
|
+
$isNot?: null;
|
|
2794
|
+
};
|
|
2795
|
+
|
|
2796
|
+
/**
|
|
2797
|
+
* WHERE condition - can be a direct value or an operator object
|
|
2798
|
+
*/
|
|
2799
|
+
export type WhereCondition<T> = T | WhereOperator<T>;
|
|
2800
|
+
|
|
2801
|
+
/**
|
|
2802
|
+
* Field-level WHERE conditions (without logical operators)
|
|
2803
|
+
*/
|
|
2804
|
+
export type WhereFieldConditions<T> = {
|
|
2805
|
+
[K in keyof T]?: WhereCondition<T[K]>;
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
/**
|
|
2809
|
+
* WHERE clause type with support for $or/$and logical operators (2 levels max)
|
|
2810
|
+
*
|
|
2811
|
+
* Examples:
|
|
2812
|
+
* - Basic OR: { $or: [{ name: 'Alice' }, { name: 'Bob' }] }
|
|
2813
|
+
* - Mixed AND + OR: { status: 'active', $or: [{ age: { $gt: 65 } }, { age: { $lt: 18 } }] }
|
|
2814
|
+
* - Nested (2 levels): { $and: [{ $or: [{ name: 'Alice' }, { name: 'Bob' }] }, { status: 'active' }] }
|
|
2815
|
+
*/
|
|
2816
|
+
export type Where<T> = WhereFieldConditions<T> & {
|
|
2817
|
+
/** OR - at least one condition must be true */
|
|
2818
|
+
$or?: (WhereFieldConditions<T>)[];
|
|
2819
|
+
/** AND - all conditions must be true (alternative to implicit root-level AND) */
|
|
2820
|
+
$and?: (WhereFieldConditions<T> | { $or?: WhereFieldConditions<T>[] })[];
|
|
2821
|
+
};
|
|
2822
|
+
`;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2498
2825
|
// src/emit-auth.ts
|
|
2499
2826
|
function emitAuth(cfgAuth) {
|
|
2500
2827
|
const strategy = cfgAuth?.strategy ?? "none";
|
|
@@ -2509,7 +2836,14 @@ function emitAuth(cfgAuth) {
|
|
|
2509
2836
|
const JWT_SHARED_SECRET = JSON.stringify(jwtShared);
|
|
2510
2837
|
const JWT_ISSUER = jwtIssuer === undefined ? "undefined" : JSON.stringify(jwtIssuer);
|
|
2511
2838
|
const JWT_AUDIENCE = jwtAudience === undefined ? "undefined" : JSON.stringify(jwtAudience);
|
|
2512
|
-
return
|
|
2839
|
+
return `/**
|
|
2840
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
2841
|
+
*
|
|
2842
|
+
* This file was automatically generated by PostgreSDK.
|
|
2843
|
+
* Any manual changes will be overwritten on the next generation.
|
|
2844
|
+
*
|
|
2845
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
2846
|
+
*/
|
|
2513
2847
|
import type { Context, Next } from "hono";
|
|
2514
2848
|
|
|
2515
2849
|
// ---- Config inlined by generator ----
|
|
@@ -2666,7 +3000,14 @@ function emitHonoRouter(tables, hasAuth, useJsExtensions) {
|
|
|
2666
3000
|
return `export { register${Type}Routes } from "./routes/${name}${ext}";`;
|
|
2667
3001
|
}).join(`
|
|
2668
3002
|
`);
|
|
2669
|
-
return
|
|
3003
|
+
return `/**
|
|
3004
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3005
|
+
*
|
|
3006
|
+
* This file was automatically generated by PostgreSDK.
|
|
3007
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3008
|
+
*
|
|
3009
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3010
|
+
*/
|
|
2670
3011
|
import { Hono } from "hono";
|
|
2671
3012
|
import { SDK_MANIFEST } from "./sdk-bundle${ext}";
|
|
2672
3013
|
import { getContract } from "./contract${ext}";
|
|
@@ -2800,7 +3141,14 @@ function emitSdkBundle(clientFiles, clientDir) {
|
|
|
2800
3141
|
}
|
|
2801
3142
|
const version = `1.0.0`;
|
|
2802
3143
|
const generated = new Date().toISOString();
|
|
2803
|
-
return
|
|
3144
|
+
return `/**
|
|
3145
|
+
* AUTO-GENERATED FILE - DO NOT EDIT
|
|
3146
|
+
*
|
|
3147
|
+
* This file was automatically generated by PostgreSDK.
|
|
3148
|
+
* Any manual changes will be overwritten on the next generation.
|
|
3149
|
+
*
|
|
3150
|
+
* To make changes, modify your schema or configuration and regenerate.
|
|
3151
|
+
*/
|
|
2804
3152
|
|
|
2805
3153
|
export const SDK_MANIFEST = {
|
|
2806
3154
|
version: "${version}",
|
|
@@ -2904,6 +3252,155 @@ export async function getByPk(
|
|
|
2904
3252
|
}
|
|
2905
3253
|
}
|
|
2906
3254
|
|
|
3255
|
+
/**
|
|
3256
|
+
* Build WHERE clause recursively, supporting $or/$and operators
|
|
3257
|
+
* Returns { sql: string, params: any[], nextParamIndex: number }
|
|
3258
|
+
*/
|
|
3259
|
+
function buildWhereClause(
|
|
3260
|
+
whereClause: any,
|
|
3261
|
+
startParamIndex: number
|
|
3262
|
+
): { sql: string; params: any[]; nextParamIndex: number } {
|
|
3263
|
+
const whereParts: string[] = [];
|
|
3264
|
+
const whereParams: any[] = [];
|
|
3265
|
+
let paramIndex = startParamIndex;
|
|
3266
|
+
|
|
3267
|
+
if (!whereClause || typeof whereClause !== 'object') {
|
|
3268
|
+
return { sql: '', params: [], nextParamIndex: paramIndex };
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
// Separate logical operators from field conditions
|
|
3272
|
+
const { $or, $and, ...fieldConditions } = whereClause;
|
|
3273
|
+
|
|
3274
|
+
// Process field-level conditions
|
|
3275
|
+
for (const [key, value] of Object.entries(fieldConditions)) {
|
|
3276
|
+
if (value === undefined) {
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
// Handle operator objects like { $gt: 5, $like: "%test%" }
|
|
3281
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
3282
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
3283
|
+
if (opValue === undefined) continue;
|
|
3284
|
+
|
|
3285
|
+
switch (op) {
|
|
3286
|
+
case '$eq':
|
|
3287
|
+
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
3288
|
+
whereParams.push(opValue);
|
|
3289
|
+
paramIndex++;
|
|
3290
|
+
break;
|
|
3291
|
+
case '$ne':
|
|
3292
|
+
whereParts.push(\`"\${key}" != $\${paramIndex}\`);
|
|
3293
|
+
whereParams.push(opValue);
|
|
3294
|
+
paramIndex++;
|
|
3295
|
+
break;
|
|
3296
|
+
case '$gt':
|
|
3297
|
+
whereParts.push(\`"\${key}" > $\${paramIndex}\`);
|
|
3298
|
+
whereParams.push(opValue);
|
|
3299
|
+
paramIndex++;
|
|
3300
|
+
break;
|
|
3301
|
+
case '$gte':
|
|
3302
|
+
whereParts.push(\`"\${key}" >= $\${paramIndex}\`);
|
|
3303
|
+
whereParams.push(opValue);
|
|
3304
|
+
paramIndex++;
|
|
3305
|
+
break;
|
|
3306
|
+
case '$lt':
|
|
3307
|
+
whereParts.push(\`"\${key}" < $\${paramIndex}\`);
|
|
3308
|
+
whereParams.push(opValue);
|
|
3309
|
+
paramIndex++;
|
|
3310
|
+
break;
|
|
3311
|
+
case '$lte':
|
|
3312
|
+
whereParts.push(\`"\${key}" <= $\${paramIndex}\`);
|
|
3313
|
+
whereParams.push(opValue);
|
|
3314
|
+
paramIndex++;
|
|
3315
|
+
break;
|
|
3316
|
+
case '$in':
|
|
3317
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
3318
|
+
whereParts.push(\`"\${key}" = ANY($\${paramIndex})\`);
|
|
3319
|
+
whereParams.push(opValue);
|
|
3320
|
+
paramIndex++;
|
|
3321
|
+
}
|
|
3322
|
+
break;
|
|
3323
|
+
case '$nin':
|
|
3324
|
+
if (Array.isArray(opValue) && opValue.length > 0) {
|
|
3325
|
+
whereParts.push(\`"\${key}" != ALL($\${paramIndex})\`);
|
|
3326
|
+
whereParams.push(opValue);
|
|
3327
|
+
paramIndex++;
|
|
3328
|
+
}
|
|
3329
|
+
break;
|
|
3330
|
+
case '$like':
|
|
3331
|
+
whereParts.push(\`"\${key}" LIKE $\${paramIndex}\`);
|
|
3332
|
+
whereParams.push(opValue);
|
|
3333
|
+
paramIndex++;
|
|
3334
|
+
break;
|
|
3335
|
+
case '$ilike':
|
|
3336
|
+
whereParts.push(\`"\${key}" ILIKE $\${paramIndex}\`);
|
|
3337
|
+
whereParams.push(opValue);
|
|
3338
|
+
paramIndex++;
|
|
3339
|
+
break;
|
|
3340
|
+
case '$is':
|
|
3341
|
+
if (opValue === null) {
|
|
3342
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
3343
|
+
}
|
|
3344
|
+
break;
|
|
3345
|
+
case '$isNot':
|
|
3346
|
+
if (opValue === null) {
|
|
3347
|
+
whereParts.push(\`"\${key}" IS NOT NULL\`);
|
|
3348
|
+
}
|
|
3349
|
+
break;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
} else if (value === null) {
|
|
3353
|
+
// Direct null value
|
|
3354
|
+
whereParts.push(\`"\${key}" IS NULL\`);
|
|
3355
|
+
} else {
|
|
3356
|
+
// Direct value (simple equality)
|
|
3357
|
+
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
3358
|
+
whereParams.push(value);
|
|
3359
|
+
paramIndex++;
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
// Handle $or operator
|
|
3364
|
+
if ($or && Array.isArray($or)) {
|
|
3365
|
+
if ($or.length === 0) {
|
|
3366
|
+
// Empty OR is logically FALSE - matches nothing
|
|
3367
|
+
whereParts.push('FALSE');
|
|
3368
|
+
} else {
|
|
3369
|
+
const orParts: string[] = [];
|
|
3370
|
+
for (const orCondition of $or) {
|
|
3371
|
+
const result = buildWhereClause(orCondition, paramIndex);
|
|
3372
|
+
if (result.sql) {
|
|
3373
|
+
orParts.push(result.sql);
|
|
3374
|
+
whereParams.push(...result.params);
|
|
3375
|
+
paramIndex = result.nextParamIndex;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
if (orParts.length > 0) {
|
|
3379
|
+
whereParts.push(\`(\${orParts.join(' OR ')})\`);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
// Handle $and operator
|
|
3385
|
+
if ($and && Array.isArray($and) && $and.length > 0) {
|
|
3386
|
+
const andParts: string[] = [];
|
|
3387
|
+
for (const andCondition of $and) {
|
|
3388
|
+
const result = buildWhereClause(andCondition, paramIndex);
|
|
3389
|
+
if (result.sql) {
|
|
3390
|
+
andParts.push(result.sql);
|
|
3391
|
+
whereParams.push(...result.params);
|
|
3392
|
+
paramIndex = result.nextParamIndex;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
if (andParts.length > 0) {
|
|
3396
|
+
whereParts.push(\`(\${andParts.join(' AND ')})\`);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
const sql = whereParts.join(' AND ');
|
|
3401
|
+
return { sql, params: whereParams, nextParamIndex: paramIndex };
|
|
3402
|
+
}
|
|
3403
|
+
|
|
2907
3404
|
/**
|
|
2908
3405
|
* LIST operation - Get multiple records with optional filters
|
|
2909
3406
|
*/
|
|
@@ -2913,60 +3410,54 @@ export async function listRecords(
|
|
|
2913
3410
|
): Promise<{ data?: any; error?: string; issues?: any; needsIncludes?: boolean; includeSpec?: any; status: number }> {
|
|
2914
3411
|
try {
|
|
2915
3412
|
const { where: whereClause, limit = 50, offset = 0, include } = params;
|
|
2916
|
-
|
|
3413
|
+
|
|
2917
3414
|
// Build WHERE clause
|
|
2918
|
-
const whereParts: string[] = [];
|
|
2919
|
-
const whereParams: any[] = [];
|
|
2920
3415
|
let paramIndex = 1;
|
|
2921
|
-
|
|
3416
|
+
const whereParts: string[] = [];
|
|
3417
|
+
let whereParams: any[] = [];
|
|
3418
|
+
|
|
2922
3419
|
// Add soft delete filter if applicable
|
|
2923
3420
|
if (ctx.softDeleteColumn) {
|
|
2924
3421
|
whereParts.push(\`"\${ctx.softDeleteColumn}" IS NULL\`);
|
|
2925
3422
|
}
|
|
2926
|
-
|
|
3423
|
+
|
|
2927
3424
|
// Add user-provided where conditions
|
|
2928
|
-
if (whereClause
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
continue;
|
|
2935
|
-
} else {
|
|
2936
|
-
whereParts.push(\`"\${key}" = $\${paramIndex}\`);
|
|
2937
|
-
whereParams.push(value);
|
|
2938
|
-
paramIndex++;
|
|
2939
|
-
}
|
|
3425
|
+
if (whereClause) {
|
|
3426
|
+
const result = buildWhereClause(whereClause, paramIndex);
|
|
3427
|
+
if (result.sql) {
|
|
3428
|
+
whereParts.push(result.sql);
|
|
3429
|
+
whereParams = result.params;
|
|
3430
|
+
paramIndex = result.nextParamIndex;
|
|
2940
3431
|
}
|
|
2941
3432
|
}
|
|
2942
|
-
|
|
3433
|
+
|
|
2943
3434
|
const whereSQL = whereParts.length > 0 ? \`WHERE \${whereParts.join(" AND ")}\` : "";
|
|
2944
|
-
|
|
3435
|
+
|
|
2945
3436
|
// Add limit and offset params
|
|
2946
3437
|
const limitParam = \`$\${paramIndex}\`;
|
|
2947
3438
|
const offsetParam = \`$\${paramIndex + 1}\`;
|
|
2948
3439
|
const allParams = [...whereParams, limit, offset];
|
|
2949
|
-
|
|
3440
|
+
|
|
2950
3441
|
const text = \`SELECT * FROM "\${ctx.table}" \${whereSQL} LIMIT \${limitParam} OFFSET \${offsetParam}\`;
|
|
2951
3442
|
log.debug(\`LIST \${ctx.table} SQL:\`, text, "params:", allParams);
|
|
2952
|
-
|
|
3443
|
+
|
|
2953
3444
|
const { rows } = await ctx.pg.query(text, allParams);
|
|
2954
|
-
|
|
3445
|
+
|
|
2955
3446
|
if (!include) {
|
|
2956
3447
|
log.debug(\`LIST \${ctx.table} rows:\`, rows.length);
|
|
2957
3448
|
return { data: rows, status: 200 };
|
|
2958
3449
|
}
|
|
2959
|
-
|
|
3450
|
+
|
|
2960
3451
|
// Include logic will be handled by the include-loader
|
|
2961
3452
|
// For now, just return the rows with a note that includes need to be applied
|
|
2962
3453
|
log.debug(\`LIST \${ctx.table} include spec:\`, include);
|
|
2963
3454
|
return { data: rows, needsIncludes: true, includeSpec: include, status: 200 };
|
|
2964
3455
|
} catch (e: any) {
|
|
2965
3456
|
log.error(\`LIST \${ctx.table} error:\`, e?.stack ?? e);
|
|
2966
|
-
return {
|
|
2967
|
-
error: e?.message ?? "Internal error",
|
|
3457
|
+
return {
|
|
3458
|
+
error: e?.message ?? "Internal error",
|
|
2968
3459
|
...(DEBUG ? { stack: e?.stack } : {}),
|
|
2969
|
-
status: 500
|
|
3460
|
+
status: 500
|
|
2970
3461
|
};
|
|
2971
3462
|
}
|
|
2972
3463
|
}
|
|
@@ -3868,6 +4359,7 @@ async function generate(configPath) {
|
|
|
3868
4359
|
files.push({ path: join(clientDir, "include-spec.ts"), content: includeSpec });
|
|
3869
4360
|
files.push({ path: join(clientDir, "params", "shared.ts"), content: emitSharedParamsZod() });
|
|
3870
4361
|
files.push({ path: join(clientDir, "base-client.ts"), content: emitBaseClient() });
|
|
4362
|
+
files.push({ path: join(clientDir, "where-types.ts"), content: emitWhereTypes() });
|
|
3871
4363
|
files.push({
|
|
3872
4364
|
path: join(serverDir, "include-builder.ts"),
|
|
3873
4365
|
content: emitIncludeBuilder(graph, cfg.includeMethodsDepth || 2)
|